node-red-contrib-knx-ultimate 4.1.28 → 4.1.30

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 +21 -0
  2. package/examples/KNX DateTime - Set Date and Time to Bus.json +89 -0
  3. package/nodes/knxUltimate-config.js +41 -16
  4. package/nodes/knxUltimateAI.html +18 -5
  5. package/nodes/knxUltimateAI.js +1315 -22
  6. package/nodes/knxUltimateDateTime.html +559 -0
  7. package/nodes/knxUltimateDateTime.js +306 -0
  8. package/nodes/knxUltimateHueLight.js +26 -6
  9. package/nodes/locales/de/knxUltimateAI.html +66 -27
  10. package/nodes/locales/de/knxUltimateDateTime.html +36 -0
  11. package/nodes/locales/de/knxUltimateDateTime.json +30 -0
  12. package/nodes/locales/en/knxUltimateAI.html +66 -27
  13. package/nodes/locales/en/knxUltimateDateTime.html +45 -0
  14. package/nodes/locales/en/knxUltimateDateTime.json +30 -0
  15. package/nodes/locales/es/knxUltimateAI.html +66 -27
  16. package/nodes/locales/es/knxUltimateDateTime.html +36 -0
  17. package/nodes/locales/es/knxUltimateDateTime.json +30 -0
  18. package/nodes/locales/fr/knxUltimateAI.html +61 -22
  19. package/nodes/locales/fr/knxUltimateDateTime.html +36 -0
  20. package/nodes/locales/fr/knxUltimateDateTime.json +30 -0
  21. package/nodes/locales/it/knxUltimateAI.html +59 -22
  22. package/nodes/locales/it/knxUltimateDateTime.html +45 -0
  23. package/nodes/locales/it/knxUltimateDateTime.json +30 -0
  24. package/nodes/locales/zh-CN/knxUltimateAI.html +59 -20
  25. package/nodes/locales/zh-CN/knxUltimateDateTime.html +36 -0
  26. package/nodes/locales/zh-CN/knxUltimateDateTime.json +30 -0
  27. package/nodes/plugins/knxUltimateAI-web-page.html +3571 -0
  28. package/nodes/plugins/themes/knxUltimateAI-theme-green.css +44 -0
  29. package/nodes/plugins/themes/knxUltimateAI-theme-lavender.css +44 -0
  30. package/nodes/plugins/themes/knxUltimateAI-theme-mix.css +41 -0
  31. package/package.json +3 -3
  32. package/nodes/plugins/knxUltimateAI-sidebar-plugin.html +0 -561
package/CHANGELOG.md CHANGED
@@ -6,6 +6,27 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 4.1.30** - March 2026<br/>
10
+
11
+ - NEW: **KNX AI** now uses a full **Web Dashboard** as the main UI, opened from the node editor via **Open Web Page** button.<br/>
12
+ - CHANGE: removed the legacy `knxUltimateAI-sidebar-plugin` file and aligned KNX AI admin routes to the Web Dashboard workflow.<br/>
13
+ - IMPROVE: **KNX AI Flow Map** UX/readability: stable layout reset on page open, clearer legend, active/idle direction rendering and dashboard visual refinements.<br/>
14
+ - UI: flattened the KNX AI Web Dashboard style (removed 3D/shadow-heavy look) for a cleaner production UI.<br/>
15
+ - Docs/help/wiki: refreshed **KNX AI** documentation in all supported languages (EN/IT/DE/FR/ES/zh-CN), updated `KNX-AI-Sidebar` pages to Web Dashboard compatibility notes, and updated docs navigation section naming/ordering.<br/>
16
+ - FIX: **KNX AI Ask chat**: SVG answers are now rendered correctly even with multiple SVG blocks, fenced `svg/xml/html` code blocks, or entity-escaped SVG markup.<br/>
17
+ - FIX: **KNX Hue Light**: KNX read response for brightness now returns `0` when the light is OFF and brightness status is configured to follow HUE on/off.<br/>
18
+ - FIX: **KNX Hue Light**: ignore dimming-only cached brightness updates from Hue while light is OFF, preventing wrong KNX brightness status jumps.<br/>
19
+ - IMPROVE: **KNX Hue Light** grouped light handling: when an ON event arrives without dimming payload, brightness is fetched from bridge snapshot before publishing KNX status.<br/>
20
+ - TEST: extended unit coverage for the new Hue Light brightness/read/grouped-light snapshot behaviors.<br/>
21
+
22
+ **Version 4.1.29** - March 2026<br/>
23
+
24
+ - NEW: **KNX DateTime** node: set date/time on the KNX bus via **DPT 19.001** (DateTime) and optionally **DPT 11.001** (Date) / **DPT 10.001** (Time).<br/>
25
+ - NEW: **KNX DateTime**: send on startup (with delay), periodic send, editor send-now button, and input-triggered send.<br/>
26
+ - UI: **KNX DateTime**: when adding a new node, it can auto-select the first KNX Gateway with an ETS import and pre-fill coherent group addresses.<br/>
27
+ - UI: **KNX DateTime**: auto-fill is also triggered when changing the selected KNX Gateway (it won’t override manual values).<br/>
28
+ - i18n/help/wiki: added **KNX DateTime** help and docs pages in all supported languages, and added the node to the docs navigation.<br/>
29
+
9
30
  **Version 4.1.28** - February 2026<br/>
