node-red-contrib-knx-ultimate 6.2.3-beta.4 → 6.3.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 (32) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/nodes/hue-config.js +13 -0
  3. package/nodes/knxUltimate.html +52 -58
  4. package/nodes/knxUltimateHueController.html +246 -35
  5. package/nodes/knxUltimateMatterControllerDevice.html +15 -3
  6. package/nodes/locales/de/knxUltimate.html +1 -1
  7. package/nodes/locales/de/knxUltimateHueController.html +3 -2
  8. package/nodes/locales/de/knxUltimateHueController.json +7 -0
  9. package/nodes/locales/de/knxUltimateMatterControllerDevice.html +2 -0
  10. package/nodes/locales/en/knxUltimate.html +1 -1
  11. package/nodes/locales/en/knxUltimateHueController.html +3 -2
  12. package/nodes/locales/en/knxUltimateHueController.json +7 -0
  13. package/nodes/locales/en/knxUltimateMatterControllerDevice.html +2 -0
  14. package/nodes/locales/es/knxUltimate.html +1 -1
  15. package/nodes/locales/es/knxUltimateHueController.html +3 -2
  16. package/nodes/locales/es/knxUltimateHueController.json +7 -0
  17. package/nodes/locales/es/knxUltimateMatterControllerDevice.html +2 -0
  18. package/nodes/locales/fr/knxUltimate.html +1 -1
  19. package/nodes/locales/fr/knxUltimateHueController.html +3 -2
  20. package/nodes/locales/fr/knxUltimateHueController.json +7 -0
  21. package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +2 -0
  22. package/nodes/locales/it/knxUltimate.html +1 -1
  23. package/nodes/locales/it/knxUltimateHueController.html +3 -2
  24. package/nodes/locales/it/knxUltimateHueController.json +7 -0
  25. package/nodes/locales/it/knxUltimateMatterControllerDevice.html +2 -0
  26. package/nodes/locales/zh-CN/knxUltimate.html +1 -1
  27. package/nodes/locales/zh-CN/knxUltimateHueController.html +3 -2
  28. package/nodes/locales/zh-CN/knxUltimateHueController.json +7 -0
  29. package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +2 -0
  30. package/nodes/utils/hueControllerResourceCatalog.js +71 -0
  31. package/package.json +1 -1
  32. package/resources/hueControllerProfiles.js +7 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,17 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 6.3.0** - August 2026<br/>
10
+
11
+ - **HUE Controller**: unified device-first workflow for all supported Hue API v2 resources, with automatic type detection and legacy-node migration.<br/>
12
+ - **Matter**: clearer commissioning progress and refined Controller/Bridge workflows and documentation.<br/>
13
+ - **Editor pickers**: Hue, Matter and KNX Group Address lists now open reliably and show every available entry before filtering.<br/>
14
+
15
+ **Version 6.2.3-beta.5** - August 2026<br/>
16
+
17
+ - **HUE Controller**: device-first selection with automatic type detection and all supported Hue API v2 resources.<br/>
18
+ - **Device pickers**: Hue, Matter and KNX Group Address lists now stay open on click and show all entries before filtering.<br/>
19
+
9
20
  **Version 6.2.3-beta.4** - August 2026<br/>
10
21
 
11
22
  - **Matter Controller — live commissioning progress**: replaced the pairing overlay spinner with a milestone-based progress bar driven by the actual matter.js commissioning flow. The blocking panel now describes the active discovery, PASE, device-information, fail-safe, regulatory, time-sync, attestation, credential, access-control, CASE reconnect and completion phase in English; when exposed by the commissionee, its product name, Vendor ID and Product ID appear below the current operation. Progress polling is local to the Node-RED editor, stops with the pairing request and is isolated by operation ID across editor tabs.<br/>
@@ -5,6 +5,10 @@
5
5
  /* eslint-disable no-inner-declarations */
6
6
  /* eslint-disable max-len */
7
7
  const cloneDeep = require('lodash/cloneDeep')
8
+ const {
9
+ HUE_CONTROLLER_RESOURCE_TYPES,
10
+ buildHueControllerResourceCatalog
11
+ } = require('./utils/hueControllerResourceCatalog')
8
12
  // const classHUE = require("./utils/hueEngine").classHUE;
9
13
  const hueColorConverter = require('./utils/colorManipulators/hueColorConverter')
10
14
 
@@ -355,6 +359,15 @@ module.exports = (RED) => {
355
359
  }
356
360
  }
357
361
  if (node.hueAllResources === undefined) return
362
+ if (_rtype === 'hue_controller') {
363
+ const resourceResults = await Promise.all(HUE_CONTROLLER_RESOURCE_TYPES.map(async (resourceType) => {
364
+ const result = await node.getResources(resourceType)
365
+ return [resourceType, Array.isArray(result?.devices) ? result.devices : []]
366
+ }))
367
+ return {
368
+ devices: buildHueControllerResourceCatalog(Object.fromEntries(resourceResults))
369
+ }
370
+ }
358
371
  // Returns capitalized string
359
372
  function capStr (s) {
360
373
  if (typeof s !== 'string') return ''
@@ -1,24 +1,25 @@
1
1
  <!-- <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/jquery.searchableSelect.js"></script> -->
2
2
 
3
3
  <style>
4
- /* Monaco editor - first area (input to bus): light green */
5
- #sendMsgToKNXCode-editor .monaco-editor,
6
- #sendMsgToKNXCode-editor .monaco-editor-background,
7
- #sendMsgToKNXCode-editor .monaco-editor .margin,
8
- #sendMsgToKNXCode-editor .monaco-editor .overflow-guard,
9
- #sendMsgToKNXCode-editor .monaco-editor .lines-content,
10
- #sendMsgToKNXCode-editor .monaco-editor .editor-scrollable {
11
- background-color: #e8f5e9 !important;
12
- }
13
- /* Monaco editor - second area (bus to output): light yellow */
14
- #receiveMsgFromKNXCode-editor .monaco-editor,
15
- #receiveMsgFromKNXCode-editor .monaco-editor-background,
16
- #receiveMsgFromKNXCode-editor .monaco-editor .margin,
17
- #receiveMsgFromKNXCode-editor .monaco-editor .overflow-guard,
18
- #receiveMsgFromKNXCode-editor .monaco-editor .lines-content,
19
- #receiveMsgFromKNXCode-editor .monaco-editor .editor-scrollable {
20
- background-color: #fffde7 !important;
21
- }
4
+ /* Monaco editor - first area (input to bus): light green */
5
+ #sendMsgToKNXCode-editor .monaco-editor,
6
+ #sendMsgToKNXCode-editor .monaco-editor-background,
7
+ #sendMsgToKNXCode-editor .monaco-editor .margin,
8
+ #sendMsgToKNXCode-editor .monaco-editor .overflow-guard,
9
+ #sendMsgToKNXCode-editor .monaco-editor .lines-content,
10
+ #sendMsgToKNXCode-editor .monaco-editor .editor-scrollable {
11
+ background-color: #e8f5e9 !important;
12
+ }
13
+
14
+ /* Monaco editor - second area (bus to output): light yellow */
15
+ #receiveMsgFromKNXCode-editor .monaco-editor,
16
+ #receiveMsgFromKNXCode-editor .monaco-editor-background,
17
+ #receiveMsgFromKNXCode-editor .monaco-editor .margin,
18
+ #receiveMsgFromKNXCode-editor .monaco-editor .overflow-guard,
19
+ #receiveMsgFromKNXCode-editor .monaco-editor .lines-content,
20
+ #receiveMsgFromKNXCode-editor .monaco-editor .editor-scrollable {
21
+ background-color: #fffde7 !important;
22
+ }
22
23
  </style>
23
24
 
24
25
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script>
@@ -76,19 +77,19 @@
76
77
  return ((this.outputRBE === "true" || this.outputRBE === true) ? "|rbe| " : "") + functionSendMsgToKNXCode + (this.name || this.topic || "KNX Device") + (this.setTopicType === 'str' || this.setTopicType === undefined ? '' : ' [' + (this.setTopicType === 'listenAllGA' ? 'Universal' : this.setTopicType) + ']') + functionreceiveMsgFromKNXCode + ((this.inputRBE === "true" || this.inputRBE === true) ? " |rbe|" : "")
77
78
  },
78
79
  paletteLabel: "KNX DEVICE",
