UNPKG

node-red-contrib-knx-ultimate

Version:

Control your KNX and KNX Secure 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.

594 lines (533 loc) 30.2 kB
<script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script> <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script> <script type="text/javascript"> RED.nodes.registerType('knxUltimateHueScene', { category: "KNX Ultimate", color: '#C0C7E9', defaults: { //buttonState: {value: true}, server: { type: "knxUltimate-config", required: false }, serverHue: { type: "hue-config", required: true }, name: { value: "" }, // Single scene namescene: { value: "" }, GAscene: { value: "" }, dptscene: { value: "" }, valscene: { value: 0 }, // the scene number or true/false namesceneStatus: { value: "" }, GAsceneStatus: { value: "" }, dptsceneStatus: { value: "" }, enableNodePINS: { value: "no" }, outputs: { value: 0 }, inputs: { value: 0 }, hueDevice: { value: "" }, hueSceneRecallType: { value: "active" }, // Multi scene GAsceneMulti: { value: "" }, namesceneMulti: { value: "" }, dptsceneMulti: { value: "" }, rules: { value: [{ t: "eq", v: "", vt: "str" }] }, selectedModeTabNumber: { value: 0 } }, inputs: 0, outputs: 0, icon: "node-hue-icon.svg", label: function () { if (this.selectedModeTabNumber === undefined) return this.name; if (Number(this.selectedModeTabNumber) === 0) return this.name; if (Number(this.selectedModeTabNumber) === 1) return this.namesceneMulti; }, paletteLabel: "Hue Scene", // button: { // enabled: function() { // // return whether or not the button is enabled, based on the current // // configuration of the node // return !this.changed // }, // visible: function() { // // return whether or not the button is visible, based on the current // // configuration of the node // return this.hasButton // }, // //toggle: "buttonState", // onclick: function() {} // }, oneditprepare: function () { // Go to the help panel try { RED.sidebar.show("help"); } catch (error) { } var node = this; var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node var oNodeServerHue = RED.nodes.node($("#node-input-serverHue").val()); // Store the config-node for (let index = 1; index <= 64; index++) { $("#node-input-valscene").append($("<option></option>") .attr("value", index) .text("Scene n. " + index) ) } $("#node-input-valscene").val(node.valscene) // 19/02/2020 Used to get the server sooner als deploy. $("#node-input-server").change(function () { try { oNodeServer = RED.nodes.node($(this).val()); } catch (error) { } }); // 19/02/2020 Used to get the server sooner als deploy. $("#node-input-serverHue").change(function () { try { oNodeServerHue = RED.nodes.node($(this).val()); } catch (error) { } }); // DPT // ######################## $.getJSON('knxUltimateDpts?serverId=' + $("#node-input-server").val(), (data) => { data.forEach(dpt => { if (dpt.value.startsWith("1.") || dpt.value.startsWith("18.")) { $("#node-input-dptscene").append($("<option></option>") .attr("value", dpt.value) .text(dpt.text)) } if (dpt.value.startsWith("18.")) { $("#node-input-dptsceneMulti").append($("<option></option>") .attr("value", dpt.value) .text(dpt.text)) } if (dpt.value.startsWith("1.")) { $("#node-input-dptsceneStatus").append($("<option></option>") .attr("value", dpt.value) .text(dpt.text)) } }); $("#node-input-dptscene").val(this.dptscene) $("#node-input-dptsceneStatus").val(this.dptsceneStatus) $("#node-input-dptsceneMulti").val(this.dptsceneMulti) ShowHideValScene(); }) // Autocomplete suggestion with ETS csv File $("#node-input-GAscene").autocomplete({ minLength: 0, source: function (request, response) { //$.getJSON("csv", request, function( data, status, xhr ) { $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => { response($.map(data, function (value, key) { var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt); if (htmlUtilsfullCSVSearch(sSearch, request.term)) { if (value.dpt.startsWith("1.") || value.dpt.startsWith("18.")) { return { label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display value: value.ga // Value } } else { return null; } } else { return null; } })); }); }, select: function (event, ui) { // Sets Datapoint and device name automatically var sDevName = ui.item.label.split("#")[1].trim(); try { sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim(); } catch (error) { } $('#node-input-namescene').val(sDevName); var optVal = $("#node-input-dptscene option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value'); // Select the option value $("#node-input-dptscene").val(optVal); ShowHideValScene(); } }).focus(function () { $(this).autocomplete('search', $(this).val() + 'exactmatch'); }); try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting($("#node-input-GAscene"), oNodeServer.id); } catch (e) {} $("#node-input-GAsceneStatus").autocomplete({ minLength: 0, source: function (request, response) { //$.getJSON("csv", request, function( data, status, xhr ) { $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => { response($.map(data, function (value, key) { var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt); if (htmlUtilsfullCSVSearch(sSearch, request.term)) { if (value.dpt.startsWith("1.")) { return { label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display value: value.ga // Value } } else { return null; } } else { return null; } })); }); }, select: function (event, ui) { // Sets Datapoint and device name automatically var sDevName = ui.item.label.split("#")[1].trim(); try { sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim(); } catch (error) { } $('#node-input-namesceneStatus').val(sDevName); var optVal = $("#node-input-dptsceneStatus option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value'); // Select the option value $("#node-input-dptsceneStatus").val(optVal); ShowHideValScene(); } }).focus(function () { $(this).autocomplete('search', $(this).val() + 'exactmatch'); }); try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting($("#node-input-GAsceneStatus"), oNodeServer.id); } catch (e) {} $("#node-input-GAsceneMulti").autocomplete({ minLength: 0, source: function (request, response) { //$.getJSON("csv", request, function( data, status, xhr ) { $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => { response($.map(data, function (value, key) { var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt); if (htmlUtilsfullCSVSearch(sSearch, request.term)) { if (value.dpt.startsWith("18.")) { return { label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display value: value.ga // Value } } else { return null; } } else { return null; } })); }); }, select: function (event, ui) { // Sets Datapoint and device name automatically var sDevName = ui.item.label.split("#")[1].trim(); try { sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim(); } catch (error) { } $('#node-input-namesceneMulti').val(sDevName); var optVal = $("#node-input-dptsceneMulti option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value'); // Select the option value $("#node-input-dptsceneMulti").val(optVal); ShowHideValScene(); } }).focus(function () { $(this).autocomplete('search', $(this).val() + 'exactmatch'); }); try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting($("#node-input-GAsceneMulti"), oNodeServer.id); } catch (e) {} // ######################## function ShowHideValScene() { if ($("#node-input-dptscene").val() !== null) { // Adapt the scene value/true if ($("#node-input-dptscene").val().startsWith("1.")) { $("#divValScene").hide(); //$("#node-input-valscene").attr("disabled", "disabled"); } else { $("#divValScene").show() //$("#node-input-valscene").removeAttr("disabled"); } } } $("#node-input-dptscene").on("change", function () { ShowHideValScene() }); ShowHideValScene(); // Autocomplete suggestion with HUE $("#node-input-name").autocomplete({ minLength: 0, source: function (request, response) { $.getJSON("KNXUltimateGetResourcesHUE?rtype=scene&serverId=" + oNodeServerHue.id, (data) => { response($.map(data.devices, function (value, key) { //alert(JSON.stringify(value) + " "+ key) var sSearch = (value.name); if (htmlUtilsfullCSVSearch(sSearch, request.term)) { return { hueDevice: value.id, // Label for Display value: value.name // Value } } else { return null; } })); }); }, select: function (event, ui) { // Sets the fields $('#node-input-hueDevice').val(ui.item.hueDevice); } }).focus(function () { $(this).autocomplete('search', $(this).val() + 'exactmatch'); }); // ######################## // ----------------------------------------------------------------------- // MULTI SCENE // ######################## function resizeRule(rule) { } function setTableTitle(_selectedIndex) { // Save only the tab 0 or 1 if (_selectedIndex === undefined) _selectedIndex = 0; if (Number(_selectedIndex) <= 1) { $("#node-input-selectedModeTabNumber").val(Number(_selectedIndex)); if (Number(_selectedIndex) === 0) { $('#tabs ul:first li:eq(0) a').html('<i class="fa fa-check" aria-hidden="true"></i> Single mode'); $('#tabs ul:first li:eq(1) a').text('Multi mode'); } else if (Number(_selectedIndex) === 1) { $('#tabs ul:first li:eq(0) a').text('Sigle mode'); $('#tabs ul:first li:eq(1) a').html('<i class="fa fa-check" aria-hidden="true"></i> Multi mode'); } } } $("#node-input-rule-container").css('min-height', '200px').css('min-width', '450px').editableList({ scrollOnAdd: true, //header: $("<div>").append($.parseHTML("<div style='width:5%; display: inline-grid'>Sort</div><div style='width:15%; display: inline-grid'>KNX Scene number</div><div style='width:60%; display: inline-grid'>HUE Scene name</div><div style='width:15%; display: inline-grid'>Recall scene as</div><div style='width:5%; display: inline-grid'>Delete</div>")), addItem: function (container, i, opt) { // row, index, data // opt.r is: { rowRuleKNXSceneNumber: rowRuleKNXSceneNumber, rowRuleHUESceneName: rowRuleHUESceneName, rowRuleHUESceneID:rowRuleHUESceneID, rowRuleRecallAs:rowRuleRecallAs} if (!opt.hasOwnProperty('r')) { opt.r = {}; } var rule = opt.r; if (!opt.hasOwnProperty('i')) { opt._i = Math.floor((0x99999 - 0x10000) * Math.random()).toString(); } container.css({ overflow: 'hidden', whiteSpace: 'nowrap' }); var row = $('<div class="form-row"/>').appendTo(container); var rowRuleKNXSceneNumber = $('<select/>', { class: "rowRuleKNXSceneNumber", type: "text", style: "width:25%; margin-left: 5px; text-align: left;" }).appendTo(row); var rowRuleHUESceneName = $("<input/>", { class: "rowRuleHUESceneName", type: "text", placeholder: "HUE device name", style: "width:45%; margin-left: 5px; text-align: left;" }).appendTo(row); var rowRuleHUESceneID = $("<input/>", { class: "rowRuleHUESceneID", type: "hidden", placeholder: "HUE device name", style: "width:0px; margin-left: 5px; text-align: left;" }).appendTo(row); var rowRuleRecallAs = $('<select/>', { class: "rowRuleRecallAs", type: "text", style: "width:25%; margin-left: 5px; text-align: left;" }).appendTo(row); var finalspan = $('<span/>', { style: "" }).appendTo(row); finalspan.append('<span class="node-input-rule-index"></span> '); for (let index = 1; index <= 64; index++) { rowRuleKNXSceneNumber.append( $("<option>") .val(index) .text(node._("knxUltimateHueScene.knx_scene_n") + index.toString()) ); rowRuleKNXSceneNumber.val(rule.rowRuleKNXSceneNumber); } rowRuleRecallAs.append( $("<option>") .val("active") .text(node._("knxUltimateHueScene.recall_active")) ); rowRuleRecallAs.append( $("<option>") .val("dynamic_palette") .text(node._("knxUltimateHueScene.recall_dynamic")) ); rowRuleRecallAs.append( $("<option>") .val("static") .text(node._("knxUltimateHueScene.recall_static")) ); rowRuleRecallAs.val(rule.rowRuleRecallAs); rowRuleHUESceneName.autocomplete({ minLength: 1, source: function (request, response) { $.getJSON("KNXUltimateGetResourcesHUE?rtype=scene&serverId=" + oNodeServerHue.id, (data) => { response($.map(data.devices, function (value, key) { //alert(JSON.stringify(value) + " "+ key) var sSearch = (value.name); if (htmlUtilsfullCSVSearch(sSearch, request.term)) { return { hueDevice: value.id, // Label for Display value: value.name // Value } } else { return null; } })); }); }, select: function (event, ui) { // Sets Datapoint and device name automatically rowRuleHUESceneID.val(ui.item.hueDevice); } }).focus(function () { $(this).autocomplete('search', $(this).val() + 'exactmatch'); }); rowRuleRecallAs.val(rule.rowRuleRecallAs) rowRuleHUESceneName.val(rule.rowRuleHUESceneName); rowRuleHUESceneID.val(rule.rowRuleHUESceneID); }, removeItem: function (opt) { }, resizeItem: resizeRule, sortItems: function (rules) { }, sortable: true, removable: true }); // Put some spaces after the container $('<br/><br/><br/>').insertAfter($("#node-input-rule-container")); // For each rule, create a row if (this.rules !== undefined) { for (var i = 0; i < this.rules.length; i++) { var rule = this.rules[i]; $("#node-input-rule-container").editableList('addItem', { r: rule, i: i }); } } // ######################## // MULTI SCENE // ----------------------------------------------------------------------- this.selectedModeTabNumber === undefined ? 0 : this.selectedModeTabNumber; $("#tabs").tabs({ activate: function (event, ui) { setTableTitle(ui.newTab.index()); } }); $("#tabs").tabs("option", "active", this.selectedModeTabNumber); setTableTitle(this.selectedModeTabNumber); }, oneditsave: function () { // Return to the info tab try { RED.sidebar.show("info"); } catch (error) { } if ($("#node-input-enableNodePINS").val() === "yes") { this.outputs = 1; this.inputs = 1; } else { this.outputs = 0; this.inputs = 0; } var node = this; // opt.r is: { rowRuleKNXSceneNumber: rowRuleKNXSceneNumber, rowRuleHUESceneName: rowRuleHUESceneName, rowRuleHUESceneID:rowRuleHUESceneID, rowRuleRecallAs:rowRuleRecallAs} var rules = $("#node-input-rule-container").editableList('items'); node.rules = []; rules.each(function (i) { var rule = $(this); var rowRuleKNXSceneNumber = rule.find(".rowRuleKNXSceneNumber").val(); var rowRuleHUESceneName = rule.find(".rowRuleHUESceneName").val(); var rowRuleHUESceneID = rule.find(".rowRuleHUESceneID").val(); var rowRuleRecallAs = rule.find(".rowRuleRecallAs").val(); node.rules.push({ rowRuleKNXSceneNumber: rowRuleKNXSceneNumber, rowRuleHUESceneName: rowRuleHUESceneName, rowRuleHUESceneID: rowRuleHUESceneID, rowRuleRecallAs: rowRuleRecallAs }); }); }, oneditcancel: function () { // Return to the info tab try { RED.sidebar.show("info"); } catch (error) { } }, oneditresize: function (size) { } }) </script> <script type="text/html" data-template-name="knxUltimateHueScene"> <div class="form-row"> <input type="hidden" id="node-input-selectedModeTabNumber"> <b><span data-i18n="knxUltimateHueScene.title"></span></b>&nbsp&nbsp<span style="color:red" &nbsp<i class="fa fa-youtube"></i></span>&nbsp<a target="_blank" href="https://youtu.be/jjEUI1J8bkA"><u><span data-i18n="common.youtube_sample"></span></u></a> <br /> <br /> <p align="center"> <i class="fa fa-tv fa-fade fa-4x"></i> </p> <br /> <label for="node-input-server" > <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKnRFWHRDcmVhdGlvbiBUaW1lAEZyIDYgQXVnIDIwMTAgMjE6NTI6MTkgKzAxMDD84aS8AAAAB3RJTUUH3gYYCicNV+4WIQAAAAlwSFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAACUSURBVHjaY2CgFZg5c+Z/ZEyWAZ8+f/6/ZsWs/xoamqMGkGrA6Wla/1+fVARjEBuGsSoGmY4eZSCNL59d/g8DIDbIAHR14OgFGQByKjIGKX5+6/T///8gGMQGiV1+/B0Fg70GIkD+RMYgxf/O5/7//2MSmAZhkBi6OrgB6Bg5DGB4ajr3f2xqsYYLSDE2THJUDg0AAAqyDVd4tp4YAAAAAElFTkSuQmCC"></img> <span data-i18n="common.knx_gw"></span> </label> <input type="text" id="node-input-server" /> </div> <div class="form-row"> <label for="node-input-serverHue"> <img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAAA0VXHyAAABFUlEQVQ4EZWSsWoCQRCG1yiENEFEi6QSkjqWWoqFoBYJ+Br6JHkMn8Iibd4ihQpaJIhWNkry/ZtdGZY78Qa+m39nZ+dm9s4550awglNBluS/gVtAX6KgDclf68w2OThgfR9iT/jnoEv4TtByDThWTCDKW4SSZTf/zj9/eZbN+izTDuKGimu0vPF8B/YN8aC8LmcOj/AAn9CFTEs70Js/oGqy79C69bqJ5XbQI2kGO5N8QL9D08S8zBtBF5ZaVsznpCMoqJnVdjTpb1Db0fwIWmQV6BLXzFOYgA6/gDVfQN9bBWp2J2hdWDPoBV5FrKnAJutHikk/CHHR8i7x4iG7qQ720IYvu3GFbpHjx3pFrOFYkA354z/5bkK826phyAAAAABJRU5ErkJggg=="/> <span data-i18n="common.hue_bridge"></span> </label> <input type="text" id="node-input-serverHue" /> </div> <br/> <div id="tabs"> <ul> <li><a href="#tabs-1"> Single scene</a></li> <li><a href="#tabs-2"> Multi scene</a></li> <li><a href="#tabs-3"> Behaviour</a></li> </ul> <div id="tabs-1"> <br/> <p> <b><span data-i18n="common.philips_hue"></span></b> </p> <div class="form-row"> <label for="node-input-hueDevice" > <i class="fa fa-play-circle"></i>&nbsp<span data-i18n="knxUltimateHueScene.hue_scene"></span></label> <input type="text" id="node-input-name" placeholder="Enter your hue device name" /> <input type="hidden" id="node-input-hueDevice" /> </div> <div class="form-row"> <label for="node-input-hueSceneRecallType"> <i class="fa fa-minus-circle"></i> <span data-i18n="knxUltimateHueScene.recall_as"></span> </label> <select id="node-input-hueSceneRecallType"> <option value="active" data-i18n="knxUltimateHueScene.recall_active"></option> <option value="dynamic_palette" data-i18n="knxUltimateHueScene.recall_dynamic"></option> <option value="static" data-i18n="knxUltimateHueScene.recall_static"></option> </select> </div> <br/> <p> <b>KNX</b> </p> <div class="form-row"> <label for="node-input-namescene" style="width:100px;"><i class="fa fa-play-circle-o"></i> <span data-i18n="knxUltimateHueScene.recall"></span></label> <label for="node-input-GAscene" style="width:20px;"><span data-i18n="common.ga"></span></label> <input type="text" id="node-input-GAscene" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;"> <label for="node-input-dptscene" style="width:40px; margin-left: 0px; text-align: right;"><span data-i18n="common.dpt"></span></label> <select id="node-input-dptscene" style="width:140px;"></select> <label for="node-input-namescene" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="common.name"></span></label> <input type="text" id="node-input-namescene" style="width:200px;margin-left: 5px; text-align: left;"> </div> <div class="form-row" id="divValScene" hidden> <label for="node-input-valscene" style="width:100px;"></label> <label for="node-input-valscene" style="width:20px;">#</label> <select id="node-input-valscene" style="width:180px;margin-left: 5px; text-align: left;"></select> </div> <div class="form-row"> <label for="node-input-namesceneStatus" style="width:100px;"><i class="fa fa-question-circle"></i> <span data-i18n="knxUltimateHueScene.status"></span></label> <label for="node-input-GAsceneStatus" style="width:20px;"><span data-i18n="common.ga"></span></label> <input type="text" id="node-input-GAsceneStatus" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;"> <label for="node-input-dptsceneStatus" style="width:40px; margin-left: 0px; text-align: right;"><span data-i18n="common.dpt"></span></label> <select id="node-input-dptsceneStatus" style="width:140px;"></select> <label for="node-input-namesceneStatus" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="common.name"></span></label> <input type="text" id="node-input-namesceneStatus" style="width:200px;margin-left: 5px; text-align: left;"> </div> <br/> <br/> </div> <!-- // End Tab 1 --> <div id="tabs-2"> <br/> <div class="form-row"> <label for="node-input-namesceneMulti" style="width:100px;"><i class="fa fa-play-circle-o"></i> <span data-i18n="knxUltimateHueScene.recall"></span></label> <label for="node-input-GAsceneMulti" style="width:20px;"><span data-i18n="common.ga"></span></label> <input type="text" id="node-input-GAsceneMulti" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;"> <label for="node-input-dptsceneMulti" style="width:40px; margin-left: 0px; text-align: right;"><span data-i18n="common.dpt"></span></label> <select id="node-input-dptsceneMulti" style="width:140px;"></select> <label for="node-input-namesceneMulti" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="common.name"></span></label> <input type="text" id="node-input-namesceneMulti" style="width:200px;margin-left: 5px; text-align: left;"> </div> <div> <dt><i class="fa fa-code-fork"></i> <span data-i18n="knxUltimateHueScene.scene_selector"></span></dt> <div class="form-row node-input-rule-container-row"> <ol id="node-input-rule-container"></ol> </div> <div class="form-row"> <p></p> </div> </div> <br/> </div> <!-- // End Tab 2 --> <div id="tabs-3"> <br/> <div class="form-row"> <label for="node-input-enableNodePINS" style="width:240px;"> <i class="fa fa-circle"></i> <span data-i18n="knxUltimateHueScene.node_pins"></span> </label> <select id="node-input-enableNodePINS"> <option value="no" data-i18n="knxUltimateHueScene.node_pins_hide"></option> <option value="yes" data-i18n="knxUltimateHueScene.node_pins_show"></option> </select> </div> </div> <!-- // End Tab 3 --> </div> <!-- // End TABS --> </script>