10
31
 
11
32
  - NEW: **KNX Load Control**: added **Mode** selector to disable the internal logic and use only `msg.shedding` commands (`shed`/`unshed`).<br/>
@@ -0,0 +1,89 @@
1
+ [{
2
+ "id": "dt_cfg_1",
3
+ "type": "knxUltimate-config",
4
+ "z": "",
5
+ "host": "224.0.23.12",
6
+ "port": "3671",
7
+ "physAddr": "15.15.202",
8
+ "suppressACKRequest": false,
9
+ "csv": "",
10
+ "KNXEthInterface": "Auto",
11
+ "KNXEthInterfaceManuallyInput": "",
12
+ "autoReconnect": "yes"
13
+ },
14
+ {
15
+ "id": "dt_inj_now",
16
+ "type": "inject",
17
+ "z": "flow_dt",
18
+ "name": "Send NOW",
19
+ "props": [
20
+ { "p": "payload" }
21
+ ],
22
+ "repeat": "",
23
+ "crontab": "",
24
+ "once": false,
25
+ "onceDelay": 0.1,
26
+ "topic": "",
27
+ "payloadType": "date",
28
+ "x": 150,
29
+ "y": 160,
30
+ "wires": [["dt_node"]]
31
+ },
32
+ {
33
+ "id": "dt_inj_custom",
34
+ "type": "inject",
35
+ "z": "flow_dt",
36
+ "name": "Send fixed ISO date",
37
+ "props": [
38
+ { "p": "payload", "v": "2026-03-02T10:00:00+01:00", "vt": "str" }
39
+ ],
40
+ "repeat": "",
41
+ "crontab": "",
42
+ "once": false,
43
+ "onceDelay": 0.1,
44
+ "topic": "",
45
+ "x": 170,
46
+ "y": 220,
47
+ "wires": [["dt_node"]]
48
+ },
49
+ {
50
+ "id": "dt_node",
51
+ "type": "knxUltimateDateTime",
52
+ "z": "flow_dt",
53
+ "server": "dt_cfg_1",
54
+ "name": "KNX Clock",
55
+ "outputtopic": "events/knx/datetime",
56
+ "gaDateTime": "1/7/1",
57
+ "nameDateTime": "Bus DateTime",
58
+ "dptDateTime": "19.001",
59
+ "gaDate": "",
60
+ "nameDate": "",
61
+ "dptDate": "11.001",
62
+ "gaTime": "",
63
+ "nameTime": "",
64
+ "dptTime": "10.001",
65
+ "sendOnDeploy": true,
66
+ "sendOnDeployDelay": 2,
67
+ "periodicSend": false,
68
+ "periodicSendInterval": 60,
69
+ "periodicSendUnit": "m",
70
+ "x": 420,
71
+ "y": 190,
72
+ "wires": [["dt_dbg"]]
73
+ },
74
+ {
75
+ "id": "dt_dbg",
76
+ "type": "debug",
77
+ "z": "flow_dt",
78
+ "name": "DateTime out",
79
+ "active": true,
80
+ "tosidebar": true,
81
+ "console": false,
82
+ "tostatus": true,
83
+ "complete": "true",
84
+ "targetType": "full",
85
+ "x": 640,
86
+ "y": 190,
87
+ "wires": []
88
+ }]
89
+
@@ -1412,7 +1412,9 @@ module.exports = (RED) => {
1412
1412
  destination: _dest,
1413
1413
  apdu: { data: _apduData, bitlength: _apduBitLength, hex: _apduHex },
1414
1414
  cemi: { hex: _cemiETS },
1415
- echoed: _echoed
1415
+ echoed: _echoed,
1416
+ repeated: isRepeated,
1417
+ repeat: isRepeated
1416
1418
  },
