node-red-contrib-knx-ultimate
Version:
Control your KNX intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control and ETS group address importer. Easy to use and highly configurable.
535 lines (486 loc) • 27.4 kB
HTML
<script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script>
<script type="text/javascript">
RED.nodes.registerType('knxUltimate-config', {
category: 'config',
defaults: {
host: { value: "224.0.23.12", required: true },
port: { value: 3671, required: true, validate: RED.validators.number() },
// the KNX physical address we'd like to use
physAddr: { value: "15.15.22", required: true },
hostProtocol: { value: "Auto", required: false }, // TunnelUDP/TunnelTCP/Multicast
// enable this option to suppress the acknowledge flag with outgoing L_Data.req requests. LoxOne needs this
suppressACKRequest: { value: false },
csv: { value: "", required: false },
KNXEthInterface: { value: "Auto" },
KNXEthInterfaceManuallyInput: { value: "" },
stopETSImportIfNoDatapoint: { value: "fake" },
loglevel: { value: "error" },
name: { value: "KNX Gateway" },
localEchoInTunneling: { value: true },
delaybetweentelegrams: { value: 25, required: false, validate: RED.validators.number() },
ignoreTelegramsWithRepeatedFlag: { value: false, required: false },
keyringFileXML: { value: "" },
knxSecureSelected: { value: false },
autoReconnect: { value: "yes" }
},
credentials: {
keyringFilePassword: { type: "password" }
},
oneditprepare: function () {
// Go to the help panel
try {
RED.sidebar.show("help");
} catch (error) { }
var node = this;
// TIMER BLINK ####################################################
let blinkStatus = 2;
let timerBlinkBackground;
function blinkBackgroundArray(_arrayElementIDwithHashAtTheBeginning) {
if (timerBlinkBackground !== undefined) clearInterval(timerBlinkBackground);
timerBlinkBackground = setInterval(() => {
for (let index = 0; index < _arrayElementIDwithHashAtTheBeginning.length; index++) {
const _elementIDwithHashAtTheBeginning = _arrayElementIDwithHashAtTheBeginning[index];
if (isEven(blinkStatus)) $(_elementIDwithHashAtTheBeginning).css("background-color", "lightgreen");
if (!isEven(blinkStatus)) $(_elementIDwithHashAtTheBeginning).css("background-color", "");
}
blinkStatus += 1;
if (blinkStatus >= 14) {
clearInterval(timerBlinkBackground);
blinkStatus = 2;
for (let index = 0; index < _arrayElementIDwithHashAtTheBeginning.length; index++) {
const _elementIDwithHashAtTheBeginning = _arrayElementIDwithHashAtTheBeginning[index];
$(_elementIDwithHashAtTheBeginning).css("background-color", "");
}
}
}, 100);
}
function isEven(n) {
return (n % 2 == 0);
}
// ################################################################
// 22/07/2021 Main tab
$("#tabsMain").tabs({
active: node.knxSecureSelected ? 1 : 0,
activate: function (event, ui) {
node.knxSecureSelected = $(ui.newTab).index() === 1;
}
});
// Autocomplete with KNX IP Interfaces
let aKNXInterfaces = [];
$.getJSON("knxUltimateDiscoverKNXGateways?" + { _: new Date().getTime() }, (data) => {
for (let index = 0; index < data.length; index++) {
const element = data[index];
const [ip, port, name, address] = element.split(':');
// Aggiungi l'elemento con descrizione e valore
aKNXInterfaces.push({
label: `${name} -> ${ip}:${port} phys addr:${address}`, // Testo visibile nella lista
value: element // Valore selezionato
});
}
$("#interfaces-count").text(`${aKNXInterfaces.length} interfaces found.`);
// Configura l'autocompletamento per il campo di input
$("#node-config-input-host").autocomplete({
minLength: 0, // Mostra suggerimenti subito
source: aKNXInterfaces, // Dati per il completamento
select: function (event, ui) {
// Quando l'utente seleziona un elemento, aggiorna il valore del campo
const [ip, port, name, address] = ui.item.value.split(':');
$("#node-config-input-host").val(ip);
$("#node-config-input-port").val(port);
$("#node-config-input-name").val(name);
$("#node-config-input-physAddr").val(address.split(".")[0] + '.' + address.split(".")[1] + '.' + Math.floor(Math.random() * 256));
blinkBackgroundArray(["#node-config-input-host", "#node-config-input-port", "#node-config-input-name", "#node-config-input-physAddr"]);
return false; // Evita il comportamento predefinito del browser
},
focus: function (event, ui) {
// Mantieni il valore corrente durante il focus, ma non cambiare il valore effettivo
$("#node-config-input-host").val(ui.item.value.split(":"[0]));
return false; // Evita di aggiornare il campo con il valore selezionato
}
}).on("focus", function () {
// Apri l'autocomplete quando il campo riceve il focus
$(this).autocomplete("search", "");
}).on("click", function () {
// Apri l'autocomplete quando il campo riceve il focus
$(this).autocomplete("search", "");
}).autocomplete("instance")._renderItem = function (ul, item) {
// Personalizza l'aspetto degli elementi della lista
const [ip, port, name, address] = item.value.split(':');
const color = (ip === "224.0.23.12") ? "gray" : "green"; // Condizione
return $("<li>")
.append(`<div style="color: ${color};"><b>${ip}:${port}</b> -> ${name} - Address:${address}</div>`) // Mostra la descrizione completa
.appendTo(ul);
}
});
$("#node-config-input-KNXEthInterface").append($("<option></option>")
.attr("value", "Auto")
.text("Auto")
);
$("#node-config-input-KNXEthInterface").append($("<option></option>")
.attr("value", "Manual")
.text("Manually enter interface's name")
);
$.getJSON('knxUltimateETHInterfaces', (data) => {
data.sort().forEach(iFace => {
$("#node-config-input-KNXEthInterface").append($("<option></option>")
.attr("value", iFace.name)
.text(iFace.name + " (" + iFace.address + ")")
)
});
$("#node-config-input-KNXEthInterface").val(typeof node.KNXEthInterface === "undefined" ? "Auto" : node.KNXEthInterface)
if (node.KNXEthInterface === "Manual") {
// Show input
$("#divKNXEthInterfaceManuallyInput").show();
} else {
$("#divKNXEthInterfaceManuallyInput").hide()
}
$("#node-config-input-KNXEthInterface").on('change', function () {
if ($("#node-config-input-KNXEthInterface").val() === "Manual") {
// Show input
$("#divKNXEthInterfaceManuallyInput").show();
} else {
// Hide input
$("#divKNXEthInterfaceManuallyInput").hide()
}
});
});
// 14/08/2021 Elimino il file delle persistenze di questo nodo
$.getJSON("deletePersistGAFile?serverId=" + node.id, (data) => { });
// 06/07/2023 Tabs
// *****************************
$("#tabs").tabs();
// *****************************
var sRetDebugText = "";
$("#getinfocam").click(function () {
sRetDebugText = "";
$("#divDebugText").show();
for (const [key, value] of Object.entries(node)) {
sRetDebugText += (`-> ${key}: ${value}\r`);
}
$("#debugText").val(sRetDebugText); // Store the config-node);
});
$("#getallgaused").click(function () {
sRetDebugText = "";
$("#divDebugText").show();
let aFound = [];
RED.nodes.eachNode(function (node) {
if (!aFound.includes(node.topic)) {
aFound.push(node.topic);
sRetDebugText += node.topic + "\r"
}
});
sRetDebugText = 'Copy these group addresses in your routing table list of your KNX/IP router.\r' + sRetDebugText;
$("#debugText").val(sRetDebugText); // Store the config-node);
});
},
oneditsave: function () {
// Return to the info tab
try {
RED.sidebar.show("info");
} catch (error) { }
var node = this;
// Check if the csv file contains errors
if (($("#node-config-input-csv").val() != 'undefined' && $("#node-config-input-csv").val() != "") || ($("#node-config-input-keyring").val() != 'undefined' && $("#node-config-input-keyring").val() != "")) {
var checkResult = node._("knxUltimate-config.ets.deploywithETS");
var myNotification = RED.notify(checkResult,
{
modal: true,
fixed: true,
type: 'info',
buttons: [
{
text: "OK",
click: function (e) {
myNotification.close();
}
}]
})
}
},
label: function () {
return typeof this.name === undefined ? (this.host + ":" + this.port) : this.name + " " + (this.host + ":" + this.port);
}
});
</script>
<style>
.ui-tabs {
background: transparent;
border: none;
}
.ui-tabs .ui-widget-header {
background: transparent;
border: none;
border-bottom: 1px solid #c0c0c0;
-moz-border-radius: 0px;
-webkit-border-radius: 0px;
border-radius: 0px;
}
.ui-tabs .ui-tabs-nav .ui-state-default {
background: transparent;
border: none;
}
.ui-tabs .ui-tabs-nav .ui-state-active {
background: transparent no-repeat bottom center;
border: none;
}
.ui-tabs .ui-tabs-nav .ui-state-default a {
color: #878787;
outline: none;
}
.ui-tabs .ui-tabs-nav .ui-state-active a {
color: #377e00;
border-bottom: 1px solid#377e00;
outline: none;
}
</style>
<script type="text/html" data-template-name="knxUltimate-config">
<div class="form-row">
<b><span data-i18n="knxUltimate-config.properties.title"></span></b>  <span style="color:red" data-i18n="[html]knxUltimate-config.properties.helplink"></span>
<br/><br/>
</div>
<div class="form-row">
<label for="node-config-input-name" style="width: 200px" >
<i class="fa fa-tag"></i> Name </label>
<input type="text" id="node-config-input-name" style="width: 200px">
</div>
<div class="form-row">
<label for="node-config-input-host" style="width: 200px"><i class="fa fa-server"></i> IP/Hostname</label>
<input type="text" id="node-config-input-host" style="width: 200px"><br/>
<span id="interfaces-count" style="font-size: 12px; color:#377e00 ; margin-top: 2px;"></span>
</div>
<!-- KNX Secure / Unsecure tabbed selector-->
<!-- <div id="tabsMain" style="display: none;"> -->
<!-- <div id="tabsMain">
<ul>
<li><a href="#unsecureKNX"><i class="fa fa-circle-o"></i> <span data-i18n="knxUltimate-config.properties.unsecureKNX"></span></a></li>
<li><a href="#SecureKNX"><i class="fa fa-shield"></i> <span data-i18n="knxUltimate-config.properties.secureKNX"></span></a></li>
</ul>
<div id="unsecureKNX" style="margin: 5px 5px 5px 5px;">
<p>
</p>
</div>
<div id="SecureKNX" style="margin: 5px 5px 5px 5px;" >
<p>
<p>WARNING: KNX SECURE IS STILL UNDER DEVELOPMENT</p>
<div class="form-row">
<i class="fa fa-youtube"></i>
<a href="https://youtu.be/OpR7ZQTlMRU" target="_blank">
<span data-i18n="knxUltimate-config.ets.youtubeKeyring"></span>
</a>
</div>
<div class="form-row">
Keyring file
<textarea rows="5" id="node-config-input-keyringFileXML" style="width:100%" data-i18n="[placeholder]knxUltimate-config.ets.keyring"></textarea>
</div>
<div class="form-row">
<label for="node-config-input-keyringFilePassword"><i class="fa fa-shield"></i> Password</label>
<input type="password" id="node-config-input-keyringFilePassword" style="width:200px;">
</div>
</p>
</div>
</div> -->
<br/>
<div id="tabs">
<ul>
<li><a href="#tabs-1"><i class="fa fa-list-ol"></i> Configuration</a></li>
<li><a href="#tabs-2"><i class="fa fa-braille"></i> Advanced</a></li>
<li><a href="#tabs-3"><i class="fa fa-code"></i> ETS file import</a></li>
<li><a href="#tabs-4"><i class="fa fa-key"></i> Utility</a></li>
</ul>
<div id="tabs-1">
<p>
<div class="form-row">
<label for="node-config-input-port" style="width: 200px">
IP Port
</label>
<input type="text" id="node-config-input-port" style="width: 100px">
</div>
<div class="form-row">
<label for="node-config-input-hostProtocol" style="width: 200px">
IP Protocol
</label>
<select id="node-config-input-hostProtocol" >
<option value="Auto" >Auto</option>
<option value="TunnelUDP" >Tunnel UDP (KNX/IP Interface)</option>
<option value="Multicast" >Multicast (KNX/IP Router)</option>
<!-- <option value="TunnelTCP" >Tunnel TCP</option> -->
</select>
</div>
<div class="form-row">
<label for="node-config-input-physAddr" style="width: 200px">
<i class="fa fa-microchip"></i>
<span data-i18n="knxUltimate-config.advanced.knx_phy_addr"></span>
</label>
<input type="text" id="node-config-input-physAddr" style="width:100px">
</div>
<div class="form-row">
<label for="node-config-input-KNXEthInterface" style="width: 200px">
<i class="fa fa-wifi"></i>
<span data-i18n="knxUltimate-config.properties.bind_local_int"></span>
</label>
<select id="node-config-input-KNXEthInterface"></select>
</div>
<div class="form-row" id="divKNXEthInterfaceManuallyInput" style="display: none;">
<label for="node-config-input-KNXEthInterfaceManuallyInput">Interface name:</label>
<input type="text" id="node-config-input-KNXEthInterfaceManuallyInput"
placeholder="Interface name, ex: eth0 or ens1 or Ethernet 1 and so on...">
</div>
<div class="form-row">
<label for="node-config-input-autoReconnect" style="width: 200px">
<i class="fa fa-plug"></i>
<span data-i18n="knxUltimate-config.properties.autoReconnect"></span>
</label>
<select id="node-config-input-autoReconnect">
<option value="yes" data-i18n="knxUltimate-config.properties.autoReconnect_yes"></option>
<option value="no" data-i18n="knxUltimate-config.properties.autoReconnect_no"></option>
</select>
</div>
</p>
</div>
<div id="tabs-2">
<p>
<div class="form-row">
<input type="checkbox" id="node-config-input-localEchoInTunneling"
style="display:inline-block; width:auto; vertical-align:top;">
<label style="width:85%" for="node-config-input-localEchoInTunneling">
<i class="fa fa-bullhorn"></i>
<span data-i18n="knxUltimate-config.advanced.localEchoInTunneling"></span>
</label>
</div>
<div class="form-row">
<input type="checkbox" id="node-config-input-ignoreTelegramsWithRepeatedFlag"
style="display:inline-block; width:auto; vertical-align:top;">
<label style="width:85%" for="node-config-input-ignoreTelegramsWithRepeatedFlag">
<i class="fa fa-ban"></i>
<span data-i18n="knxUltimate-config.advanced.ignoreTelegramsWithRepeatedFlag"></span>
</label>
</div>
<div class="form-row">
<input type="checkbox" id="node-config-input-suppressACKRequest"
style="display:inline-block; width:auto; vertical-align:top;">
<label style="width:85%" for="node-config-input-suppressACKRequest">
<i class="fa fa-ban"></i>
<span data-i18n="knxUltimate-config.advanced.suppress_ack"></span>
</label>
</div>
<div class="form-row">
<label for="node-config-input-delaybetweentelegrams" style="width:auto">
<i class="fa fa-hourglass-start"></i>
<span data-i18n="knxUltimate-config.advanced.delaybetweentelegrams"></span>
</label>
<input type="number" id="node-config-input-delaybetweentelegrams" style="width:20%">
</div>
<div class="form-row">
<label for="node-config-input-loglevel">
<i class="fa fa-question-circle"></i>
<span data-i18n="knxUltimate-config.advanced.log_level"></span>
</label>
<select id="node-config-input-loglevel" style="width:40%;">
<option value="disable" data-i18n="knxUltimate-config.advanced.select_silent"></option>
<option value="error" data-i18n="knxUltimate-config.advanced.select_error"></option>
<option value="warn" data-i18n="knxUltimate-config.advanced.select_warning"></option>
<option value="info" data-i18n="knxUltimate-config.advanced.select_info"></option>
<option value="debug" data-i18n="knxUltimate-config.advanced.select_debug"></option>
</select>
<!-- <// DEBUG LEVELS: success < debug < info < warn < error < disable
</div> -->
</div>
</p>
</div>
<div id="tabs-3">
<p>
<div id="etsCSVListBox">
<h3><span data-i18n="knxUltimate-config.properties.ets_import"></span></h3>
<div>
<div class="form-row">
<span data-i18n="knxUltimate-config.ets.description"></span>
</div>
<div class="form-row">
<span style="color:red" data-i18n="[html]knxUltimate-config.ets.instruction"></span>
</div>
<div class="form-row">
<span style="color:red" data-i18n="[html]knxUltimate-config.ets.youtube"></span>
</div>
<div class="form-row">
<label for="node-config-input-stopETSImportIfNoDatapoint" style="width:250px">
<i class="fa fa-question-circle"></i>
<span data-i18n="knxUltimate-config.ets.help_ga"></span>
</label>
<select id="node-config-input-stopETSImportIfNoDatapoint" style="width:210px">
<option value="stop" data-i18n="knxUltimate-config.ets.import_select_stop"></option>
<option value="fake" data-i18n="knxUltimate-config.ets.import_select_fake"></option>
<option value="skip" data-i18n="knxUltimate-config.ets.import_select_skip"></option>
</select>
</div>
<div class="form-row">
<label style="width:auto" for="node-config-input-csv">
<i class="fa fa-th-list"></i> ETS group address list
</label>
</div>
<div class="form-row">
<textarea rows="20" id="node-config-input-csv" style="width:100%"
data-i18n="[placeholder]knxUltimate-config.ets.ga_list_help"></textarea>
</div>
</div>
</div>
</p>
</div>
<div id="tabs-4">
<p>
<div class="form-row">
<label style="width:300px"><i class="fa fa-sign-in"></i> Gather debug and log for troubleshoot</label>
<input type="button" id="getinfocam" class="ui-button ui-corner-all ui-widget"
style="background-color:#AEE1FF;width:150px" value="Read">
</div>
<div class="form-row">
<label style="width:300px"><i class="fa fa-sign-in"></i> Get all used GA for KNX routing filter</label>
<input type="button" id="getallgaused" class="ui-button ui-corner-all ui-widget"
style="background-color:#AEE1FF;width:150px" value="Read">
</div>
<div class="form-row" id="divDebugText" style="display: none;">
<textarea rows="10" id="debugText" style="width:100%"></textarea>
</div>
</p>
</div>
</div>
</script>
<script type="text/markdown" data-help-name="knxUltimate-config">
<p>This node connects to your KNX/IP Gateway.</p>
**General**
|Property|Description|
|--|--|
| Name | Node name. |
| IP/Hostname | ETH/KNX Router multicast address or Interface unicast IP address. If you have an KNX/IP interface, use the interface's IP address, for example 1982.168.1.22, otherwise, if you have a KNX/IP router, put the multicast address 224.0.23.12. You can also type an **Hostname** instead of an IP. |
<br/>
**Configuration**
|Property|Description|
|--|--|
| IP Port | The port. Default is 3671. |
| IP Protocol | *Tunnel UDP* is for KNX/IP interfaces, *Multicast UDP* is for KNX/IP Routers. Leave **Auto** for auto detect. CAUTION: **Auto** won't work with hostnames (for example myknx.home.com), but only with IP addresses. Default is "Auto". |
| KNX Physical Address | The physical KNX address, example 1.1.200. Default is "15.15.22".|
| Bind to local interface | The Node will use this local interface for communications. Leave "Auto" for automatic selection. If you have more than one lan connection, for example Ethernet and Wifi, it's strongly recommended to manually select the interface, otherwise not all UDP telegram will reach your computer, thus the Node may not work as expected. Default is "Auto". |
| Automatically connect to KNX BUS at start | Auto connect to the bus at start. Default is "Yes". |
<br/>
**Advanced**
|Property|Description|
|--|--|
| Echo sent message to all node with same Group Address | Send the msg input coming from the flow, to all nodes having the same group address. The nodes will receive the new msg as if it's coming from the KNX bus. This is useful in case of using the KNX emulation and in case the connection to the KNX bus is not established. **This option will be deprecated in the next version and defaulted to checked.** Default is checked. |
| Suppress repeated (R-Flag) telegrams fom BUS | Ignore repeated KNX telegrams coming from the bus. Default is unchecked. |
| Suppress ACK request in tunneling mode | Enable it if you have a very old KNX/IP gateway. It ignores the ACK procedure and accepts all telegrams. Default is unchecked.|
| Delay between each telegram (in milliseconds) | KNX specs states, that the maximum telegram sending speed is 50 telegrams per second. A speed between 25 and 50ms should be fine, unless you're connecting to a remote KNX Gateway via a slow internet connection (in this case, you should increase the value by, for example, 100 to 200ms). Don't exeed 200ms. |
| Loglevel | Log level, in case you need to debug something with the dev. Default is "Error", |
<br/>
**ETS file import**
|Property|Description|
|--|--|
| If Group Address has no Datapoint | If a group address doesn't have a datapoint, it allow to choose wether to stop import, import quth a fake datapoint of 1.001 or to skip import of that group address |
| ETS group address list | Use this section to import your ETS CSV or ESF file. You can either **paste the CSV or ESF file content** or **set the file path**, for example *./pi/homecsv.csv*. Please refer to the help links for further infos. |
<br/>
**Utility**
|Property|Description|
|--|--|
| Gather debug info for troubleshoot | Please click the button and add it to the gitHub issue you want to open, it will help me a lot to helping you. |
| Get all used GA for KNX routing filter | Press READ to retrieve a plain text list of all group address belonging to this gateway, that has been used in the flows. You can use this list to populate your KNX/IP router filter table. |
<br/>
[](https://www.paypal.com/donate/?hosted_button_id=S8SKPUBSPK758)
</script>