79
- button: {
80
- enabled: function () {
81
- return !this.changed;
82
- },
83
- visible: function () {
84
- const isUniversal = this.setTopicType === 'listenAllGA' || this.listenallga === true || this.listenallga === 'true';
85
- if (isUniversal) return false;
86
- return this.buttonEnabled === true || this.buttonEnabled === "true";
87
- },
88
- onclick: function () {
89
- const node = this;
90
- const mode = node.buttonMode || 'toggle';
91
- const request = { id: node.id, mode };
80
+ button: {
81
+ enabled: function () {
82
+ return !this.changed;
83
+ },
84
+ visible: function () {
85
+ const isUniversal = this.setTopicType === 'listenAllGA' || this.listenallga === true || this.listenallga === 'true';
86
+ if (isUniversal) return false;
87
+ return this.buttonEnabled === true || this.buttonEnabled === "true";
88
+ },
89
+ onclick: function () {
90
+ const node = this;
91
+ const mode = node.buttonMode || 'toggle';
92
+ const request = { id: node.id, mode };
92
93
  if (mode === 'value') {
93
94
  request.value = node.buttonStaticValue;
94
95
  }
@@ -148,6 +149,7 @@
148
149
 
149
150
 
150
151
  var node = this;
152
+ let topicPickerMouseDown = false;
151
153
  if ($("#node-input-server").val() === "_ADD_") {
152
154
  // Node-Red 4.0.x has a bug not selecting the default server node
153
155
  try {
@@ -964,8 +966,20 @@ return msg;`
964
966
  }
965
967
  } catch (e) { }
966
968
  }
967
- }).focus(function () {
968
- $(this).autocomplete('search', $(this).val() + 'exactmatch');
969
+ }).on('mousedown.knxUltimateTopicPicker', function () {
970
+ topicPickerMouseDown = true;
971
+ }).on('focus.knxUltimateTopicPicker', function () {
972
+ if (!topicPickerMouseDown) {
973
+ $(this).autocomplete('search', '');
974
+ }
975
+ }).on('click.knxUltimateTopicPicker', function () {
976
+ topicPickerMouseDown = false;
977
+ const input = this;
978
+ setTimeout(function () {
979
+ $(input).autocomplete('search', '');
980
+ }, 0);
981
+ }).on('blur.knxUltimateTopicPicker', function () {
982
+ topicPickerMouseDown = false;
969
983
  }).autocomplete("instance")._renderItem = function (ul, item) {
970
984
  const isSecure = !!item.isSecure;
971
985
  const colorStyle = isSecure ? 'color: green;' : '';
@@ -1185,10 +1199,6 @@ return msg;`
1185
1199
  <input id="node-input-gaSecure" name="node-input-gaSecure" type="hidden" value="false" />
1186
1200
 
1187
1201
  <div class="form-row">
1188
- <b>KNX Device node</b>
1189
- <br />
1190
- <br />
1191
-
1192
1202
  <label for="node-input-server">
1193
1203
  <i class="fa fa-circle-o"></i> <span data-i18n="knxUltimate.properties.node-input-server"></span>
1194
1204
  </label>
@@ -1209,13 +1219,14 @@ return msg;`
1209
1219
  <option value="global" data-i18n="knxUltimate.selectlists.SetTopic_global"></option>
1210
1220
  <option value="env" data-i18n="knxUltimate.selectlists.SetTopic_env"></option>
1211
1221
  </select>
1212
- <input type="text" id="node-input-topic" data-i18n="[placeholder]knxUltimate.placeholder.search"
1222
+ <input type="text" id="node-input-topic" data-i18n="[placeholder]knxUltimate.placeholder.search"
1213
1223
  style="width:180px" />
1214
1224
  <span id="gaSecureShield" title="Data Secure" style="display:none;color:green;margin-left:6px;"><i
1215
1225
  class="fa fa-shield"></i></span>
1226
+ <input style="flex:1 1 240px; min-width:240px; max-width:240px;" type="text" id="node-input-name"
1227
+ data-i18n="[placeholder]knxUltimate.properties.node-input-name" />
1216
1228
  </div>
1217
1229
 
1218
-
1219
1230
  <div class="form-row" id="divDatapointSelection">
1220
1231
  <label for="node-input-dpt">
1221
1232
  <img
@@ -1229,19 +1240,10 @@ return msg;`
1229
1240
  </div>
1230
1241
  <div class="form-row">
1231
1242
  <span id="dptDetailsContainer" style="display:none;width:100%;">
1232
- <div id="sampleCodeEditor" class="dpt-details-helplink" style="color:red;margin-bottom:4px;"></div>
1233
- <div class="node-text-editor" id="example-editor" style="height:200px;min-height:150px;padding:0px;"></div>
1243
+ <!-- <div id="sampleCodeEditor" class="dpt-details-helplink" style="color:red;margin-bottom:4px;"></div> -->
1244
+ <div class="node-text-editor" id="example-editor" style="height:110px;padding:0px;"></div>
1234
1245
  </span>
1235
1246
  </div>
1236
- <!-- <div class="form-row">
1237
- <input type="checkbox" id="node-input-listenallga"
1238
- style="display:inline-block; width:auto; vertical-align:top;" />
1239
- <label style="width:auto" for="node-input-listenallga">
1240
- <img
1241
- src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAB7CAAAewgFu0HU+AAAFGmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMSAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMjAtMDMtMjNUMTY6MjM6MjMrMDE6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDIwLTAzLTIzVDE2OjI1OjM0KzAxOjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIwLTAzLTIzVDE2OjI1OjM0KzAxOjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmJmNGM3NWVjLTIwNGYtNGY1YS05YTMxLTQ5NTU5YWJmZDE4NSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpiZjRjNzVlYy0yMDRmLTRmNWEtOWEzMS00OTU1OWFiZmQxODUiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpiZjRjNzVlYy0yMDRmLTRmNWEtOWEzMS00OTU1OWFiZmQxODUiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmJmNGM3NWVjLTIwNGYtNGY1YS05YTMxLTQ5NTU5YWJmZDE4NSIgc3RFdnQ6d2hlbj0iMjAyMC0wMy0yM1QxNjoyMzoyMyswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDIxLjEgKE1hY2ludG9zaCkiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+nhtLUgAAAE9JREFUKJG1UMsOACAIgub//7IdqvVQtjrExU1EEQLuiGCvgTNgl5D74MmVZPu4wIxQAgm+/sDec2VhgQPgf0sq1unjMlYJE/3MZrvy+kMFZQkZEWfC7ikAAAAASUVORK5CYII="></img>
1242
- <span data-i18n="knxUltimate.properties.node-input-listenallga"></span>
1243
- </label>
1244
- </div> -->
1245
1247
  <div id="tabs">
1246
1248
  <ul>
1247
1249
  <li><a href="#tabs-1"><i class="fa fa-braille"></i> Advanced options</a></li>
@@ -1254,14 +1256,6 @@ return msg;`
1254
1256
  <i class="fa fa-desktop"></i> General properties
1255
1257
  </dt>
1256
1258
  </div>
1257
- <div class="form-row">
1258
- <label style="width:180px" for="node-input-name">
1259
- <i class="fa fa-tag"></i>
1260
- <span data-i18n="knxUltimate.properties.node-input-name"></span>
1261
- </label>
1262
- <input style="flex:1 1 240px; min-width:240px; max-width:240px;" type="text" id="node-input-name"
1263
- data-i18n="[placeholder]knxUltimate.properties.node-input-name" />
1264
- </div>
1265
1259
  <div class="form-row" id="divTopic">
1266
1260
  <label style="width:180px" for="node-input-outputtopic">
1267
1261
  <i class="fa fa-tasks"></i>
@@ -36,6 +36,7 @@
36
36
  // Node-RED 4.x uses `_ADD_` both for the visible "none" option (when
37
37
  // gateways exist) and for "Add new..." (when none exist).
38
38
  const KNX_EMPTY_SERVER_VALUES = new Set(['', 'none', '_add_', '__none__']);
39
+ const HUE_EMPTY_SERVER_VALUES = new Set(['', 'none', '_add_', '__none__']);
39
40
  const TUTORIALS_URL = 'https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E';
40
41
  const profileBundle = window.KNXUltimateHueControllerProfiles;
41
42
 
@@ -294,6 +295,11 @@
294
295
  let legacyContext;
295
296
  let closeMigrationDialog = () => { };
296
297
  const profileDrafts = {};
298
+ let cachedHueResources = [];
299
+ let selectedHueResource;
300
+ let activeHueServerId;
301
+ let hueDevicePickerMouseDown = false;
302
+ const defaultHueDevicePlaceholder = $('#node-input-name').attr('placeholder') || '';
297
303
 
298
304
  const hasLegacyHueNodes = editorContainsLegacyHueNodes();
299
305
  const $migrationButton = $('#hue-controller-migrate-legacy-flow');
@@ -357,6 +363,168 @@
357
363
  });
358
364
  };
359
365
 
366
+ const controllerText = (key, fallback) => {
367
+ try {
368
+ const translated = controllerNode._(`knxUltimateHueController.${key}`);
369
+ if (translated && translated !== `knxUltimateHueController.${key}`) return translated;
370
+ } catch (error) { /* empty */ }
371
+ return fallback;
372
+ };
373
+
374
+ const resolveHueServerId = ({ allowStored = false } = {}) => {
375
+ const domValue = $('#node-input-serverHue').val();
376
+ if (domValue !== undefined && domValue !== null) {
377
+ const normalized = String(domValue).trim();
378
+ if (normalized !== '' && !HUE_EMPTY_SERVER_VALUES.has(normalized.toLowerCase())) return normalized;
379
+ }
380
+ if (allowStored && controllerNode.serverHue !== undefined && controllerNode.serverHue !== null) {
381
+ const stored = String(controllerNode.serverHue).trim();
382
+ if (stored !== '' && !HUE_EMPTY_SERVER_VALUES.has(stored.toLowerCase())) return stored;
383
+ }
384
+ return '';
385
+ };
386
+
387
+ const getHueDeviceValue = () => {
388
+ const domValue = $('#node-input-hueDevice').val();
389
+ if (domValue !== undefined && domValue !== null && String(domValue).trim() !== '') return String(domValue).trim();
390
+ return controllerNode.hueDevice === undefined || controllerNode.hueDevice === null
391
+ ? ''
392
+ : String(controllerNode.hueDevice).trim();
393
+ };
394
+
395
+ const getRawHueResourceId = (value) => String(value || '').split('#')[0];
396
+
397
+ const updateSelectedDevicePresentation = (controllerType = controllerNode.hueControllerType) => {
398
+ const hasDevice = getHueDeviceValue() !== '';
399
+ const profile = PROFILE_TYPES[controllerType] || PROFILE_TYPES.light;
400
+ const typeLabel = controllerText(`types.${controllerType}`, profile.label);
401
+ $('#hue-controller-selected-device-type-value').text(typeLabel);
402
+ $('#hue-controller-selected-device-type').toggle(hasDevice);
403
+ $('#hue-controller-locate-device').toggle(hasDevice && controllerType === 'light');
404
+ $('#hue-controller-profile-editor').toggle(hasDevice);
405
+ $('#node-input-hueControllerType').val(controllerType);
406
+ };
407
+
408
+ const filterHueResources = (term) => {
409
+ const search = String(term || '').replace(/exactmatch/gi, '').trim().toLowerCase();
410
+ return cachedHueResources.filter((item) => {
411
+ if (search === '') return true;
412
+ return String(item.name || item.id || '').toLowerCase().includes(search);
413
+ }).map((item) => ({
414
+ ...item,
415
+ value: item.name || item.id,
416
+ label: item.name || item.id
417
+ }));
418
+ };
419
+
420
+ const reconcileSavedHueResource = () => {
421
+ const savedDevice = getHueDeviceValue();
422
+ if (savedDevice === '') return;
423
+ const rawId = getRawHueResourceId(savedDevice);
424
+ const match = cachedHueResources.find((item) => item.hueDevice === savedDevice)
425
+ || cachedHueResources.find((item) => item.id === rawId && item.controllerType === controllerNode.hueControllerType)
426
+ || cachedHueResources.find((item) => item.id === rawId);
427
+ if (!match) {
428
+ updateSelectedDevicePresentation();
429
+ return;
430
+ }
431
+ selectedHueResource = match;
432
+ if (!$('#node-input-name').val()) $('#node-input-name').val(match.name || match.id);
433
+ if (match.controllerType !== controllerNode.hueControllerType) {
434
+ applyHueResourceSelection(match);
435
+ return;
436
+ }
437
+ updateSelectedDevicePresentation(match.controllerType);
438
+ };
439
+
440
+ const fetchHueResources = (term, response, { forceRefresh = false } = {}) => {
441
+ const reply = typeof response === 'function' ? response : () => { };
442
+ const hueServerId = resolveHueServerId({ allowStored: true });
443
+ if (hueServerId === '') {
444
+ reply([]);
445
+ return;
446
+ }
447
+ if (!forceRefresh && cachedHueResources.length > 0) {
448
+ reply(filterHueResources(term));
449
+ return;
450
+ }
451
+ $('#hue-controller-devices-loading').show();
452
+ const refreshQuery = forceRefresh ? '&forceRefresh=1' : '';
453
+ $.getJSON(`KNXUltimateGetResourcesHUE?rtype=hue_controller&serverId=${encodeURIComponent(hueServerId)}${refreshQuery}&_=${Date.now()}`, (data) => {
454
+ const devices = Array.isArray(data) ? data : (Array.isArray(data?.devices) ? data.devices : []);
455
+ cachedHueResources = devices.filter((item) => item && PROFILE_TYPES[item.controllerType] && item.id && item.hueDevice);
456
+ $('#node-input-name').attr('placeholder', cachedHueResources.length > 0
457
+ ? defaultHueDevicePlaceholder
458
+ : controllerText('no_devices', 'No Hue resources found. Check the Hue Bridge and press refresh.'));
459
+ reconcileSavedHueResource();
460
+ reply(filterHueResources(term));
461
+ }).fail(() => {
462
+ cachedHueResources = [];
463
+ $('#node-input-name').attr('placeholder', controllerText('no_devices', 'No Hue resources found. Check the Hue Bridge and press refresh.'));
464
+ reply([]);
465
+ }).always(() => {
466
+ $('#hue-controller-devices-loading').hide();
467
+ });
468
+ };
469
+
470
+ const configureUnifiedDeviceControl = () => {
471
+ const $deviceName = $('#node-input-name');
472
+ if (!$deviceName.length) return;
473
+ $deviceName.autocomplete({
474
+ minLength: 0,
475
+ source(request, response) {
476
+ fetchHueResources(request.term, response);
477
+ },
478
+ select(event, ui) {
479
+ applyHueResourceSelection(ui.item);
480
+ return false;
481
+ },
482
+ change(event, ui) {
483
+ if (ui.item || selectedHueResource?.name === $deviceName.val()) return;
484
+ selectedHueResource = undefined;
485
+ controllerNode.hueDevice = '';
486
+ $('#node-input-hueDevice').val('');
487
+ updateSelectedDevicePresentation();
488
+ }
489
+ });
490
+ $deviceName.off('.knxUltimateHueControllerDevice')
491
+ .on('mousedown.knxUltimateHueControllerDevice', () => {
492
+ hueDevicePickerMouseDown = true;
493
+ })
494
+ .on('focus.knxUltimateHueControllerDevice', function () {
495
+ if (!hueDevicePickerMouseDown) $(this).autocomplete('search', '');
496
+ })
497
+ .on('click.knxUltimateHueControllerDevice', function () {
498
+ hueDevicePickerMouseDown = false;
499
+ const input = this;
500
+ setTimeout(() => $(input).autocomplete('search', ''), 0);
501
+ })
502
+ .on('blur.knxUltimateHueControllerDevice', () => {
503
+ hueDevicePickerMouseDown = false;
504
+ });
505
+ };
506
+
507
+ const applyHueResourceSelection = (item) => {
508
+ if (!item || !PROFILE_TYPES[item.controllerType] || !item.hueDevice) return;
509
+ const nextType = item.controllerType;
510
+ const typeChanged = nextType !== controllerNode.hueControllerType;
511
+ collectCurrentEditor();
512
+ if (typeChanged) prepareProfileValues(nextType, getProfileDefinition(nextType));
513
+ selectedHueResource = item;
514
+ controllerNode.hueControllerType = nextType;
515
+ controllerNode.name = item.name || item.id;
516
+ controllerNode.hueDevice = item.hueDevice;
517
+ if (item.deviceObject) controllerNode.hueDeviceObject = cloneEditorValue(item.deviceObject);
518
+ const nextDefinition = getProfileDefinition(nextType);
519
+ controllerNode.inputs = Number(nextDefinition?.inputs || 0);
520
+ controllerNode.outputs = Number(nextDefinition?.outputs || 0);
521
+ $('#node-input-name').val(controllerNode.name);
522
+ $('#node-input-hueDevice').val(controllerNode.hueDevice);
523
+ $('#node-input-hueControllerType').val(nextType);
524
+ renderProfileEditor(nextType, true);
525
+ updateSelectedDevicePresentation(nextType);
526
+ };
527
+
360
528
  const renderProfileEditor = (controllerType, skipCollect) => {
361
529
  // Controlled lifecycle transition:
362
530
  // collect old -> dispose old -> mount private HTML -> translate ->
@@ -403,6 +571,13 @@
403
571
  $profileContent.find('#node-input-server, #node-input-serverHue').each(function () {
404
572
  $(this).closest('.form-row').remove();
405
573
  });
574
+ // Device selection belongs to the unified wrapper. Remove the private
575
+ // profile's duplicate row/hidden value while keeping the static fields
576
+ // available to its mature lifecycle callbacks.
577
+ $profileContent.find('#node-input-name').each(function () {
578
+ $(this).closest('.form-row').remove();
579
+ });
580
+ $profileContent.find('#node-input-hueDevice').remove();
406
581
  $container.empty().append($profileContent.contents());
407
582
  // Node-RED translates the static dialog before oneditprepare. This
408
583
  // fragment belongs to another node type, so qualify every key with the
@@ -428,9 +603,14 @@
428
603
  // Light deliberately receives the real Hue config-node selection.
429
604
  // Its private editor owns the bounded readiness poll and displays the
430
605
  // hourglass until the bridge resources are available or it times out.
606
+ // The Light editor completes asynchronously and installs its historic
607
+ // light-only autocomplete inside Go(). Give it an explicit callback
608
+ // that restores the Controller-wide catalog after that late setup.
609
+ controllerNode.__configureHueControllerDeviceControl = configureUnifiedDeviceControl;
431
610
  if (typeof definition.oneditprepare === 'function') {
432
611
  definition.oneditprepare.call(legacyContext);
433
612
  }
613
+ configureUnifiedDeviceControl();
434
614
  // Profile guidance is already available in the node help. Keeping it
435
615
  // out of the embedded editor makes every device profile more compact.
436
616
  $container.find('.form-tips').remove();
@@ -449,29 +629,46 @@
449
629
  $('#node-input-server')
450
630
  .off('.knxUltimateHueControllerKnxVisibility')
451
631
  .on('change.knxUltimateHueControllerKnxVisibility', updateControllerKnxVisibility);
632
+ updateSelectedDevicePresentation(controllerType);
452
633
  focusProfileHelp(controllerType);
453
634
  };
454
635
 
455
636
  const selectedType = PROFILE_TYPES[this.hueControllerType] ? this.hueControllerType : 'light';
456
- $('#node-input-hueControllerType')
457
- .val(selectedType)
458
- .off('.knxUltimateHueController')
459
- .on('change.knxUltimateHueController', function () {
460
- const nextType = PROFILE_TYPES[$(this).val()] ? $(this).val() : 'light';
461
- const nextDefinition = getProfileDefinition(nextType);
462
- collectCurrentEditor();
463
- prepareProfileValues(nextType, nextDefinition);
464
- // A deliberate function change adopts that function's normal pin shape.
465
- controllerNode.inputs = Number(nextDefinition?.inputs || 0);
466
- controllerNode.outputs = Number(nextDefinition?.outputs || 0);
467
- renderProfileEditor(nextType, true);
637
+ $('#node-input-hueControllerType').val(selectedType);
638
+
639
+ activeHueServerId = resolveHueServerId({ allowStored: true });
640
+ $('#hue-controller-refresh-devices')
641
+ .off('.knxUltimateHueControllerDevice')
642
+ .on('click.knxUltimateHueControllerDevice', () => {
643
+ cachedHueResources = [];
644
+ fetchHueResources('', (items) => {
645
+ if (items.length) $('#node-input-name').autocomplete('search', '');
646
+ }, { forceRefresh: true });
647
+ });
648
+ $('#node-input-serverHue')
649
+ .off('.knxUltimateHueControllerDevice')
650
+ .on('change.knxUltimateHueControllerDevice', () => {
651
+ const nextServerId = resolveHueServerId();
652
+ if (nextServerId === activeHueServerId) return;
653
+ activeHueServerId = nextServerId;
654
+ cachedHueResources = [];
655
+ selectedHueResource = undefined;
656
+ controllerNode.name = '';
657
+ controllerNode.hueDevice = '';
658
+ $('#node-input-name, #node-input-hueDevice').val('');
659
+ updateSelectedDevicePresentation();
468
660
  });
469
661
 
470
662
  this._hueControllerCollectEditor = collectCurrentEditor;
471
663
  this._hueControllerGetLegacyContext = () => legacyContext;
472
664
  this._hueControllerGetProfile = () => activeProfile;
473
665
  this._hueControllerCloseMigration = () => closeMigrationDialog();
666
+ this._hueControllerCleanupDevice = () => {
667
+ $('#hue-controller-refresh-devices, #node-input-serverHue, #node-input-name').off('.knxUltimateHueControllerDevice');
668
+ delete controllerNode.__configureHueControllerDeviceControl;
669
+ };
474
670
  renderProfileEditor(selectedType);
671
+ fetchHueResources('', () => { });
475
672
  },
476
673
  oneditsave() {
477
674
  // Preserve lifecycle order: close auxiliary UI, collect mounted fields,
@@ -496,10 +693,12 @@
496
693
  if (Number.isFinite(Number(legacyContext.inputs))) this.inputs = Number(legacyContext.inputs);
497
694
  if (Number.isFinite(Number(legacyContext.outputs))) this.outputs = Number(legacyContext.outputs);
498
695
  }
696
+ if (typeof this._hueControllerCleanupDevice === 'function') this._hueControllerCleanupDevice();
499
697
  delete this._hueControllerCollectEditor;
500
698
  delete this._hueControllerGetLegacyContext;
501
699
  delete this._hueControllerGetProfile;
502
700
  delete this._hueControllerCloseMigration;
701
+ delete this._hueControllerCleanupDevice;
503
702
  },
504
703
  oneditcancel() {
505
704
  // Cancel releases profile-owned editor state but deliberately does not
@@ -515,7 +714,9 @@
515
714
  if (definition && typeof definition.oneditcancel === 'function') {
516
715
  definition.oneditcancel.call(legacyContext || this);
517
716
  }
717
+ if (typeof this._hueControllerCleanupDevice === 'function') this._hueControllerCleanupDevice();
518
718
  delete this._hueControllerCloseMigration;
719
+ delete this._hueControllerCleanupDevice;
519
720
  }
520
721
  });
521
722
  }());
@@ -551,29 +752,37 @@
551
752
  </label>
552
753
  <input type="text" id="node-input-serverHue">
553
754
  </div>
554
- <div class="form-row">
555
- <label for="node-input-hueControllerType" style="width:160px;">
556
- <i class="fa fa-cubes"></i>
557
- <span data-i18n="knxUltimateHueController.device_function"></span>
755
+ <br />
756
+ <p>
757
+ <b><span data-i18n="knxUltimateHueController.hue_section"></span></b>
758
+ </p>
759
+ <div class="form-row" id="hue-controller-device-row">
760
+ <label for="node-input-name">
761
+ <i class="fa fa-play-circle"></i>
762
+ <span data-i18n="knxUltimateHueController.hue_device"></span>
558
763
  </label>
559
- <select id="node-input-hueControllerType" style="width:33.333%;">
560
- <option value="light" data-i18n="knxUltimateHueController.types.light"></option>
561
- <option value="plug" data-i18n="knxUltimateHueController.types.plug"></option>
562
- <option value="button" data-i18n="knxUltimateHueController.types.button"></option>
563
- <option value="relative_rotary" data-i18n="knxUltimateHueController.types.relative_rotary"></option>
564
- <option value="motion" data-i18n="knxUltimateHueController.types.motion"></option>
565
- <option value="area_motion" data-i18n="knxUltimateHueController.types.area_motion"></option>
566
- <option value="camera_motion" data-i18n="knxUltimateHueController.types.camera_motion"></option>
567
- <option value="contact" data-i18n="knxUltimateHueController.types.contact"></option>
568
- <option value="light_level" data-i18n="knxUltimateHueController.types.light_level"></option>
569
- <option value="temperature" data-i18n="knxUltimateHueController.types.temperature"></option>
570
- <option value="humidity" data-i18n="knxUltimateHueController.types.humidity"></option>
571
- <option value="scene" data-i18n="knxUltimateHueController.types.scene"></option>
572
- <option value="device_power" data-i18n="knxUltimateHueController.types.device_power"></option>
573
- <option value="zigbee_connectivity" data-i18n="knxUltimateHueController.types.zigbee_connectivity"></option>
574
- <option value="device_software_update" data-i18n="knxUltimateHueController.types.device_software_update"></option>
575
- </select>
764
+ <input type="text" id="node-input-name" data-i18n="[placeholder]knxUltimateHueController.hue_device_placeholder"
765
+ style="flex:1 1 240px; min-width:240px; max-width:240px;">
766
+ <button type="button" id="hue-controller-refresh-devices" class="red-ui-button"
767
+ style="margin-left:6px; color:#1b7d33; border-color:#1b7d33;" data-i18n="[title]knxUltimateHueController.refresh_devices">
768
+ <i class="fa fa-sync"></i>
769
+ </button>
770
+ <button type="button" id="hue-controller-locate-device" class="red-ui-button hue-locate-device"
771
+ style="display:none; margin-left:6px; color:#ff9800; border-color:#ff9800;" data-i18n="[title]knxUltimateHueController.locate_device">
772
+ <i class="fa fa-play"></i>
773
+ </button>
774
+ <span id="hue-controller-devices-loading" style="margin-left:6px; display:none; color:#1b7d33;">
775
+ <i class="fa fa-circle-notch fa-spin"></i>
776
+ </span>
777
+ <span id="hue-controller-selected-device-type" style="display:none; margin-left:10px; color:#666; white-space:nowrap;">
778
+ <i class="fa fa-cubes"></i>
779
+ <span data-i18n="knxUltimateHueController.device_type"></span>:
780
+ <span id="hue-controller-selected-device-type-value" style="font-weight:600;"></span>
781
+ </span>
782
+ <input type="hidden" id="node-input-hueDevice">
783
+ <input type="hidden" id="node-input-hueControllerType">
576
784
  </div>
785
+ <br />
577
786
  <div id="hue-controller-profile-editor"></div>
578
787
  <br/>
579
788
  <br/>
@@ -586,7 +795,9 @@
586
795
 
587
796
  [**KNX-Ultimate video tutorials (YouTube playlist)**](https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E)
588
797
 
589
- 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.
798
+ The unified controller for Hue API v2 resources. Choose a Hue device or resource; its device type is detected automatically and the matching KNX mapping, behaviour and node pins are then displayed.
799
+
800
+ Clicking or focusing the Hue device field always opens the complete resource list, even when a device is already selected. Typing continues to filter the list.
590
801
 
591
802
  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.
592
803
 
@@ -610,7 +821,7 @@ For a single light, the **Dim**, **Tunable White**, **RGB/HSV**, and native-effe
610
821
 
611
822
  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.
612
823
 
613
- Changing **Device function** changes the visible editor and the node pin layout. After saving, reopen the node to verify the selected resource and mappings.
824
+ Changing the selected **Hue device** automatically changes the detected **Device type**, visible editor and node pin layout. After saving, reopen the node to verify the selected resource and mappings.
614
825
 
615
826
  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. Saved DPT values are retained while selector options load asynchronously.
616
827
 
@@ -297,6 +297,7 @@
297
297
  $deviceNameInput.val(node.name);
298
298
  }
299
299
  let selectedMatterDeviceName = ($deviceNameInput.val() || '').trim();
300
+ let matterDevicePickerMouseDown = false;
300
301
  let showingNoHueDevicesPlaceholder = false;
301
302
  const HUE_EMPTY_SERVER_VALUES = new Set(['', 'none', '_add_', '__none__', '__null__', 'null', 'undefined']);
302
303
  let locateSessionActive = false;
@@ -1506,8 +1507,16 @@
1506
1507
  focus(event, ui) {
1507
1508
  event.preventDefault();
1508
1509
  }
1509
- }).focus(function () {
1510
- $(this).autocomplete('search', `${$(this).val()}exactmatch`);
1510
+ }).on('mousedown.knxUltimateMatterControllerDevicePicker', () => {
1511
+ matterDevicePickerMouseDown = true;
1512
+ }).on('focus.knxUltimateMatterControllerDevicePicker', function () {
1513
+ if (!matterDevicePickerMouseDown) $(this).autocomplete('search', '');
1514
+ }).on('click.knxUltimateMatterControllerDevicePicker', function () {
1515
+ matterDevicePickerMouseDown = false;
1516
+ const input = this;
1517
+ setTimeout(() => $(input).autocomplete('search', ''), 0);
1518
+ }).on('blur.knxUltimateMatterControllerDevicePicker', () => {
1519
+ matterDevicePickerMouseDown = false;
1511
1520
  }).on('input.knxUltimateMatterControllerDevice', function () {
1512
1521
  const typedValue = $(this).val().trim();
1513
1522
  if (typedValue === '' || typedValue !== selectedMatterDeviceName) {
@@ -1529,7 +1538,7 @@
1529
1538
  node._cachedHueLightDevices = cachedHueDevices;
1530
1539
  fetchHueDevices('', () => {
1531
1540
  if ($deviceNameInput.length) {
1532
- $deviceNameInput.autocomplete('search', `${$deviceNameInput.val()}exactmatch`);
1541
+ $deviceNameInput.autocomplete('search', '');
1533
1542
  }
1534
1543
  }, { forceRefresh: true });
1535
1544
  });
@@ -2668,6 +2677,9 @@ the device picker. Door Lock, Window Covering, Thermostat, Fan and Switch endpoi
2668
2677
  dedicated profiles. Switch press, long-press and multi-press events are emitted on the
2669
2678
  optional flow output; simpler endpoints continue through the generic mapped fallback.
2670
2679
 
2680
+ Clicking or focusing the Matter device field always opens the complete commissioned
2681
+ endpoint list, even when a device is already selected. Typing continues to filter it.
2682
+
2671
2683
  **Flow input**
2672
2684
 
2673
2685
  Enable **Node Input/Output PINs** to reveal the **Flow input** section directly below
@@ -11,7 +11,7 @@ Dieser Node steuert eine KNX-Gruppenadresse und ist der am häufigsten verwendet
11
11
  |--|--|
12
12
  | Gateway | Zu verwendendes KNX-Gateway auswählen |
13
13
  | GA-Typ (Dropdown) | Typ der Gruppenadresse. **3-Ebenen** ist Standard (Eingabe der 3-stufigen GA oder GA-Bezeichnung, sofern ETS importiert). **Global** liest die GA beim Start aus einer globalen Variablen, **Flow** analog auf Flow-Ebene. **$Env variable** liest die GA aus einer Umgebungsvariable. **Universeller Modus (alle GAs abhören)** reagiert auf ALLE Gruppenadressen. |
14
- | Gruppenadresse | Zu steuernde Gruppenadresse. Mit importierter ETS kannst du den Gerätenamen tippen. Kann leer bleiben, wenn du sie per `msg.setConfig` setzt. |
14
+ | Gruppenadresse | Zu steuernde Gruppenadresse. Bei importierter ETS öffnet ein Klick oder Fokus auf dieses Feld die vollständige Liste, auch wenn bereits eine GA ausgewählt ist; durch Tippen werden die Einträge gefiltert. Kann leer bleiben, wenn du sie per `msg.setConfig` setzt. |
15
15
  | Datenpunkt | Der zum Node gehörende Datapoint. |
16
16
 
17
17
  <br/>
@@ -1,8 +1,9 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueController">
2
2
  <p><a href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E" target="_blank" rel="noopener noreferrer"><strong>KNX-Ultimate video tutorials (YouTube playlist)</strong></a> Sehen Sie sich vor dem Start die <a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">Videoanleitung auf YouTube</a> an.</p>
3
3
  <p><strong>HUE Controller</strong> ist der einheitliche Knoten für Hue-API-v2-Ressourcen.</p>
4
- <p>Wählen Sie die Gerätefunktion und konfigurieren Sie anschließend dieselbe KNX-Zuordnung und dasselbe Verhalten wie im bewährten dedizierten Hue-Knoten.</p>
5
- <p>Die Auswahl der Gerätefunktion verwendet ein Drittel der verfügbaren Breite, damit der Editor kompakt bleibt.</p>
4
+ <p>Wählen Sie ein Hue-Gerät oder eine Hue-Ressource. HUE Controller erkennt den Gerätetyp automatisch und öffnet den passenden Editor für KNX-Zuordnungen, Verhalten und Node-Anschlüsse.</p>
5
+ <p>Geräteauswahl, Aktualisierung und erkannter Typ stehen wie beim Matter Controller in derselben kompakten Zeile.</p>
6
+ <p>Ein Klick oder Fokus auf das Gerätefeld öffnet immer die vollständige Hue-Ressourcenliste, auch wenn bereits ein Gerät ausgewählt ist. Beim Tippen wird die Liste weiterhin gefiltert.</p>
6
7
  <p>KNX-Zuordnungszeilen halten GA, DPT und Name in einer Zeile. DPT-Auswahl und Namensfeld verwenden kompakte Breiten; das Namensfeld kann sich in schmalen Editoren weiter verkleinern, ohne den gespeicherten Text zu ändern. Gespeicherte DPT-Werte bleiben erhalten, während die Auswahloptionen asynchron geladen werden.</p>
7
8
  <p>Unterstützt werden Leuchten und Leuchtengruppen, Steckdosen, Taster, Tap dial, Bewegungs- und Kamerabewegungsressourcen, Kontakte, Lichtstärke, Temperatur, Luftfeuchtigkeit, Szenen, Batterie, Zigbee-Verbindung und Softwareupdate.</p>
8
9
  <p>Wenn kein <strong>KNX-Gateway</strong> ausgewählt ist (einschließlich <code>none</code> und <em>Neu hinzufügen...</em>), werden die KNX-Zuordnungsfelder ausgeblendet; die Auswahl der Hue-Ressource und reine Flow-Optionen bleiben verfügbar.</p>
@@ -5,6 +5,13 @@
5
5
  },
6
6
  "knxUltimateHueController": {
7
7
  "device_function": "Gerätefunktion",
8
+ "hue_section": "Philips Hue",
9
+ "hue_device": "Hue-Gerät",
10
+ "hue_device_placeholder": "Hue-Gerät oder -Ressource suchen",
11
+ "device_type": "Gerätetyp",
12
+ "refresh_devices": "Hue-Geräte aktualisieren",
13
+ "locate_device": "Ausgewählte Hue-Leuchte lokalisieren",
14
+ "no_devices": "Keine Hue-Ressourcen gefunden. Prüfen Sie die Hue Bridge und aktualisieren Sie die Liste.",
8
15
  "legacy_node_notice": "Dieser Legacy-HUE-Knoten ist veraltet. Verwenden Sie für neue Flows den neuen HUE Controller-Knoten.",
9
16
  "migration_button": "Legacy-HUE-Knoten konvertieren",
10
17
  "migration_button_title": "Alle Legacy-HUE-Knoten im Editor konvertieren",
@@ -3,6 +3,8 @@
3
3
 
4
4
  Dieser Knoten steuert einen bereits gekoppelten Matter-Endpunkt über KNX. Wähle das Matter-Gerät aus; der Editor erkennt die Fähigkeiten des Endpunkts und zeigt nur die passenden KNX-Zuordnungen.
5
5
 
6
+ Ein Klick oder Fokus auf das Matter-Gerätefeld öffnet immer die vollständige Liste der gekoppelten Endpunkte, auch wenn bereits ein Gerät ausgewählt ist. Beim Tippen wird die Liste weiterhin gefiltert.
7
+
6
8
  Er ersetzt die unveröffentlichten getrennten Matter-Controller-Nodes und behält die komplette Licht-UI bei, wenn der gewählte Endpunkt ein Licht ist.
7
9
 
8
10
  ## Konfiguration
@@ -11,7 +11,7 @@
11
11
  |--|--|
12
12
  | Gateway | Select the KNX gateway to be used |
13
13
  | GA Type dropdown list | The group address type. **3-Levels** is the default, where you can type the _3 level group address_ or the _group address name_ (if you've upload the ETS file), or **Global**, for reading the GA at startup from a global variable, or **Flow** that do the same as the _Global_, but at flow level. Select **$Env variable** to read the group address from an environment variable. Select **Universal mode (listen to all Group Addresses)** to react to ALL group addresses.|
14
- | Group Addr. | The KNX Group Address you want to control. If you've imported the ETS group addresses file, you can just start typing in your device name. You can leave it empty if you wish set it with _msg.setConfig_ input message. |
14
+ | Group Addr. | The KNX Group Address you want to control. If you've imported the ETS group addresses file, clicking or focusing this field opens the complete list, even when a GA is already selected; typing filters the entries. You can leave it empty if you wish set it with _msg.setConfig_ input message. |
15
15
  | Datapoint | The datapoint belonging to your node.|
16
16
 
17
17
  <br/>
@@ -1,8 +1,9 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueController">
2
2
  <p><a href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E" target="_blank" rel="noopener noreferrer"><strong>KNX-Ultimate video tutorials (YouTube playlist)</strong></a> <a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">Watch the explanatory video on YouTube</a> before starting.</p>
3
3
  <p><strong>HUE Controller</strong> is the unified node for Hue API v2 resources.</p>
4
- <p>Select the device function, then configure the same KNX mapping and behaviour provided by the established dedicated Hue node.</p>
5
- <p>The device function selector uses one third of the available width to keep the editor compact.</p>
4
+ <p>Select a Hue device or resource. HUE Controller detects its device type automatically and opens the matching KNX mapping, behaviour and node-pin editor.</p>
5
+ <p>The device picker, refresh action and detected type share the same compact row, matching the Matter Controller workflow.</p>
6
+ <p>Clicking or focusing the device field always opens the complete Hue resource list, even when a device is already selected. Typing continues to filter the list.</p>
6
7
  <p>KNX mapping rows keep GA, DPT and Name on one line. DPT selectors and Name fields use compact widths, and the Name field can contract further on narrow editor trays without changing its stored text. Saved DPT values are retained while the selector options load asynchronously.</p>
7
8
  <p>Supported functions: lights and grouped lights, plugs, buttons, Tap dial, motion and camera motion, contacts, light level, temperature, humidity, scenes, battery, Zigbee connectivity, and device software update.</p>
8
9
  <p>When no <strong>KNX Gateway</strong> is selected (including <code>none</code> and <em>Add new...</em>), KNX mapping fields are hidden while Hue resource selection and flow-only options remain available.</p>
@@ -5,6 +5,13 @@
5
5
  },
6
6
  "knxUltimateHueController": {
7
7
  "device_function": "Device function",
8
+ "hue_section": "Philips Hue",
9
+ "hue_device": "Hue device",
10
+ "hue_device_placeholder": "Search your Hue device or resource",
11
+ "device_type": "Device type",
12
+ "refresh_devices": "Refresh Hue devices",
13
+ "locate_device": "Locate selected Hue light",
14
+ "no_devices": "No Hue resources found. Check the Hue Bridge and press refresh.",
8
15
  "legacy_node_notice": "This legacy HUE node is deprecated. Use the new HUE Controller node for new flows.",
9
16
  "migration_button": "Convert legacy HUE nodes",
10
17
  "migration_button_title": "Convert all legacy HUE nodes in the editor",
@@ -3,6 +3,8 @@
3
3
 
4
4
  This node controls a commissioned Matter endpoint from KNX. Select the Matter device and the editor detects its capabilities, then shows only the KNX mappings that make sense for that endpoint.
5
5
 
6
+ Clicking or focusing the Matter device field always opens the complete commissioned endpoint list, even when a device is already selected. Typing continues to filter it.
7
+
6
8
  It replaces the unpublished per-device Matter controller nodes and keeps the full light UI when the selected endpoint is a light.
7
9
 
8
10
  ## Configuration
@@ -11,7 +11,7 @@
11
11
  |-|-|
12
12
  | Puerta | Seleccione la puerta de enlace KNX para ser utilizada |
13
13
  | Lista desplegable Tipo de GA | El tipo de dirección de grupo. **3 niveles** es el valor predeterminado, donde puede escribir la dirección de grupo de nivel _3 o el _group name_ (si ha cargado el archivo ETS), o **global**, para leer el GA al inicio desde una variable global, o **flujo** que hace lo mismo que el _global_, pero a nivel de flujo. Seleccione **$Env variable** para leer la dirección de grupo de una variable de entorno. Seleccione **Modo universal (escuche todas las direcciones de grupo)** para reaccionar a todas las direcciones de grupo. |
14
- | ADR DE GRUPO. | La dirección de grupo KNX que desea controlar. Si ha importado el archivo de direcciones del grupo ETS, puede comenzar a escribir el nombre de su dispositivo. Puede dejarlo vacío si lo desea establecerlo con _MSG.SetConfig_ Mensaje de entrada. |
14
+ | ADR DE GRUPO. | La dirección de grupo KNX que desea controlar. Si ha importado el archivo de direcciones de grupo ETS, al hacer clic o enfocar este campo se abre la lista completa, aunque ya haya una GA seleccionada; al escribir se filtran las entradas. Puede dejarlo vacío si desea establecerlo con el mensaje de entrada _msg.setConfig_. |
15
15
  | Punto de datos | El punto de datos que pertenece a su nodo. |
16
16
 
17
17
  <br/>
@@ -1,8 +1,9 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueController">
2
2
  <p><a href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E" target="_blank" rel="noopener noreferrer"><strong>KNX-Ultimate video tutorials (YouTube playlist)</strong></a> Antes de empezar, <a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">mira el vídeo explicativo en YouTube</a>.</p>
3
3
  <p><strong>HUE Controller</strong> es el nodo unificado para recursos de Hue API v2.</p>
4
- <p>Selecciona la función del dispositivo y configura el mismo mapeo KNX y comportamiento del nodo Hue dedicado ya consolidado.</p>
5
- <p>El selector de función utiliza un tercio del ancho disponible para mantener compacto el editor.</p>
4
+ <p>Selecciona un dispositivo o recurso Hue. HUE Controller detecta automáticamente su tipo y abre el editor correspondiente para mapeos KNX, comportamiento y puertos del nodo.</p>
5
+ <p>El selector de dispositivo, la actualización y el tipo detectado comparten la misma fila compacta, como en Matter Controller.</p>
6
+ <p>Al hacer clic o enfocar el campo del dispositivo siempre se abre la lista completa de recursos Hue, aunque ya haya un dispositivo seleccionado. Al escribir, la lista sigue filtrándose.</p>
6
7
  <p>Las filas de mapeo KNX mantienen GA, DPT y Nombre en una sola línea. Los campos DPT y Nombre usan anchos compactos; Nombre puede reducirse aún más en editores estrechos sin modificar el texto guardado. Los valores DPT guardados se conservan mientras las opciones del selector se cargan de forma asíncrona.</p>
7
8
  <p>Admite luces y grupos de luces, enchufes, botones, Tap dial, movimiento y movimiento de cámara, contactos, nivel de luz, temperatura, humedad, escenas, batería, conectividad Zigbee y actualización de software.</p>
8
9
  <p>Cuando no hay ningún <strong>Gateway KNX</strong> seleccionado (incluidos <code>none</code> y <em>Añadir nuevo...</em>), se ocultan los campos de mapeo KNX; la selección del recurso Hue y las opciones exclusivas del flujo siguen disponibles.</p>
@@ -5,6 +5,13 @@
5
5
  },
6
6
  "knxUltimateHueController": {
7
7
  "device_function": "Función del dispositivo",
8
+ "hue_section": "Philips Hue",
9
+ "hue_device": "Dispositivo Hue",
10
+ "hue_device_placeholder": "Buscar dispositivo o recurso Hue",
11
+ "device_type": "Tipo de dispositivo",
12
+ "refresh_devices": "Actualizar dispositivos Hue",
13
+ "locate_device": "Localizar la luz Hue seleccionada",
14
+ "no_devices": "No se encontraron recursos Hue. Comprueba Hue Bridge y actualiza la lista.",
8
15
  "legacy_node_notice": "Este nodo HUE legacy está obsoleto. Usa el nuevo nodo HUE Controller para los flujos nuevos.",
9
16
  "migration_button": "Convertir nodos HUE legacy",
10
17
  "migration_button_title": "Convertir todos los nodos HUE legacy del editor",
@@ -3,6 +3,8 @@
3
3
 
4
4
  Este nodo controla desde KNX un endpoint Matter ya emparejado. Selecciona el dispositivo Matter y el editor detecta sus capacidades, mostrando solo las asignaciones KNX coherentes con ese endpoint.
5
5
 
6
+ Al hacer clic o enfocar el campo del dispositivo Matter siempre se abre la lista completa de endpoints emparejados, aunque ya haya un dispositivo seleccionado. Al escribir, la lista sigue filtrándose.
7
+
6
8
  Sustituye a los nodos Matter separados no publicados y conserva toda la UI de luz cuando el endpoint seleccionado es una luz.
7
9
 
8
10
  ## Configuración
@@ -11,7 +11,7 @@
11
11
  |-|-|
12
12
  | Porte | Sélectionnez la passerelle KNX à utiliser |
13
13
  | Liste déroulante de type GA | Le type d'adresse de groupe. **3 niveaux** est la valeur par défaut, où vous pouvez saisir l'adresse du groupe de niveau _3 ou le _group name_ (si vous avez téléchargé le fichier ETS), ou **global**, pour lire le GA au démarrage à partir d'une variable globale, ou **flux** qui font la même chose que le _Global_, mais au niveau du flux. Sélectionnez **$Env variable** pour lire l'adresse de groupe à partir d'une variable d'environnement. Sélectionnez **Mode universel (écoutez toutes les adresses de groupe)** pour réagir à toutes les adresses de groupe. |
14
- | Groupe addr. | L'adresse du groupe KNX que vous souhaitez contrôler. Si vous avez importé le fichier d'adresses du groupe ETS, vous pouvez simplement commencer à saisir le nom de votre appareil. Vous pouvez le laisser vide si vous souhaitez le définir avec le message d'entrée _msg.setConfig_. |
14
+ | Groupe addr. | L'adresse du groupe KNX que vous souhaitez contrôler. Si vous avez importé le fichier d'adresses de groupe ETS, cliquer sur ce champ ou lui donner le focus ouvre la liste complète, même lorsqu'une GA est déjà sélectionnée ; la saisie filtre les entrées. Vous pouvez le laisser vide si vous souhaitez le définir avec le message d'entrée _msg.setConfig_. |
15
15
  | Point de données | Le point de données appartenant à votre nœud. |
16
16
 
17
17
  <br/>
@@ -1,8 +1,9 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueController">
2
2
  <p><a href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E" target="_blank" rel="noopener noreferrer"><strong>KNX-Ultimate video tutorials (YouTube playlist)</strong></a> Avant de commencer, <a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">regardez la vidéo explicative sur YouTube</a>.</p>
3
3
  <p><strong>HUE Controller</strong> est le nœud unifié pour les ressources Hue API v2.</p>
4
- <p>Sélectionnez la fonction de l'appareil, puis configurez le même mappage KNX et le même comportement que dans le nœud Hue dédié éprouvé.</p>
5
- <p>Le sélecteur de fonction utilise un tiers de la largeur disponible afin de conserver un éditeur compact.</p>
4
+ <p>Sélectionnez un appareil ou une ressource Hue. HUE Controller détecte automatiquement son type et ouvre l'éditeur correspondant pour le mappage KNX, le comportement et les ports du nœud.</p>
5
+ <p>Le sélecteur d'appareil, l'actualisation et le type détecté partagent la même ligne compacte, comme dans Matter Controller.</p>
6
+ <p>Un clic ou le focus sur le champ de l'appareil ouvre toujours la liste complète des ressources Hue, même si un appareil est déjà sélectionné. La saisie continue de filtrer la liste.</p>
6
7
  <p>Les lignes de mappage KNX conservent GA, DPT et Nom sur une seule ligne. Les champs DPT et Nom utilisent des largeurs compactes ; le champ Nom peut encore se réduire dans un éditeur étroit sans modifier le texte enregistré. Les valeurs DPT enregistrées sont conservées pendant le chargement asynchrone des options du sélecteur.</p>
7
8
  <p>Fonctions prises en charge : lampes et groupes, prises, boutons, Tap dial, mouvement et mouvement de caméra, contacts, niveau de lumière, température, humidité, scènes, batterie, connectivité Zigbee et mise à jour logicielle.</p>
8
9
  <p>Lorsqu'aucune <strong>Passerelle KNX</strong> n'est sélectionnée (y compris <code>none</code> et <em>Ajouter...</em>), les champs de mappage KNX sont masqués ; la sélection de la ressource Hue et les options réservées au flow restent disponibles.</p>
@@ -5,6 +5,13 @@
5
5
  },
6
6
  "knxUltimateHueController": {
7
7
  "device_function": "Fonction de l'appareil",
8
+ "hue_section": "Philips Hue",
9
+ "hue_device": "Appareil Hue",
10
+ "hue_device_placeholder": "Rechercher un appareil ou une ressource Hue",
11
+ "device_type": "Type d'appareil",
12
+ "refresh_devices": "Actualiser les appareils Hue",
13
+ "locate_device": "Localiser la lampe Hue sélectionnée",
14
+ "no_devices": "Aucune ressource Hue trouvée. Vérifiez le Hue Bridge et actualisez la liste.",
8
15
  "legacy_node_notice": "Ce nœud HUE legacy est obsolète. Utilisez le nouveau nœud HUE Controller pour les nouveaux flows.",
9
16
  "migration_button": "Convertir les nœuds HUE legacy",
10
17
  "migration_button_title": "Convertir tous les nœuds HUE legacy présents dans l'éditeur",
@@ -3,6 +3,8 @@
3
3
 
4
4
  Ce nœud contrôle depuis KNX un endpoint Matter déjà appairé. Sélectionnez l'appareil Matter et l'éditeur détecte ses capacités, puis affiche uniquement les mappings KNX adaptés à cet endpoint.
5
5
 
6
+ Un clic ou le focus sur le champ de l'appareil Matter ouvre toujours la liste complète des endpoints appairés, même si un appareil est déjà sélectionné. La saisie continue de filtrer la liste.
7
+
6
8
  Il remplace les nœuds Matter séparés non publiés et conserve toute l'UI lumière lorsque l'endpoint sélectionné est une lumière.
7
9
 
8
10
  ## Configuration
@@ -11,7 +11,7 @@ Questo nodo controlla un Indirizzo di Gruppo KNX; è il nodo più utilizzato.
11
11
  |--|--|
12
12
  | Gateway | Seleziona il gateway KNX da utilizzare |
13
13
  | Elenco a discesa tipo GA | Tipo di indirizzo di gruppo. **3-Livelli** è il default, dove puoi digitare il GA a 3 livelli o il nome GA (se hai caricato il file ETS); **Global** legge il GA da una variabile globale all'avvio; **Flow** fa lo stesso a livello di flow. Seleziona **$Env variable** per leggere il GA da una variabile d'ambiente. Seleziona **Modalità universale (ascolta tutti gli Indirizzi di Gruppo)** per reagire a TUTTI i GA. |
14
- | Ind. Gruppo | L'indirizzo di gruppo KNX da controllare. Se hai importato il file ETS, puoi iniziare a digitare il nome del dispositivo. Puoi lasciarlo vuoto se intendi impostarlo tramite messaggio di ingresso `msg.setConfig`. |
14
+ | Ind. Gruppo | L'indirizzo di gruppo KNX da controllare. Se hai importato il file ETS, facendo clic o portando il focus sul campo si apre l'elenco completo, anche quando un GA è già selezionato; digitando puoi filtrare le voci. Puoi lasciarlo vuoto se intendi impostarlo tramite messaggio di ingresso `msg.setConfig`. |
15
15
  | Datapoint | Il Datapoint associato al nodo. |
16
16
 
17
17
  <br/>
@@ -1,8 +1,9 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueController">
2
2
  <p><a href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E" target="_blank" rel="noopener noreferrer"><strong>KNX-Ultimate video tutorials (YouTube playlist)</strong></a> Prima di iniziare, <a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">guarda il video esplicativo su YouTube</a>.</p>
3
3
  <p><strong>HUE Controller</strong> è il nodo unificato per le risorse Hue API v2.</p>
4
- <p>Seleziona la funzione del dispositivo, quindi configura la stessa mappatura KNX e lo stesso comportamento disponibili nel nodo Hue dedicato già consolidato.</p>
5
- <p>Il selettore della funzione dispositivo usa un terzo della larghezza disponibile, mantenendo l'editor più compatto.</p>
4
+ <p>Seleziona un dispositivo o una risorsa Hue. HUE Controller ne rileva automaticamente il tipo e apre l'editor corretto per mappature KNX, comportamento e pin del nodo.</p>
5
+ <p>Selettore del dispositivo, pulsante di aggiornamento e tipo rilevato condividono la stessa riga compatta, come nel workflow del Matter Controller.</p>
6
+ <p>Un click o il focus sul campo dispositivo apre sempre l'elenco completo delle risorse Hue, anche quando un dispositivo è già selezionato. Digitando, l'elenco continua a essere filtrato.</p>
6
7
  <p>Le righe di mappatura KNX mantengono GA, DPT e Nome sulla stessa linea. I campi DPT e Nome usano larghezze compatte e Nome può restringersi ulteriormente negli editor più stretti senza modificare il testo memorizzato. I valori DPT salvati vengono conservati mentre le opzioni del selettore si caricano in modo asincrono.</p>
7
8
  <p>Funzioni supportate: luci e gruppi di luci, prese, pulsanti, Tap dial, movimento e movimento telecamera, contatti, livello luce, temperatura, umidità, scene, batteria, connettività Zigbee e aggiornamento software.</p>
8
9
  <p>Quando non è selezionato alcun <strong>Gateway KNX</strong> (inclusi <code>none</code> e <em>Aggiungi nuovo...</em>), i campi di mappatura KNX vengono nascosti, mentre la selezione della risorsa Hue e le opzioni per il solo flow restano disponibili.</p>
@@ -5,6 +5,13 @@
5
5
  },
6
6
  "knxUltimateHueController": {
7
7
  "device_function": "Funzione dispositivo",
8
+ "hue_section": "Philips Hue",
9
+ "hue_device": "Dispositivo Hue",
10
+ "hue_device_placeholder": "Cerca il dispositivo o la risorsa Hue",
11
+ "device_type": "Tipo dispositivo",
12
+ "refresh_devices": "Aggiorna dispositivi Hue",
13
+ "locate_device": "Localizza la luce Hue selezionata",
14
+ "no_devices": "Nessuna risorsa Hue trovata. Controlla Hue Bridge e premi aggiorna.",
8
15
  "legacy_node_notice": "Questo nodo HUE legacy è deprecato. Per i nuovi flow usa il nuovo nodo HUE Controller.",
9
16
  "migration_button": "Converti nodi HUE legacy",
10
17
  "migration_button_title": "Converti tutti i nodi HUE legacy presenti nell'editor",
@@ -3,6 +3,8 @@
3
3
 
4
4
  Questo nodo controlla da KNX un endpoint Matter già abbinato. Seleziona il dispositivo Matter e l'editor rileva le sue capability, mostrando solo le mappature KNX coerenti con quell'endpoint.
5
5
 
6
+ Un click o il focus sul campo del dispositivo Matter apre sempre l'elenco completo degli endpoint abbinati, anche quando un dispositivo è già selezionato. Digitando, l'elenco continua a essere filtrato.
7
+
6
8
  Sostituisce i nodi Matter separati non pubblicati e mantiene tutta la UI luce quando l'endpoint selezionato è una luce.
7
9
 
8
10
  ## Configurazione
@@ -11,7 +11,7 @@
11
11
  |--|--|
12
12
  | Gateway | 选择要使用的 KNX 网关 |
13
13
  | GA 类型 | 组地址类型。默认 **3-层级**(可输入三层组地址或名称,若已导入 ETS);**Global** 启动时从全局变量读取;**Flow** 在流程作用域读取;**$Env variable** 从环境变量读取;**通用模式(监听所有组地址)** 对所有 GA 做出响应。|
14
- | Group Addr. | 需要控制的组地址。若已导入 ETS,可直接输入设备名进行匹配。也可留空,之后通过输入消息 `msg.setConfig` 设置。|
14
+ | Group Addr. | 需要控制的组地址。若已导入 ETS,单击此字段或使其获得焦点会打开完整列表,即使已选择了 GA;输入文字可筛选条目。也可留空,之后通过输入消息 `msg.setConfig` 设置。|
15
15
  | Datapoint | 与节点关联的 Datapoint。|
16
16
 
17
17
  <br/>
@@ -1,8 +1,9 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueController">
2
2
  <p><a href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E" target="_blank" rel="noopener noreferrer"><strong>KNX-Ultimate 视频教程(YouTube 播放列表)</strong></a> 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  <p><strong>HUE Controller</strong> 是面向 Hue API v2 资源的统一节点。</p>
4
- <p>选择设备功能,然后使用原有专用 Hue 节点中已经稳定的 KNX 映射和行为设置。</p>
5
- <p>设备功能选择器使用可用宽度的三分之一,使编辑器保持紧凑。</p>
4
+ <p>选择 Hue 设备或资源后,HUE Controller 会自动检测设备类型,并打开对应的 KNX 映射、行为和节点端口编辑器。</p>
5
+ <p>设备选择、刷新操作和检测到的类型显示在同一紧凑行中,与 Matter Controller 的工作流程一致。</p>
6
+ <p>单击设备字段或使其获得焦点时,即使已经选择了设备,也会始终打开完整的 Hue 资源列表;输入文字仍可筛选列表。</p>
6
7
  <p>KNX 映射行会将 GA、DPT 和名称保持在同一行。DPT 与名称字段采用紧凑宽度;在较窄的编辑器中,名称字段还可继续收缩,而不会更改已保存的文本。选择器选项异步加载时,已保存的 DPT 值会被保留。</p>
7
8
  <p>支持灯和灯组、插座、按钮、Tap dial、运动和摄像机运动、接触、光照度、温度、湿度、场景、电池、Zigbee 连接和设备软件更新。</p>
8
9
  <p>未选择<strong>KNX 网关</strong>时(包括 <code>none</code> 和“新增...”),KNX 映射字段会隐藏,而 Hue 资源选择和仅用于流程的选项仍然可用。</p>
@@ -5,6 +5,13 @@
5
5
  },
6
6
  "knxUltimateHueController": {
7
7
  "device_function": "设备功能",
8
+ "hue_section": "Philips Hue",
9
+ "hue_device": "Hue 设备",
10
+ "hue_device_placeholder": "搜索 Hue 设备或资源",
11
+ "device_type": "设备类型",
12
+ "refresh_devices": "刷新 Hue 设备",
13
+ "locate_device": "定位所选 Hue 灯",
14
+ "no_devices": "未找到 Hue 资源。请检查 Hue Bridge 并刷新列表。",
8
15
  "legacy_node_notice": "此旧版 HUE 节点已弃用。新流程请使用新的 HUE Controller 节点。",
9
16
  "migration_button": "转换旧版 HUE 节点",
10
17
  "migration_button_title": "转换编辑器中的所有旧版 HUE 节点",
@@ -3,6 +3,8 @@
3
3
 
4
4
  此节点用于从 KNX 控制已经配对的 Matter endpoint。选择 Matter 设备后,编辑器会检测它的能力,并只显示与该 endpoint 匹配的 KNX 映射。
5
5
 
6
+ 单击 Matter 设备字段或使其获得焦点时,即使已经选择了设备,也会始终打开完整的已配对 endpoint 列表;输入文字仍可筛选列表。
7
+
6
8
  它替代未发布的多个专用 Matter 控制节点;当选择的 endpoint 是灯时,仍保留完整的灯光 UI。
7
9
 
8
10
  ## 配置
@@ -0,0 +1,71 @@
1
+ 'use strict'
2
+
3
+ // Resource queries are intentionally ordered with Plug before Light. Hue plugs
4
+ // often expose an on/off service whose API v2 type is `light`; preferring the
5
+ // plug profile prevents the same resource from appearing twice or being
6
+ // classified as a lamp in the unified Controller picker.
7
+ const HUE_CONTROLLER_RESOURCE_TYPES = Object.freeze([
8
+ 'plug',
9
+ 'light',
10
+ 'button',
11
+ 'relative_rotary',
12
+ 'motion',
13
+ 'area_motion',
14
+ 'camera_motion',
15
+ 'contact',
16
+ 'light_level',
17
+ 'temperature',
18
+ 'humidity',
19
+ 'scene',
20
+ 'device_power',
21
+ 'zigbee_connectivity',
22
+ 'device_software_update'
23
+ ])
24
+
25
+ const normalizeHueDeviceValue = (controllerType, item) => {
26
+ const id = String(item?.id || item?.rid || '').trim()
27
+ if (id === '') return ''
28
+ const resourceType = String(item?.type || item?.deviceObject?.type || '').trim().toLowerCase()
29
+ if (controllerType === 'light') {
30
+ return `${id}#${resourceType === 'grouped_light' ? 'grouped_light' : 'light'}`
31
+ }
32
+ if (controllerType === 'plug') {
33
+ return `${id}#${resourceType || 'plug'}`
34
+ }
35
+ return id
36
+ }
37
+
38
+ const buildHueControllerResourceCatalog = (resourcesByType = {}) => {
39
+ const catalogById = new Map()
40
+
41
+ HUE_CONTROLLER_RESOURCE_TYPES.forEach((controllerType) => {
42
+ const resources = Array.isArray(resourcesByType[controllerType])
43
+ ? resourcesByType[controllerType]
44
+ : []
45
+ resources.forEach((item) => {
46
+ const id = String(item?.id || item?.rid || '').trim()
47
+ if (id === '' || id === 'error' || catalogById.has(id)) return
48
+ const hueDevice = normalizeHueDeviceValue(controllerType, item)
49
+ if (hueDevice === '') return
50
+ catalogById.set(id, {
51
+ ...item,
52
+ id,
53
+ hueDevice,
54
+ controllerType
55
+ })
56
+ })
57
+ })
58
+
59
+ return Array.from(catalogById.values()).sort((left, right) => {
60
+ return String(left.name || left.id).localeCompare(String(right.name || right.id), undefined, {
61
+ numeric: true,
62
+ sensitivity: 'base'
63
+ })
64
+ })
65
+ }
66
+
67
+ module.exports = {
68
+ HUE_CONTROLLER_RESOURCE_TYPES,
69
+ buildHueControllerResourceCatalog,
70
+ normalizeHueDeviceValue
71
+ }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=20.18.1"
5
5
  },
6
- "version": "6.2.3-beta.4",
6
+ "version": "6.3.0",
7
7
  "description": "KNX Ultimate is the most advanced KNX integration for Node-RED, providing secure KNX/IP communication, routing, ETS project import, Philips Hue, Matter Controller and Matter Bridge (control matter device via KNX and expose KNX GA via Matter), MQTT, diagnostics with AI, virtual devices, and powerful automation nodes. Build professional, reliable, and scalable smart home and building automation projects with minimal effort.",
8
8
  "files": [
9
9
  "nodes/",
@@ -1573,6 +1573,13 @@
1573
1573
  RED.sidebar.show("help");
1574
1574
  } catch (error) { }
1575
1575
  onEditPrepare();
1576
+ // HUE Controller owns a device-first picker covering every supported
1577
+ // Hue API v2 resource. onEditPrepare installs the mature Light-only
1578
+ // autocomplete, so let the wrapper restore its unified source after
1579
+ // this asynchronous readiness callback completes.
1580
+ if (typeof node.__configureHueControllerDeviceControl === 'function') {
1581
+ node.__configureHueControllerDeviceControl();
1582
+ }
1576
1583
  }
1577
1584
 
1578
1585