1417
1419
  knxMultiRouting: {
1418
1420
  gateway: { id: node.id, name: node.name || '', physAddr: node.physAddr || '' },
@@ -1429,7 +1431,9 @@ module.exports = (RED) => {
1429
1431
  knx: {
1430
1432
  event: _evt,
1431
1433
  destination: _dest,
1432
- rawValue: _rawValue
1434
+ rawValue: _rawValue,
1435
+ repeated: isRepeated,
1436
+ repeat: isRepeated
1433
1437
  }
1434
1438
  }
1435
1439
  _input.handleSend(msg)
@@ -1449,7 +1453,8 @@ module.exports = (RED) => {
1449
1453
  _devicename: _input.name ? _input.name : '',
1450
1454
  _outputtopic: _input.outputtopic,
1451
1455
  _oNode: null,
1452
- _echoed
1456
+ _echoed,
1457
+ _repeated: isRepeated
1453
1458
  })
1454
1459
  _input.RecallScene(msgRecall.payload, false)
1455
1460
  } catch (error) { }
@@ -1465,7 +1470,8 @@ module.exports = (RED) => {
1465
1470
  _devicename: _input.name || '',
1466
1471
  _outputtopic: _dest,
1467
1472
  _oNode: null,
1468
- _echoed
1473
+ _echoed,
1474
+ _repeated: isRepeated
1469
1475
  })
1470
1476
  _input.SaveScene(msgSave.payload, false)
1471
1477
  } catch (error) { }
@@ -1492,7 +1498,8 @@ module.exports = (RED) => {
1492
1498
  _devicename: oDevice.name || '',
1493
1499
  _outputtopic: oDevice.outputtopic,
1494
1500
  _oNode: null,
1495
- _echoed
1501
+ _echoed,
1502
+ _repeated: isRepeated
1496
1503
  })
1497
1504
  oDevice.currentPayload = msg.payload
