node-red-contrib-knx-ultimate 4.3.24 → 5.0.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.
- package/CHANGELOG.md +5 -0
- package/nodes/commonFunctions.js +115 -0
- package/nodes/knxUltimate-config.html +60 -4
- package/nodes/knxUltimate-config.js +11 -0
- package/nodes/locales/de/knxUltimate-config.json +5 -1
- package/nodes/locales/en/knxUltimate-config.json +5 -1
- package/nodes/locales/es/knxUltimate-config.json +5 -1
- package/nodes/locales/fr/knxUltimate-config.json +5 -1
- package/nodes/locales/it/knxUltimate-config.json +5 -1
- package/nodes/locales/zh-CN/knxUltimate-config.json +5 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
**Version 5.0.0** - June 2026<br/>
|
|
10
|
+
|
|
11
|
+
- KNXUltimate engine: updated to **6.0.1** (see the engine [CHANGELOG](https://github.com/Supergiovane/knxultimate/blob/main/CHANGELOG.md)).<br/>
|
|
12
|
+
- KNX config node: new **"Reveal keyring passwords"** button in the **Utility** tab, enabled only when **KNX Secure** is selected. It shows, in clear text, all the keyring passwords (interfaces, backbone, group addresses and devices keys/passwords) plus the general keyring password, decoded from the loaded keyring file. Localized in EN/IT/DE/FR/ES/zh-CN.<br/>
|
|
13
|
+
|
|
9
14
|
**Version 4.3.24** - June 2026<br/>
|
|
10
15
|
|
|
11
16
|
- Hue **Contact Sensor** and **Motion** nodes: fixed the state on the KNX bus getting stuck on a wrong value (e.g. a contact sensor showing **closed** while the door stayed open) after the Hue event-stream reconnected. The nodes now re-publish the authoritative state at startup and after every (re)connection, and ignore stale/out-of-order reports by checking the `contact_report.changed` / `motion_report.changed` timestamp. Closes [#514](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/issues/514).<br/>
|
package/nodes/commonFunctions.js
CHANGED
|
@@ -408,6 +408,121 @@ module.exports = (RED) => {
|
|
|
408
408
|
}
|
|
409
409
|
})
|
|
410
410
|
|
|
411
|
+
// 2026-06 Reveal all keyring passwords (and the general keyring password) in clear text.
|
|
412
|
+
// Used by the "Utility" tab button, enabled only when KNX Secure is selected.
|
|
413
|
+
RED.httpAdmin.get('/knxUltimateKeyringDump', RED.auth.needsPermission('knxUltimate-config.read'), async (req, res) => {
|
|
414
|
+
try {
|
|
415
|
+
let keyringContent = (req.query.keyring || '').toString()
|
|
416
|
+
let password = (req.query.pwd || '').toString()
|
|
417
|
+
// '__PWRD__' is the Node-RED placeholder for an unchanged password credential: ignore it.
|
|
418
|
+
if (password === '__PWRD__') password = ''
|
|
419
|
+
// Fill any missing field (keyring and/or password) from the existing config node.
|
|
420
|
+
if ((!keyringContent || !password) && req.query.serverId) {
|
|
421
|
+
const cfg = RED.nodes.getNode(req.query.serverId)
|
|
422
|
+
if (cfg) {
|
|
423
|
+
if (!keyringContent) { try { keyringContent = cfg.keyringFileXML || keyringContent } catch (e) { } }
|
|
424
|
+
if (!password) { try { password = (cfg.credentials && cfg.credentials.keyringFilePassword) ? cfg.credentials.keyringFilePassword : password } catch (e) { } }
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (!keyringContent || !password) {
|
|
428
|
+
return res.json({ ok: false, error: 'MISSING_KEYRING_OR_PASSWORD' })
|
|
429
|
+
}
|
|
430
|
+
let Keyring
|
|
431
|
+
try {
|
|
432
|
+
({ Keyring } = require('knxultimate/build/secure/keyring'))
|
|
433
|
+
} catch (e) {
|
|
434
|
+
try { RED.log.error(`KNXUltimate: cannot load Keyring module: ${e.message}`) } catch (err) { }
|
|
435
|
+
return res.json({ ok: false, error: 'KEYRING_MODULE_UNAVAILABLE' })
|
|
436
|
+
}
|
|
437
|
+
const kr = new Keyring()
|
|
438
|
+
try {
|
|
439
|
+
await kr.load(keyringContent, password)
|
|
440
|
+
} catch (e) {
|
|
441
|
+
try { RED.log.error(`KNXUltimate: keyring load error: ${e.message}`) } catch (err) { }
|
|
442
|
+
return res.json({ ok: false, error: 'KEYRING_LOAD_FAILED' })
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const toIAString = (value) => {
|
|
446
|
+
if (!value) return ''
|
|
447
|
+
return typeof value.toString === 'function' ? value.toString() : String(value)
|
|
448
|
+
}
|
|
449
|
+
const toBufferString = (value) => {
|
|
450
|
+
if (!value) return ''
|
|
451
|
+
if (Buffer.isBuffer(value)) return value.toString('hex')
|
|
452
|
+
return String(value)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const lines = []
|
|
456
|
+
lines.push('================ KNX Secure keyring passwords ================')
|
|
457
|
+
lines.push(`Created By: ${kr.getCreatedBy?.() || ''}`)
|
|
458
|
+
lines.push(`Created On: ${kr.getCreated?.() || ''}`)
|
|
459
|
+
lines.push(`General keyring password: ${password}`)
|
|
460
|
+
lines.push('')
|
|
461
|
+
|
|
462
|
+
lines.push('Interfaces:')
|
|
463
|
+
const interfaceMap = kr.getInterfaces?.()
|
|
464
|
+
const interfaces = Array.from(interfaceMap ? interfaceMap.values() : [])
|
|
465
|
+
if (interfaces.length === 0) {
|
|
466
|
+
lines.push(' (none)')
|
|
467
|
+
} else {
|
|
468
|
+
interfaces.forEach((iface, idx) => {
|
|
469
|
+
lines.push(` [${idx + 1}] ${toIAString(iface.individualAddress) || '(unknown)'} (${iface.type || ''})`)
|
|
470
|
+
lines.push(` Host: ${toIAString(iface.host) || ''}`)
|
|
471
|
+
lines.push(` User ID: ${typeof iface.userId === 'number' ? iface.userId : ''}`)
|
|
472
|
+
lines.push(` Password: ${iface.decryptedPassword || ''}`)
|
|
473
|
+
lines.push(` Authentication: ${iface.decryptedAuthentication || ''}`)
|
|
474
|
+
})
|
|
475
|
+
}
|
|
476
|
+
lines.push('')
|
|
477
|
+
|
|
478
|
+
lines.push('Backbones:')
|
|
479
|
+
const backbones = kr.getBackbones?.() || []
|
|
480
|
+
if (backbones.length === 0) {
|
|
481
|
+
lines.push(' (none)')
|
|
482
|
+
} else {
|
|
483
|
+
backbones.forEach((backbone, idx) => {
|
|
484
|
+
lines.push(` [${idx + 1}] Multicast: ${backbone.multicastAddress || ''}`)
|
|
485
|
+
lines.push(` Key (hex): ${toBufferString(backbone.decryptedKey)}`)
|
|
486
|
+
})
|
|
487
|
+
}
|
|
488
|
+
lines.push('')
|
|
489
|
+
|
|
490
|
+
lines.push('Group Addresses:')
|
|
491
|
+
const groupAddressMap = kr.getGroupAddresses?.()
|
|
492
|
+
const groupAddresses = Array.from(groupAddressMap ? groupAddressMap.values() : [])
|
|
493
|
+
if (groupAddresses.length === 0) {
|
|
494
|
+
lines.push(' (none)')
|
|
495
|
+
} else {
|
|
496
|
+
groupAddresses.forEach((group, idx) => {
|
|
497
|
+
lines.push(` [${idx + 1}] ${toIAString(group.address) || ''}`)
|
|
498
|
+
lines.push(` Key (hex): ${toBufferString(group.decryptedKey)}`)
|
|
499
|
+
})
|
|
500
|
+
}
|
|
501
|
+
lines.push('')
|
|
502
|
+
|
|
503
|
+
lines.push('Devices:')
|
|
504
|
+
const deviceMap = kr.getDevices?.()
|
|
505
|
+
const devices = Array.from(deviceMap ? deviceMap.values() : [])
|
|
506
|
+
if (devices.length === 0) {
|
|
507
|
+
lines.push(' (none)')
|
|
508
|
+
} else {
|
|
509
|
+
devices.forEach((device, idx) => {
|
|
510
|
+
lines.push(` [${idx + 1}] ${toIAString(device.individualAddress) || ''}`)
|
|
511
|
+
lines.push(` Tool Key (hex): ${toBufferString(device.decryptedToolKey)}`)
|
|
512
|
+
lines.push(` Management Password: ${device.decryptedManagementPassword || ''}`)
|
|
513
|
+
lines.push(` Authentication: ${device.decryptedAuthentication || ''}`)
|
|
514
|
+
lines.push(` Serial Number: ${device.serialNumber || ''}`)
|
|
515
|
+
})
|
|
516
|
+
}
|
|
517
|
+
lines.push('================ End of keyring passwords ================')
|
|
518
|
+
|
|
519
|
+
res.json({ ok: true, dump: lines.join('\n') })
|
|
520
|
+
} catch (error) {
|
|
521
|
+
try { RED.log.error(`KNXUltimate: knxUltimateKeyringDump error: ${error.message}`) } catch (e) { }
|
|
522
|
+
res.json({ ok: false, error: 'UNEXPECTED_ERROR' })
|
|
523
|
+
}
|
|
524
|
+
})
|
|
525
|
+
|
|
411
526
|
RED.httpAdmin.get('/knxUltimateGetHueColor', (req, res) => {
|
|
412
527
|
try {
|
|
413
528
|
const serverId = RED.nodes.getNode(req.query.serverId) // Retrieve node.id of the config node.
|
|
@@ -164,13 +164,13 @@
|
|
|
164
164
|
active: (node.knxSecureSelected === true || node.knxSecureSelected === 'true') ? 1 : 0,
|
|
165
165
|
activate: function (event, ui) {
|
|
166
166
|
node.knxSecureSelected = $(ui.newTab).index() === 1;
|
|
167
|
-
try { updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); enforceProtocolFromIP(); } catch (e) { }
|
|
167
|
+
try { updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); enforceProtocolFromIP(); updateRevealKeyringButton(); } catch (e) { }
|
|
168
168
|
}
|
|
169
169
|
});
|
|
170
170
|
// Ensure the correct tab is enforced after all UI init
|
|
171
171
|
try {
|
|
172
172
|
const initialActive = (node.knxSecureSelected === true || node.knxSecureSelected === 'true') ? 1 : 0;
|
|
173
|
-
setTimeout(() => { $("#tabsMain").tabs("option", "active", initialActive); updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); enforceProtocolFromIP(); }, 0);
|
|
173
|
+
setTimeout(() => { $("#tabsMain").tabs("option", "active", initialActive); updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); enforceProtocolFromIP(); updateRevealKeyringButton(); }, 0);
|
|
174
174
|
} catch (e) { }
|
|
175
175
|
|
|
176
176
|
// Keyring ACE editor setup
|
|
@@ -1041,6 +1041,56 @@
|
|
|
1041
1041
|
} catch (e) { }
|
|
1042
1042
|
});
|
|
1043
1043
|
|
|
1044
|
+
// Utility: reveal all keyring passwords in clear text.
|
|
1045
|
+
// Enabled only when KNX Secure is selected.
|
|
1046
|
+
function updateRevealKeyringButton() {
|
|
1047
|
+
try {
|
|
1048
|
+
const isSecure = isSecureTabActive();
|
|
1049
|
+
$("#revealkeyringpwd").prop('disabled', !isSecure)
|
|
1050
|
+
.css('opacity', isSecure ? 1 : 0.5)
|
|
1051
|
+
.css('cursor', isSecure ? 'pointer' : 'not-allowed');
|
|
1052
|
+
} catch (e) { }
|
|
1053
|
+
}
|
|
1054
|
+
updateRevealKeyringButton();
|
|
1055
|
+
|
|
1056
|
+
$("#revealkeyringpwd").click(function () {
|
|
1057
|
+
if (!isSecureTabActive()) return;
|
|
1058
|
+
try {
|
|
1059
|
+
const keyring = (node._keyringEditor && typeof node._keyringEditor.getValue === 'function')
|
|
1060
|
+
? node._keyringEditor.getValue()
|
|
1061
|
+
: $("#node-config-input-keyringFileXML").val();
|
|
1062
|
+
const pwd = $("#node-config-input-keyringFilePassword").val();
|
|
1063
|
+
const params = { serverId: node.id };
|
|
1064
|
+
if (typeof keyring === 'string' && keyring.trim() !== '') params.keyring = keyring.trim();
|
|
1065
|
+
// '__PWRD__' is the Node-RED placeholder for an unchanged password credential:
|
|
1066
|
+
// never send it, so the backend falls back to the real stored credential.
|
|
1067
|
+
if (typeof pwd === 'string' && pwd.trim() !== '' && pwd !== '__PWRD__') params.pwd = pwd.trim();
|
|
1068
|
+
$("#divDebugText").show();
|
|
1069
|
+
$("#debugText").val(node._('knxUltimate-config.utility.reveal_keyring_loading'));
|
|
1070
|
+
const scrollToDebugText = function () {
|
|
1071
|
+
try {
|
|
1072
|
+
const el = document.getElementById('debugText');
|
|
1073
|
+
if (el && typeof el.scrollIntoView === 'function') {
|
|
1074
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1075
|
+
}
|
|
1076
|
+
} catch (e) { }
|
|
1077
|
+
};
|
|
1078
|
+
scrollToDebugText();
|
|
1079
|
+
$.getJSON('knxUltimateKeyringDump', params, (data) => {
|
|
1080
|
+
if (data && data.ok) {
|
|
1081
|
+
$("#debugText").val(data.dump);
|
|
1082
|
+
} else {
|
|
1083
|
+
const code = (data && data.error) ? data.error : 'UNEXPECTED_ERROR';
|
|
1084
|
+
$("#debugText").val(node._('knxUltimate-config.utility.reveal_keyring_fail') + ' (' + code + ')');
|
|
1085
|
+
}
|
|
1086
|
+
scrollToDebugText();
|
|
1087
|
+
}).fail(function (jqXHR, textStatus, errorThrown) {
|
|
1088
|
+
const err = (errorThrown || textStatus || '').toString();
|
|
1089
|
+
$("#debugText").val(node._('knxUltimate-config.utility.reveal_keyring_fail') + (err ? (': ' + err) : ''));
|
|
1090
|
+
});
|
|
1091
|
+
} catch (e) { }
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1044
1094
|
|
|
1045
1095
|
|
|
1046
1096
|
},
|
|
@@ -1569,8 +1619,14 @@
|
|
|
1569
1619
|
<input type="button" id="clearpersistga" class="ui-button ui-corner-all ui-widget"
|
|
1570
1620
|
style="background-color:#FFD2D2;width:150px" data-i18n="[value]knxUltimate-config.utility.clear_persist_button">
|
|
1571
1621
|
</div>
|
|
1572
|
-
|
|
1573
|
-
|
|
1622
|
+
|
|
1623
|
+
<div class="form-row">
|
|
1624
|
+
<label><i class="fa fa-key"></i> <span data-i18n="knxUltimate-config.utility.reveal_keyring_label"></span></label>
|
|
1625
|
+
<input type="button" id="revealkeyringpwd" class="ui-button ui-corner-all ui-widget"
|
|
1626
|
+
style="background-color:#FFE9B3;width:150px" data-i18n="[value]knxUltimate-config.utility.reveal_keyring_button">
|
|
1627
|
+
</div>
|
|
1628
|
+
|
|
1629
|
+
<div class="form-row" id="divDebugText" style="display: none;">
|
|
1574
1630
|
<textarea rows="10" id="debugText" style="width:100%"></textarea>
|
|
1575
1631
|
</div>
|
|
1576
1632
|
</p>
|
|
@@ -24,6 +24,12 @@ const loggerClass = require('./utils/sysLogger')
|
|
|
24
24
|
const payloadRounder = require('./utils/payloadManipulation')
|
|
25
25
|
const utils = require('./utils/utils')
|
|
26
26
|
|
|
27
|
+
// Versions logged once at startup (node package + KNXUltimate engine)
|
|
28
|
+
let NODE_VERSION = 'unknown'
|
|
29
|
+
try { NODE_VERSION = require('../package.json').version } catch (e) { /* empty */ }
|
|
30
|
+
let KNX_ENGINE_VERSION = 'unknown'
|
|
31
|
+
try { KNX_ENGINE_VERSION = require('knxultimate/package.json').version } catch (e) { /* empty */ }
|
|
32
|
+
|
|
27
33
|
// DATAPONT MANIPULATION HELPERS
|
|
28
34
|
// ####################
|
|
29
35
|
const sortBy = (field) => (a, b) => {
|
|
@@ -185,6 +191,11 @@ const findAutoEthernetInterface = (targetIP) => {
|
|
|
185
191
|
}
|
|
186
192
|
|
|
187
193
|
module.exports = (RED) => {
|
|
194
|
+
// Log node and KNXUltimate engine versions once, at Node-RED startup.
|
|
195
|
+
try {
|
|
196
|
+
RED.log.info(`KNXUltimate: node-red-contrib-knx-ultimate v${NODE_VERSION} (KNXUltimate engine v${KNX_ENGINE_VERSION})`)
|
|
197
|
+
} catch (e) { /* empty */ }
|
|
198
|
+
|
|
188
199
|
function knxUltimateConfigNode (config) {
|
|
189
200
|
RED.nodes.createNode(this, config)
|
|
190
201
|
const node = this
|
|
@@ -144,7 +144,11 @@
|
|
|
144
144
|
"clear_persist_ok": "Löschen",
|
|
145
145
|
"clear_persist_cancel": "Abbrechen",
|
|
146
146
|
"clear_persist_done": "Persistenter GA-Cache gelöscht.",
|
|
147
|
-
"clear_persist_fail": "Persistenter GA-Cache konnte nicht gelöscht werden"
|
|
147
|
+
"clear_persist_fail": "Persistenter GA-Cache konnte nicht gelöscht werden",
|
|
148
|
+
"reveal_keyring_label": "Keyring-Passwörter im Klartext anzeigen (KNX Secure)",
|
|
149
|
+
"reveal_keyring_button": "Anzeigen",
|
|
150
|
+
"reveal_keyring_loading": "Keyring wird dekodiert, bitte warten...",
|
|
151
|
+
"reveal_keyring_fail": "Keyring konnte nicht dekodiert werden. Prüfen Sie die Keyring-Datei und das allgemeine Passwort."
|
|
148
152
|
}
|
|
149
153
|
}
|
|
150
154
|
}
|
|
@@ -148,7 +148,11 @@
|
|
|
148
148
|
"clear_persist_ok": "Delete",
|
|
149
149
|
"clear_persist_cancel": "Cancel",
|
|
150
150
|
"clear_persist_done": "Persisted GA cache deleted.",
|
|
151
|
-
"clear_persist_fail": "Unable to delete persisted GA cache"
|
|
151
|
+
"clear_persist_fail": "Unable to delete persisted GA cache",
|
|
152
|
+
"reveal_keyring_label": "Reveal keyring passwords in clear text (KNX Secure)",
|
|
153
|
+
"reveal_keyring_button": "Reveal",
|
|
154
|
+
"reveal_keyring_loading": "Decoding keyring, please wait...",
|
|
155
|
+
"reveal_keyring_fail": "Unable to decode the keyring. Check the keyring file and the general password."
|
|
152
156
|
}
|
|
153
157
|
}
|
|
154
158
|
}
|
|
@@ -148,7 +148,11 @@
|
|
|
148
148
|
"clear_persist_ok": "Eliminar",
|
|
149
149
|
"clear_persist_cancel": "Cancelar",
|
|
150
150
|
"clear_persist_done": "Caché persistente de GA eliminada.",
|
|
151
|
-
"clear_persist_fail": "No se puede eliminar la caché persistente de GA"
|
|
151
|
+
"clear_persist_fail": "No se puede eliminar la caché persistente de GA",
|
|
152
|
+
"reveal_keyring_label": "Mostrar las contraseñas del keyring en texto claro (KNX Secure)",
|
|
153
|
+
"reveal_keyring_button": "Mostrar",
|
|
154
|
+
"reveal_keyring_loading": "Decodificando el keyring, espere...",
|
|
155
|
+
"reveal_keyring_fail": "No se puede decodificar el keyring. Comprueba el archivo keyring y la contraseña general."
|
|
152
156
|
}
|
|
153
157
|
}
|
|
154
158
|
}
|
|
@@ -148,7 +148,11 @@
|
|
|
148
148
|
"clear_persist_ok": "Supprimer",
|
|
149
149
|
"clear_persist_cancel": "Annuler",
|
|
150
150
|
"clear_persist_done": "Cache GA persistant supprimé.",
|
|
151
|
-
"clear_persist_fail": "Impossible de supprimer le cache GA persistant"
|
|
151
|
+
"clear_persist_fail": "Impossible de supprimer le cache GA persistant",
|
|
152
|
+
"reveal_keyring_label": "Afficher les mots de passe du keyring en clair (KNX Secure)",
|
|
153
|
+
"reveal_keyring_button": "Afficher",
|
|
154
|
+
"reveal_keyring_loading": "Décodage du keyring en cours, veuillez patienter...",
|
|
155
|
+
"reveal_keyring_fail": "Impossible de décoder le keyring. Vérifiez le fichier keyring et le mot de passe général."
|
|
152
156
|
}
|
|
153
157
|
}
|
|
154
158
|
}
|
|
@@ -152,7 +152,11 @@
|
|
|
152
152
|
"clear_persist_ok": "Elimina",
|
|
153
153
|
"clear_persist_cancel": "Annulla",
|
|
154
154
|
"clear_persist_done": "Cache persistente GA eliminata.",
|
|
155
|
-
"clear_persist_fail": "Impossibile eliminare la cache persistente GA"
|
|
155
|
+
"clear_persist_fail": "Impossibile eliminare la cache persistente GA",
|
|
156
|
+
"reveal_keyring_label": "Mostra le password del keyring in chiaro (KNX Secure)",
|
|
157
|
+
"reveal_keyring_button": "Mostra",
|
|
158
|
+
"reveal_keyring_loading": "Decodifica del keyring in corso, attendere...",
|
|
159
|
+
"reveal_keyring_fail": "Impossibile decodificare il keyring. Verifica il file keyring e la password generale."
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
162
|
}
|
|
@@ -140,7 +140,11 @@
|
|
|
140
140
|
"clear_persist_ok": "删除",
|
|
141
141
|
"clear_persist_cancel": "取消",
|
|
142
142
|
"clear_persist_done": "已删除持久化 GA 缓存。",
|
|
143
|
-
"clear_persist_fail": "无法删除持久化 GA 缓存"
|
|
143
|
+
"clear_persist_fail": "无法删除持久化 GA 缓存",
|
|
144
|
+
"reveal_keyring_label": "以明文显示密钥环密码 (KNX Secure)",
|
|
145
|
+
"reveal_keyring_button": "显示",
|
|
146
|
+
"reveal_keyring_loading": "正在解码密钥环,请稍候...",
|
|
147
|
+
"reveal_keyring_fail": "无法解码密钥环。请检查密钥环文件和通用密码。"
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
150
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"engines": {
|
|
4
4
|
"node": ">=20.18.1"
|
|
5
5
|
},
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "5.0.0",
|
|
7
7
|
"description": "Control your KNX and KNX Secure intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control, ETS group address importer, KNX AI for diagnosticsand KNX routing between interfaces. Easy to use and highly configurable.",
|
|
8
8
|
"files": [
|
|
9
9
|
"nodes/",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dns-sync": "0.2.1",
|
|
20
20
|
"google-translate-tts": "^0.3.0",
|
|
21
21
|
"js-yaml": "4.1.1",
|
|
22
|
-
"knxultimate": "
|
|
22
|
+
"knxultimate": "6.0.1",
|
|
23
23
|
"lodash": "4.18.1",
|
|
24
24
|
"node-color-log": "12.0.1",
|
|
25
25
|
"ping": "0.4.4",
|