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, ETS group address importer, and KNX routing between interfaces. Easy to use and highly configurable.

904 lines (849 loc) 84.9 kB
<!-- <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/jquery.searchableSelect.js"></script> --> <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script> <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/KNXSendSnippets.js"></script> <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/KNXReceiveSnippets.js"></script> <script type="text/javascript"> RED.nodes.registerType('knxUltimate', { category: "KNX Ultimate", color: '#7dd484', defaults: { //buttonState: {value: true}, server: { type: "knxUltimate-config", required: true }, topic: { value: "" }, setTopicType: { value: "str" }, outputtopic: { value: "" }, dpt: { value: "" }, initialread: { value: 0 }, notifyreadrequest: { value: false }, notifyresponse: { value: false }, notifywrite: { value: true }, notifyreadrequestalsorespondtobus: { value: false }, notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized: { value: "0" }, name: { value: "" }, outputtype: { value: "write" }, outputRBE: { value: "true" }, inputRBE: { value: "false" }, formatmultiplyvalue: { value: 1 }, formatnegativevalue: { value: "leave" }, formatdecimalsvalue: { value: 999 }, passthrough: { value: "no" }, sendMsgToKNXCode: { value: "" }, receiveMsgFromKNXCode: { value: "" }, listenallga: { value: "" }, gaSecure: { value: false }, buttonEnabled: { value: true }, buttonMode: { value: "toggle" }, buttonStaticValue: { value: "" }, buttonToggleInitial: { value: "false" }, periodicSend: { value: false }, periodicSendInterval: { value: 60 } }, inputs: 1, outputs: 1, icon: function () { try { return (this.gaSecure === true || this.gaSecure === 'true') ? "node-knx-icon-secure.svg" : "node-knx-icon.svg"; } catch (e) { return "node-knx-icon.svg"; } }, label: function () { const functionSendMsgToKNXCode = (this.sendMsgToKNXCode !== undefined && this.sendMsgToKNXCode !== '') ? "f(x) " : ""; const functionreceiveMsgFromKNXCode = (this.receiveMsgFromKNXCode !== undefined && this.receiveMsgFromKNXCode !== '') ? " f(x)" : ""; 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|" : "") }, paletteLabel: "KNX DEVICE", button: { enabled: function () { return !this.changed; }, visible: function () { const isUniversal = this.setTopicType === 'listenAllGA' || this.listenallga === true || this.listenallga === 'true'; if (isUniversal) return false; return this.buttonEnabled === true || this.buttonEnabled === "true"; }, onclick: function () { const node = this; const mode = node.buttonMode || 'read'; const request = { id: node.id, mode }; if (mode === 'value') { request.value = node.buttonStaticValue; } $.ajax({ type: "POST", url: "knxUltimate/buttonAction", data: request, success: function (response) { const key = mode === 'read' ? "manualReadOk" : "manualWriteOk"; const baseMessage = RED._("node-red-contrib-knx-ultimate/knxUltimate:knxUltimate." + key) || "KNX command sent"; let notifyMessage = baseMessage; if (mode !== 'read') { let payloadValue; if (response && Object.prototype.hasOwnProperty.call(response, 'payload')) { payloadValue = response.payload; } else if (mode === 'value' && Object.prototype.hasOwnProperty.call(request, 'value')) { payloadValue = request.value; } if (payloadValue !== undefined) { let payloadText; if (typeof payloadValue === 'object') { try { payloadText = JSON.stringify(payloadValue); } catch (error) { payloadText = String(payloadValue); } } else { payloadText = String(payloadValue); } const payloadLabel = RED._("node-red:common.label.payload") || "payload"; notifyMessage = `${baseMessage} (${payloadLabel}: ${payloadText})`; } } RED.notify(notifyMessage, "success"); }, error: function (xhr) { let message; if (xhr && xhr.responseJSON && xhr.responseJSON.error) { message = xhr.responseJSON.error; } const key = mode === 'read' ? "manualReadError" : "manualWriteError"; RED.notify(message || (RED._("node-red-contrib-knx-ultimate/knxUltimate:knxUltimate." + key) || "Unable to send KNX command"), "error"); } }); } }, oneditprepare: function () { // Go to the help panel try { RED.sidebar.show("help"); } catch (error) { } try { $("#node-input-topic").data('knxGaAutocomplete', true); $("#node-input-knxFunctionHelperGAList").data('knxGaAutocomplete', true); } catch (error) { } var node = this; if ($("#node-input-server").val() === "_ADD_") { // Node-Red 4.0.x has a bug not selecting the default server node try { $("#node-input-server").prop("selectedIndex", 0); } catch (error) { } } var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node const $buttonEnabled = $('#node-input-buttonEnabled'); const $buttonOptions = $('#knx-button-options'); const $buttonMode = $('#node-input-buttonMode'); const $buttonToggleInitial = $('#node-input-buttonToggleInitial'); const $buttonStaticValue = $('#node-input-buttonStaticValue'); const $buttonToggleRow = $('.knx-button-toggle-row'); const $buttonValueRow = $('.knx-button-value-row'); const coerceBoolean = (value) => (value === true || value === 'true'); $buttonEnabled.prop('checked', coerceBoolean(node.buttonEnabled)); $buttonMode.val(node.buttonMode || 'read'); $buttonToggleInitial.val(coerceBoolean(node.buttonToggleInitial) ? 'true' : 'false'); $buttonStaticValue.val(node.buttonStaticValue || ''); const refreshButtonOptions = () => { const enabled = $buttonEnabled.prop('checked'); const mode = $buttonMode.val() || 'read'; if (enabled) { $buttonOptions.show(); } else { $buttonOptions.hide(); } $buttonToggleRow.toggle(enabled && mode === 'toggle'); $buttonValueRow.toggle(enabled && mode === 'value'); node.buttonEnabled = enabled; node.buttonMode = mode; node.buttonToggleInitial = $buttonToggleInitial.val(); node.buttonStaticValue = $buttonStaticValue.val(); }; $buttonEnabled.on('change', refreshButtonOptions); $buttonMode.on('change', refreshButtonOptions); $buttonToggleInitial.on('change', refreshButtonOptions); $buttonStaticValue.on('change keyup', function () { node.buttonStaticValue = $(this).val(); }); refreshButtonOptions(); // Secure GA cache from keyring let secureGAs = new Set(); function refreshSecureGAs() { try { const sid = $("#node-input-server").val(); if (!sid || sid === '_ADD_') { secureGAs = new Set(); return; } $.getJSON("knxUltimateKeyringDataSecureGAs?serverId=" + sid + "&_=" + new Date().getTime(), (data) => { try { const set = new Set(); if (Array.isArray(data)) data.forEach(ga => { if (typeof ga === 'string') set.add(ga); }); secureGAs = set; } catch (e) { secureGAs = new Set(); } }).fail(function () { secureGAs = new Set(); }); } catch (error) { secureGAs = new Set(); } } $("#tabs").tabs(); // 15/09/2020 Supergiovane, set the help sample based on Datapoint const knxFunctionHelperItems = [ { id: 'getGAValue', label: 'getGAValue(address, dpt?)', aceValue: "getGAValue('1/1/1', '1.001')", snippet: "getGAValue('${1:1/1/1}', '${2:1.001}')", doc: 'Read the cached value of another group address. Provide the datapoint if the ETS import is not available.' }, { id: 'setGAValue', label: 'setGAValue(address, value, dpt?)', aceValue: "setGAValue('1/1/1', true)", snippet: "setGAValue('${1:1/1/1}', ${2:true}, '${3:1.001}')", doc: 'Send a value to any KNX group address. The datapoint is optional when the ETS file is imported.' }, { id: 'self', label: 'self(value)', aceValue: 'self(false)', snippet: 'self(${1:false})', doc: 'Set this node value and forward it to the KNX bus.' }, { id: 'toggle', label: 'toggle()', aceValue: 'toggle()', snippet: 'toggle()', doc: 'Invert this node value and write it to the KNX bus.' } ]; const globalScope = typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : {}); function knxUltimateDptsGetHelp(_dpt, _forceClose) { const detailsContainer = $("#dptDetailsContainer") if (_forceClose === true) { detailsContainer.hide() } const serverId = $("#node-input-server").val() if (serverId === "_ADD_" || serverId === '' || _dpt === null || _dpt === '') return $.getJSON("knxUltimateDptsGetHelp?dpt=" + _dpt + "&serverId=" + serverId + "&" + { _: new Date().getTime() }, (data) => { const helplinkContainer = $("#sampleCodeEditor") try { if (node.sampleEditor) { node.sampleEditor.destroy() delete node.sampleEditor } } catch (error) { } $("#example-editor").empty() helplinkContainer.empty() const translate = (key, opts) => { try { if (typeof RED !== 'undefined' && RED._) { return RED._(key, opts) } } catch (error) { } return null } const noSampleText = translate('knxUltimate.dptDetails.noSample') || 'Currently, no sample payload is available.' const createEditor = (value) => { node.sampleEditor = RED.editor.createEditor({ id: 'example-editor', mode: 'ace/mode/javascript', value }) try { node.sampleEditor.setReadOnly(true) node.sampleEditor.setShowPrintMargin(false) } catch (error) { } } try { const hasHelp = data.help !== 'NO' if (hasHelp) { createEditor(data.help) if (data.helplink) { const label = translate('Detail', { dpt: _dpt }) || ('More details for ' + _dpt) helplinkContainer.html(`&nbsp;<i class="fa fa-question-circle"></i>&nbsp;<a target="_blank" href="${data.helplink}"><u>${label}</u></a>`) } } else { createEditor(noSampleText) if (data.helplink) { const labelWiki = translate('Wiki') || 'Open wiki' helplinkContainer.html(`&nbsp;<i class="fa fa-question-circle"></i>&nbsp;<a target="_blank" href="${data.helplink}"><u>${labelWiki}</u></a>`) } } } catch (error) { } if (detailsContainer.is(':visible') && node.sampleEditor) { setTimeout(() => { try { node.sampleEditor.resize(true) } catch (error) { } }, 0) } }) } const applyEditorOptions = (editor) => { try { if (!editor) return; if (typeof editor.updateOptions === 'function') { editor.updateOptions({ lineNumbers: 'off', minimap: { enabled: false }, scrollbar: { verticalScrollbarSize: 8, horizontalScrollbarSize: 8 } }); } else if (editor.renderer && typeof editor.renderer.setShowGutter === 'function') { editor.renderer.setShowGutter(false); } if (typeof editor.setShowPrintMargin === 'function') editor.setShowPrintMargin(false); if (typeof ace !== 'undefined' && ace.require) { try { ace.require('ace/ext/language_tools'); } catch (error) { } } if (typeof editor.setOptions === 'function') { editor.setOptions({ enableBasicAutocompletion: true, enableLiveAutocompletion: true }); } if (typeof editor.completers === 'undefined') { editor.completers = []; } if (Array.isArray(editor.completers) && !editor._knxHelperCompleter) { const aceCompletions = knxFunctionHelperItems.map(item => ({ caption: item.label, value: item.aceValue, snippet: item.snippet, meta: 'KNX helper', doc: item.doc })); const helperCompleter = { getCompletions: function (_editor, _session, _pos, prefix, callback) { const search = (prefix || '').toLowerCase(); const filtered = search ? aceCompletions.filter(entry => entry.caption.toLowerCase().startsWith(search) || entry.value.toLowerCase().startsWith(search)) : aceCompletions; callback(null, filtered.length ? filtered : aceCompletions); }, getDocTooltip: function (item) { if (!item || item.docHTML || !item.doc) return; item.docHTML = '<b>' + item.caption + '</b><hr />' + item.doc; } }; editor.completers.push(helperCompleter); editor._knxHelperCompleter = helperCompleter; } if (typeof monaco !== 'undefined' && !globalScope.knxFunctionMonacoCompletionProvider) { try { const suggestions = knxFunctionHelperItems.map(item => ({ label: item.label, kind: monaco.languages.CompletionItemKind.Function, insertText: item.snippet, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, documentation: item.doc })); globalScope.knxFunctionMonacoCompletionProvider = monaco.languages.registerCompletionItemProvider('javascript', { provideCompletionItems: () => ({ suggestions }) }); } catch (error) { } } } catch (error) { } }; const highlightEditor = (editor, active) => { if (!editor) return; try { if (editor.renderer && editor.renderer.scroller) { editor.renderer.scroller.style.backgroundColor = active ? '#e6ffe6' : ''; } if (editor.renderer && editor.renderer.content) { editor.renderer.content.style.backgroundColor = active ? '#e6ffe6' : ''; } if (typeof editor.getDomNode === 'function') { const dom = editor.getDomNode(); if (dom) dom.style.backgroundColor = active ? '#e6ffe6' : ''; } else if (editor.container) { editor.container.style.backgroundColor = active ? '#e6ffe6' : ''; } } catch (error) { } }; const attachFocusHandlers = (editor) => { if (!editor) return; try { if (typeof editor.on === 'function' && editor.renderer) { editor.on('focus', () => { node.activeCodeEditor = editor; highlightEditor(editor, true); }); editor.on('blur', () => highlightEditor(editor, false)); } else if (typeof editor.onDidFocusEditorWidget === 'function') { editor.onDidFocusEditorWidget(() => { node.activeCodeEditor = editor; highlightEditor(editor, true); }); if (typeof editor.onDidBlurEditorWidget === 'function') { editor.onDidBlurEditorWidget(() => highlightEditor(editor, false)); } } } catch (error) { } }; const sanitizeAutocompleteValue = (raw) => { if (typeof raw !== 'string') return ''; return raw.replace(/['"]/g, '').trim(); }; const replaceLiteralAroundCursorAce = (editor, text) => { try { const AceRange = (typeof ace !== 'undefined' && ace.require) ? ace.require('ace/range').Range : null; if (!AceRange) return false; const pos = editor.getCursorPosition(); const line = editor.session.getLine(pos.row) || ''; let left = pos.column - 1; while (left >= 0 && line[left] !== "'") left--; if (left < 0) return false; let right = pos.column; while (right < line.length && line[right] !== "'") right++; if (right >= line.length) return false; const newLiteral = "'" + text + "'"; const range = new AceRange(pos.row, left, pos.row, right + 1); editor.session.replace(range, newLiteral); editor.moveCursorTo(pos.row, left + newLiteral.length); return true; } catch (error) { } return false; }; const replaceLiteralAroundCursorMonaco = (editor, text) => { try { const position = editor.getPosition(); if (!position) return false; const model = typeof editor.getModel === 'function' ? editor.getModel() : null; if (!model) return false; const lineContent = model.getLineContent(position.lineNumber) || ''; let leftIdx = position.column - 2; if (leftIdx >= lineContent.length) leftIdx = lineContent.length - 1; while (leftIdx >= 0 && lineContent[leftIdx] !== "'") leftIdx--; if (leftIdx < 0) return false; let rightIdx = position.column - 1; if (rightIdx < leftIdx) rightIdx = leftIdx + 1; while (rightIdx < lineContent.length && lineContent[rightIdx] !== "'") rightIdx++; if (rightIdx >= lineContent.length) return false; const newLiteral = "'" + text + "'"; const range = new monaco.Range( position.lineNumber, leftIdx + 1, position.lineNumber, rightIdx + 2 ); editor.executeEdits('knxInsertGA', [{ range, text: newLiteral, forceMoveMarkers: true }]); editor.setPosition({ lineNumber: position.lineNumber, column: leftIdx + 1 + newLiteral.length }); return true; } catch (error) { } return false; }; const insertTextIntoEditor = (editor, text) => { if (!editor || !text) return; try { if (editor.session && typeof editor.session.insert === 'function') { editor.focus(); const selectionRange = editor.getSelection && typeof editor.getSelectionRange === 'function' ? editor.getSelectionRange() : null; if (selectionRange && !selectionRange.isEmpty()) { editor.session.replace(selectionRange, text); return; } const replaced = replaceLiteralAroundCursorAce(editor, text); if (!replaced) { const pos = editor.getCursorPosition(); editor.session.insert(pos, text); } return; } if (typeof editor.executeEdits === 'function' && typeof editor.getPosition === 'function' && typeof monaco !== 'undefined') { const selection = typeof editor.getSelection === 'function' ? editor.getSelection() : null; const hasSelection = selection && (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn); if (selection && hasSelection) { const selectionRange = new monaco.Range( selection.startLineNumber, selection.startColumn, selection.endLineNumber, selection.endColumn ); editor.executeEdits('knxInsertGA', [{ range: selectionRange, text, forceMoveMarkers: true }]); const targetLine = selectionRange.startLineNumber; const targetColumn = selectionRange.startColumn + text.length; editor.setPosition({ lineNumber: targetLine, column: targetColumn }); editor.focus(); return; } const position = editor.getPosition(); if (!position) return; let replaced = replaceLiteralAroundCursorMonaco(editor, text); if (!replaced) { const range = new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column); const newColumn = position.column + text.length; editor.executeEdits('knxInsertGA', [{ range, text, forceMoveMarkers: true }]); editor.setPosition({ lineNumber: position.lineNumber, column: newColumn }); } editor.focus(); } } catch (error) { } }; node.sendMsgToKNXCodeEditor = RED.editor.createEditor({ id: 'sendMsgToKNXCode-editor', mode: 'ace/mode/nrjavascript', value: node.sendMsgToKNXCode }); applyEditorOptions(node.sendMsgToKNXCodeEditor); node.receiveMsgFromKNXCodeEditor = RED.editor.createEditor({ id: 'receiveMsgFromKNXCode-editor', mode: 'ace/mode/nrjavascript', value: node.receiveMsgFromKNXCode }); applyEditorOptions(node.receiveMsgFromKNXCodeEditor); node.activeCodeEditor = null; attachFocusHandlers(node.sendMsgToKNXCodeEditor); attachFocusHandlers(node.receiveMsgFromKNXCodeEditor); $("#btn-insert-knxFunctionGA").off('click').on('click', function () { const rawValue = $("#node-input-knxFunctionHelperGAList").val(); if (!rawValue || rawValue.trim() === '') { $("#node-input-knxFunctionHelperGAList").focus(); return; } const sanitizedValue = sanitizeAutocompleteValue(rawValue); const editor = node.activeCodeEditor || node.sendMsgToKNXCodeEditor || node.receiveMsgFromKNXCodeEditor; if (!editor) return; if (!sanitizedValue) return; insertTextIntoEditor(editor, sanitizedValue); }); const $knxFunctionHelperInput = $("#node-input-knxFunctionHelperGAList"); const $knxFunctionHelperInsertButton = $("#btn-insert-knxFunctionGA"); let knxFunctionHelperDefaultPlaceholder = $knxFunctionHelperInput.attr('placeholder') || ''; if (!knxFunctionHelperDefaultPlaceholder) { try { knxFunctionHelperDefaultPlaceholder = RED._("node-red-contrib-knx-ultimate/knxUltimate:knxUltimate.placeholder.search") || ''; } catch (error) { } } const knxFunctionHelperNoEtsPlaceholder = 'To enable the search, IMPORT THE ETS FILE'; const refreshKnxFunctionHelperState = (hasEtsCsv) => { $("#divknxFunctionHelperGAList").show(); $knxFunctionHelperInput.prop('disabled', !hasEtsCsv); $knxFunctionHelperInsertButton.prop('disabled', !hasEtsCsv); if (hasEtsCsv) { $knxFunctionHelperInput.attr('placeholder', knxFunctionHelperDefaultPlaceholder); } else { $knxFunctionHelperInput.val(''); $knxFunctionHelperInput.attr('placeholder', knxFunctionHelperNoEtsPlaceholder); } }; const configureSnippetPicker = (snippets, inputSelector, datalistSelector, applySnippet) => { const inputEl = $(inputSelector) const datalistEl = $(datalistSelector) if (!inputEl.length || !datalistEl.length) return const items = Array.isArray(snippets) ? snippets.slice() : [] const translate = key => { try { if (typeof RED !== 'undefined' && RED._) { return RED._(key) } } catch (error) { } return null } const populate = () => { datalistEl.empty() if (!items.length) { inputEl.val('') inputEl.prop('disabled', true) const emptyPlaceholder = inputEl.data('empty') || translate('knxUltimate.snippets.emptyPlaceholder') || '' inputEl.attr('placeholder', emptyPlaceholder) return } inputEl.prop('disabled', false) const defaultPlaceholder = inputEl.data('placeholder') || translate('knxUltimate.snippets.searchPlaceholder') || '' inputEl.attr('placeholder', defaultPlaceholder) items.forEach(snippet => { const opt = document.createElement('option') opt.value = snippet.title || snippet.id || 'Snippet' datalistEl.append(opt) }) } populate() const applySelectedSnippet = value => { if (!value) return const snippet = items.find(sn => (sn.title || sn.id) === value) if (!snippet) return applySnippet(snippet) inputEl.val('') } inputEl.on('change', function () { applySelectedSnippet($(this).val()) }) inputEl.on('keydown', function (evt) { if (evt.key === 'Enter') { evt.preventDefault() applySelectedSnippet($(this).val()) } }) } configureSnippetPicker(window.KNXSendSnippets || [], '#sendSnippetPicker', '#sendSnippetOptions', snippet => { node.sendMsgToKNXCodeEditor.session.setValue(snippet.code || '') }) configureSnippetPicker(window.KNXReceiveSnippets || [], '#receiveSnippetPicker', '#receiveSnippetOptions', snippet => { node.receiveMsgFromKNXCodeEditor.session.setValue(snippet.code || '') }) const dptDetailsContainer = $('#dptDetailsContainer') $('#toggleDptDetails').on('click', function (evt) { evt.preventDefault() if (!dptDetailsContainer.length) return dptDetailsContainer.stop(true, true).slideToggle(200, function () { if (dptDetailsContainer.is(':visible') && node.sampleEditor) { try { node.sampleEditor.resize(true) } catch (error) { } } }) }) function checkUI() { // Backward compatibility if (node.outputRBE === true || $("#node-input-outputRBE").val() === true) { node.outputRBE = 'true'; $("#node-input-outputRBE").val("true") } if (node.outputRBE === undefined || node.outputRBE === false || $("#node-input-outputRBE").val() === false) { node.outputRBE = 'false'; $("#node-input-outputRBE").val("false") } if (node.inputRBE === true || $("#node-input-inputRBE").val() === true) { node.inputRBE = 'true'; $("#node-input-inputRBE").val("true") } if (node.inputRBE === undefined || node.inputRBE === false || $("#node-input-inputRBE").val() === false) { node.inputRBE = 'false'; $("#node-input-inputRBE").val("false") } if (node.passthrough === undefined) { node.passthrough = 'no'; $("#node-input-passthrough").val("no") } if (node.initialread === undefined || node.initialread === false) { node.initialread = 0; $("#node-input-initialread").val(0) } oNodeServer = RED.nodes.node($("#node-input-server").val()); if (oNodeServer === undefined) { // Show the DEPLOY FIRST message $("#divDeployFirst").show(); $("#divMain").hide(); } else { $("#divDeployFirst").hide(); $("#divMain").show(); try { if (typeof oNodeServer.csv !== "undefined" && oNodeServer.csv !== "") { $("#isETSFileLoaded").val("si"); } else { $("#isETSFileLoaded").val("no"); } } catch (error) { $("#isETSFileLoaded").val("no"); } refreshKnxFunctionHelperState($("#isETSFileLoaded").val() === "si"); if (oNodeServer.knxSecureSelected) { $("#divknxsecure").show(); } else { $("#divknxsecure").hide(); } if ($("#node-input-server").val() !== "_ADD_" && $("#node-input-server").val() !== '') { refreshSecureGAs(); $.getJSON("knxUltimateDpts?serverId=" + $("#node-input-server").val() + "&_=" + new Date().getTime(), (data) => { $("#node-input-dpt").empty(); data.forEach(dpt => { $("#node-input-dpt").append($("<option></option>") .attr("value", dpt.value) .text(dpt.text)) }); $("#node-input-dpt").val(node.dpt); // Load help sample knxUltimateDptsGetHelp(node.dpt, true); }) } // Add write and response as default for existing nodes like was default before if (node.notifywrite === undefined) { node.notifywrite = true node.notifyresponse = true $("#node-input-notifywrite").prop("checked", true) $("#node-input-notifyresponse").prop("checked", true) } // Add Write as default for existing clients output if (node.outputtype === undefined) { node.outputtype = "write" $("#node-input-outputtype").val("write") } $("#node-input-notifyreadrequest").on('change', function () { if ($("#node-input-notifyreadrequest").is(":checked")) { if ($("#node-input-setTopicType").val() === "listenAllGA") { } else { $("#divnotifyreadrequestautoreact").show(); } } else { $("#divnotifyreadrequestautoreact").hide(); } }) $("#node-input-periodicSend").on('change', function () { if ($("#node-input-periodicSend").is(":checked")) { $("#divPeriodicSendInterval").show() } else { $("#divPeriodicSendInterval").hide() } }) // Set the group address type if (node.setTopicType === undefined) { node.setTopicType = 'str'; $("#node-input-setTopicType").val('str'); } // KNX Function helper: search for a GA and devicename // ---------------------------------------------------------- try { $("#node-input-knxFunctionHelperGAList").autocomplete('destroy'); $("#node-input-knxFunctionHelperGAList").removeClass(); // Rimuove eventuali classi aggiunte dall'autocompletamento } catch (error) { } $("#node-input-knxFunctionHelperGAList").off(); // Rimuovi tutti gli eventi associati $("#node-input-knxFunctionHelperGAList").val(''); // Pulisce il valore del campo di input, se necessario $("#node-input-knxFunctionHelperGAList").autocomplete({ minLength: 0, source: function (request, response) { $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id + "&" + { _: new Date().getTime() }, (data) => { response($.map(data, function (value, key) { var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt); if (htmlUtilsfullCSVSearch(sSearch, request.term)) { return { label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display value: value.ga + " " + value.devicename // Value } } else { return null; } })); }); }, select: function (event, ui) { } }).focus(function () { $(this).autocomplete('search', $(this).val() + 'exactmatch'); }); // ---------------------------------------------------------- $("#node-input-setTopicType").on('change', function () { try { $("#divDatapointSelection").show(); $("#node-input-topic").show(); $("#node-input-topic").autocomplete('destroy'); $("#node-input-topic").off(); // Rimuovi tutti gli eventi associati $("#node-input-topic").val(''); // Pulisce il valore del campo di input, se necessario $("#node-input-topic").prop('disabled', false); // Assicura che il campo non sia disabilitato $("#node-input-topic").removeClass(); // Rimuove eventuali classi aggiunte dall'autocompletamento } catch (error) { } if ($("#node-input-setTopicType").val() === 'str') { $("#node-input-topic").prop('placeholder', 'Select your GA'); // Autocomplete suggestion with ETS csv File $("#node-input-topic").autocomplete({ minLength: 0, source: function (request, response) { $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id + "&" + { _: new Date().getTime() }, (data) => { response($.map(data, function (value, key) { var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt); if (htmlUtilsfullCSVSearch(sSearch, request.term)) { const isSecure = secureGAs.has(value.ga); return { label: value.ga + " # " + value.devicename + " # " + value.dpt, value: value.ga, ga: value.ga, devicename: value.devicename, dpt: value.dpt, isSecure: isSecure } } else { return null; } })); }); }, select: function (event, ui) { // Sets Datapoint and device name automatically var sDevName = ui.item.devicename || ''; try { sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim(); } catch (error) { } $('#node-input-name').val(sDevName); var optVal = $("#node-input-dpt option:contains('" + (ui.item.dpt || '').trim() + "')").attr('value'); var $dptSelect = $("#node-input-dpt"); if (optVal !== undefined && optVal !== null) { $dptSelect.val(optVal).trigger('change'); } else { $dptSelect.trigger('change'); } // Persist secure flag for dynamic icon try { $("#node-input-gaSecure").val(ui.item.isSecure ? 'true' : 'false'); } catch (e) { } // Toggle shield near GA input try { if (ui.item.isSecure) { $("#gaSecureShield").show(); } else { $("#gaSecureShield").hide(); } } catch (e) { } } }).focus(function () { $(this).autocomplete('search', $(this).val() + 'exactmatch'); }).autocomplete("instance")._renderItem = function (ul, item) { const isSecure = !!item.isSecure; const colorStyle = isSecure ? 'color: green;' : ''; const shield = isSecure ? '<i class="fa fa-shield"></i> ' : ''; const label = `${shield}${item.ga || ''} # ${item.devicename || ''} # ${item.dpt || ''}`; return $("<li>").append(`<div style="${colorStyle}">${label}</div>`).appendTo(ul); }; $("#divDatapointSelection").show(); $("#divNode-input-initialread").show(); $("#divOutputRBE").show() $("#divInputRBE").show() $("#divPeriodicSend").show() if ($("#node-input-periodicSend").is(':checked')) { $("#divPeriodicSendInterval").show() } else { $("#divPeriodicSendInterval").hide() } } else if ($("#node-input-setTopicType").val() === 'listenAllGA') { $("#node-input-topic").hide(); $("#divDatapointSelection").hide() $("#divOutputRBE").hide() $("#node-input-outputRBE").val("false") $("#divInputRBE").hide() $("#node-input-inputRBE").val("false") $("#divPeriodicSend").hide() $("#divPeriodicSendInterval").hide() $("#node-input-periodicSend").prop('checked', false) $("#divnotifyreadrequestautoreact").hide(); $("#divTopic").hide() $("#divNode-input-initialread").hide(); // Call a fake datapoint to load a sample "Universal Node" knxUltimateDptsGetHelp("0.000", true); // 15/09/2020 Supergiovane, load sample help } else { // 15/09/2020 Supergiovane, load the help sample of the current datapoint knxUltimateDptsGetHelp($("#node-input-dpt").val(), false); // 15/09/2020 Supergiovane, load sample help $("#divOutputRBE").show() $("#divInputRBE").show() $("#divTopic").show() $("#divPeriodicSend").show() if ($("#node-input-periodicSend").is(':checked')) { $("#divPeriodicSendInterval").show() } else { $("#divPeriodicSendInterval").hide() } if ($("#node-input-notifyreadrequest").is(":checked")) { $("#divnotifyreadrequestautoreact").show(); } else { $("#divnotifyreadrequestautoreact").hide(); } $("#divNode-input-initialread").show(); $("#node-input-topic").prop('placeholder', $("#node-input-setTopicType").val()); if ($("#node-input-topic").val() === '') $("#node-input-topic").val('MyVariable'); $("#divDatapointSelection").hide(); } }); // Hide or show the GA and DPT fields if Notify on all Group Addresses is checked if (oNodeServer !== undefined && oNodeServer !== null) { if (oNodeServer.csv !== undefined && oNodeServer.csv !== "") { // There is a ETS csv file, show the init read option $("#divNode-input-initialread").show() } else { // 25/10/2019 Warn user that the node will node encode/decode datagram, if Listen All GA's if the config node doesn't contain the csv if ($("#node-input-setTopicType").val() === 'listenAllGA') { // There isn't a ETS csv file, hide and deselect the init read option $("#divNode-input-initialread").hide(); $("#node-input-initialread").val(0); } else { $("#divNode-input-initialread").show() } } } else { $("#node-input-setTopicType").val('str') $("#divTopic").show() $("#divDatapointSelection").show() $("#divOutputRBE").show() $("#divInputRBE").show() $("#divNode-input-initialread").show() } // ***************************** setTimeout(() => { if ($("#node-input-setTopicType").val() === 'listenAllGA') { // Call a fake datapoint to load a sample "Universal Node" knxUltimateDptsGetHelp("0.000", true); // 15/09/2020 Supergiovane, load sample help