1498
1505
  _input.setNodeStatus({
@@ -1532,7 +1539,8 @@ module.exports = (RED) => {
1532
1539
  _Rawvalue: _rawValue,
1533
1540
  _outputtopic: _dest,
1534
1541
  _oNode: _input,
1535
- _echoed
1542
+ _echoed,
1543
+ _repeated: isRepeated
1536
1544
  })
1537
1545
  _input.setNodeStatus({
1538
1546
  fill: 'green',
@@ -1558,7 +1566,8 @@ module.exports = (RED) => {
1558
1566
  _devicename: _input.name ? _input.name : '',
1559
1567
  _outputtopic: _input.outputtopic,
1560
1568
  _oNode: _input,
1561
- _echoed
1569
+ _echoed,
1570
+ _repeated: isRepeated
1562
1571
  })
1563
1572
  // Check RBE INPUT from KNX Bus, to avoid send the payload to the flow, if it's equal to the current payload
1564
1573
  if (!checkRBEInputFromKNXBusAllowSend(_input, msg.payload)) {
@@ -1605,7 +1614,9 @@ module.exports = (RED) => {
1605
1614
  destination: _dest,
1606
1615
  apdu: { data: _apduData, bitlength: _apduBitLength, hex: _apduHex },
1607
1616
  cemi: { hex: _cemiETS },
1608
- echoed: _echoed
1617
+ echoed: _echoed,
1618
+ repeated: isRepeated,
1619
+ repeat: isRepeated
1609
1620
  },
1610
1621
  knxMultiRouting: {
1611
1622
  gateway: { id: node.id, name: node.name || '', physAddr: node.physAddr || '' },
@@ -1634,7 +1645,8 @@ module.exports = (RED) => {
1634
1645
  _Rawvalue: _rawValue,
1635
1646
  _outputtopic: _dest,
1636
1647
  _oNode: _input,
1637
- _echoed
1648
+ _echoed,
1649
+ _repeated: isRepeated
1638
1650
  })
1639
1651
  _input.setNodeStatus({
1640
1652
  fill: 'blue',
@@ -1661,7 +1673,8 @@ module.exports = (RED) => {
1661
1673
  _devicename: _input.name ? _input.name : '',
1662
1674
  _outputtopic: _input.outputtopic,
1663
1675
  _oNode: _input,
1664
- _echoed
1676
+ _echoed,
1677
+ _repeated: isRepeated
1665
1678
  })
1666
1679
  // Check RBE INPUT from KNX Bus, to avoid send the payload to the flow, if it's equal to the current payload
1667
1680
  if (!checkRBEInputFromKNXBusAllowSend(_input, msg.payload)) {
@@ -1705,7 +1718,9 @@ module.exports = (RED) => {
1705
1718
  destination: _dest,
1706
1719
  apdu: { data: null, bitlength: 0, hex: '' },
1707
1720
  cemi: { hex: _cemiETS },
1708
- echoed: _echoed
1721
+ echoed: _echoed,
1722
+ repeated: isRepeated,
1723
+ repeat: isRepeated
1709
1724
  },
1710
1725
  knxMultiRouting: {
1711
1726
  gateway: { id: node.id, name: node.name || '', physAddr: node.physAddr || '' },
@@ -1736,7 +1751,8 @@ module.exports = (RED) => {
1736
1751
  _Rawvalue: null,
1737
1752
  _outputtopic: _dest,
1738
1753
  _oNode: _input,
1739
- _echoed
1754
+ _echoed,
1755
+ _repeated: isRepeated
1740
1756
  })
1741
1757
  _input.setNodeStatus({
1742
1758
  fill: 'grey',
@@ -1763,7 +1779,8 @@ module.exports = (RED) => {
1763
1779
  _devicename: _input.name || '',
1764
1780
  _outputtopic: _input.outputtopic,
1765
1781
  _oNode: _input,
1766
- _echoed
1782
+ _echoed,
1783
+ _repeated: isRepeated
1767
1784
  })
1768
1785
  msg.previouspayload = typeof _input.currentPayload !== 'undefined' ? _input.currentPayload : '' // 24/01/2020 Reset previous payload
1769
1786
  // 24/09/2019 Autorespond to BUS
@@ -2002,7 +2019,7 @@ module.exports = (RED) => {
2002
2019
  }
2003
2020
  }
2004
2021
 
2005
- function buildInputMessage ({ _srcGA, _destGA, _event, _Rawvalue, _inputDpt, _devicename, _outputtopic, _oNode, _echoed = false }) {
2022
+ function buildInputMessage ({ _srcGA, _destGA, _event, _Rawvalue, _inputDpt, _devicename, _outputtopic, _oNode, _echoed = false, _repeated = false }) {
2006
2023
  let sPayloadmeasureunit = 'unknown'
2007
2024
  let sDptdesc = 'unknown'
2008
2025
  let sPayloadsubtypevalue = 'unknown'
@@ -2054,13 +2071,17 @@ module.exports = (RED) => {
2054
2071
  devicename: typeof _devicename !== 'undefined' ? _devicename : '',
2055
2072
  payloadmeasureunit: '',
2056
2073
  payloadsubtypevalue: '',
2074
+ repeated: _repeated === true,
2075
+ repeat: _repeated === true,
2057
2076
  knx: {
2058
2077
  event: _event,
2059
2078
  dpt: 'unknown',
2060
2079
  dptdesc: '',
2061
2080
  source: _srcGA,
2062
2081
  destination: _destGA,
2063
- rawValue: _Rawvalue
2082
+ rawValue: _Rawvalue,
2083
+ repeated: _repeated === true,
2084
+ repeat: _repeated === true
2064
2085
  }
2065
2086
  }
2066
2087
 
@@ -2197,13 +2218,17 @@ module.exports = (RED) => {
2197
2218
  payloadsubtypevalue: sPayloadsubtypevalue,
2198
2219
  gainfo,
2199
2220
  echoed: _echoed,
2221
+ repeated: _repeated === true,
2222
+ repeat: _repeated === true,
2200
2223
  knx: {
2201
2224
  event: _event,
2202
2225
  dpt: sInputDpt,
2203
2226
  dptdesc: sDptdesc,
2204
2227
  source: _srcGA,
2205
2228
  destination: _destGA,
2206
- rawValue: _Rawvalue
2229
+ rawValue: _Rawvalue,
2230
+ repeated: _repeated === true,
2231
+ repeat: _repeated === true
2207
2232
  }
2208
2233
  }
2209
2234
  // 11/11/2021 jsValue is null, as well as _Rawvalue, in case of READ REQUEST message.
@@ -13,11 +13,11 @@
13
13
  notifyresponse: { value: true },
14
14
  notifywrite: { value: true },
15
15
 
16
- analysisWindowSec: { value: 60, required: true, validate: RED.validators.number() },
17
- historyWindowSec: { value: 300, required: true, validate: RED.validators.number() },
16
+ analysisWindowSec: { value: 120, required: true, validate: RED.validators.number() },
17
+ historyWindowSec: { value: 600, required: true, validate: RED.validators.number() },
18
18
  emitIntervalSec: { value: 0, required: true, validate: RED.validators.number() },
19
19
  topN: { value: 10, required: true, validate: RED.validators.number() },
20
- maxEvents: { value: 5000, required: true, validate: RED.validators.number() },
20
+ maxEvents: { value: 50000, required: true, validate: RED.validators.number() },
21
21
 
22
22
  rateWindowSec: { value: 10, required: true, validate: RED.validators.number() },
23
23
  maxTelegramPerSecOverall: { value: 0, required: true, validate: RED.validators.number() },
@@ -33,7 +33,7 @@
33
33
  llmEnabled: { value: false },
34
34
  llmProvider: { value: "openai_compat" },
35
35
  llmBaseUrl: { value: "https://api.openai.com/v1/chat/completions" },
36
- llmModel: { value: "gpt-4o-mini" },
36
+ llmModel: { value: "gpt-5.4" },
37
37
  llmSystemPrompt: { value: "" },
38
38
  llmTemperature: { value: 0.2, required: false, validate: RED.validators.number() },
39
39
  llmMaxTokens: { value: 600, required: false, validate: RED.validators.number() },
@@ -43,7 +43,7 @@
43
43
  llmIncludeFlowContext: { value: true },
44
44
  llmMaxFlowNodesInPrompt: { value: 80, required: false, validate: RED.validators.number() },
45
45
  llmIncludeDocsSnippets: { value: true },
46
- llmDocsLanguage: { value: "it" },
46
+ llmDocsLanguage: { value: "en" },
47
47
  llmDocsMaxSnippets: { value: 5, required: false, validate: RED.validators.number() },
48
48
  llmDocsMaxChars: { value: 3000, required: false, validate: RED.validators.number() }
49
49
  },
@@ -100,6 +100,13 @@
100
100
 
101
101
  const nodeId = this.id;
102
102
 
103
+ $("#knx-ai-open-web-page").on("click", function (evt) {
104
+ evt.preventDefault();
105
+ const target = "knxUltimateAI/sidebar/page" + (nodeId ? ("?nodeId=" + encodeURIComponent(nodeId)) : "");
106
+ const wnd = window.open(target, "_blank", "noopener,noreferrer");
107
+ try { if (wnd && typeof wnd.focus === "function") wnd.focus(); } catch (e) { }
108
+ });
109
+
103
110
  const setModelsStatus = (text, level) => {
104
111
  const $status = $("#knx-ai-models-status");
105
112
  if (!$status || $status.length === 0) return;
@@ -190,6 +197,12 @@
190
197
  <label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="knxUltimateAI.properties.topic"></span></label>
191
198
  <input type="text" id="node-input-topic" data-i18n="[placeholder]knxUltimateAI.properties.topic">
192
199
  </div>
200
+ <div class="form-row">
201
+ <label><i class="fa fa-external-link"></i> Web UI</label>
202
+ <button type="button" class="red-ui-button" id="knx-ai-open-web-page" style="background-color:#AD9EFF; border-color:#AD9EFF; color:#ffffff !important; -webkit-text-fill-color:#ffffff;">
203
+ <i class="fa fa-magic" style="color:#2b1f4d !important;"></i> <span style="color:#2b1f4d !important;">Open KNX AI Web</span>
204
+ </button>
205
+ </div>
193
206
 
194
207
  <div id="knx-ai-accordion">
195
208
  <h3><span data-i18n="knxUltimateAI.sections.capture"></span></h3>