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.
925 lines (865 loc) • 80.3 kB
HTML
<!-- <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: false },
buttonMode: { value: "read" },
buttonStaticValue: { value: "" },
buttonToggleInitial: { value: "false" }
},
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 () {
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." + 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." + key) || "Unable to send KNX command"), "error");
}
});
}
},
oneditprepare: function () {
// Go to the help panel
try {
RED.sidebar.show("help");
} 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(` <i class="fa fa-question-circle"></i> <a target="_blank" href="${data.helplink}"><u>${label}</u></a>`)
}
} else {
createEditor(noSampleText)
if (data.helplink) {
const labelWiki = translate('Wiki') || 'Open wiki'
helplinkContainer.html(` <i class="fa fa-question-circle"></i> <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 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");
$("#divknxFunctionHelperGAList").show();
} else {
$("#isETSFileLoaded").val("no");
$("#divknxFunctionHelperGAList").hide();
}
} catch (error) {
$("#isETSFileLoaded").val("no");
$("#divknxFunctionHelperGAList").hide();
}
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();
}
})
// 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()
} 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")
$("#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()
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
}
}, 300);
}
}
// 02/04/2020 Alert user about data type
// ###########################
$("#node-input-dpt").on("change", function (event) {
// Load help sample
knxUltimateDptsGetHelp(event.target.value, false);
});
// ###########################
// If KNX Function is populated, set focus on the KNX Function tab.
if (this.sendMsgToKNXCodeEditor.getValue() !== '' || this.receiveMsgFromKNXCodeEditor.getValue()) {
$("#tabs").tabs(
{ active: 1 }
);
}
// Backwart compatibility listenAllGA
if (node.listenallga === true) {
$("#node-input-setTopicType").val('listenAllGA');
}
// 19/02/2020 Used to alert the user if the CSV file has not been loaded and to get the server sooner als deploy
// ###########################
$("#node-input-server").change(function () {
checkUI();
});
// ###########################
// Show GA shield on dialog open based on saved secure flag
try {
const initialSecure = (node.gaSecure === true || node.gaSecure === 'true');
if (initialSecure) {
$("#gaSecureShield").show();
} else {
$("#gaSecureShield").hide();
}
} catch (e) { }
},
oneditsave: function () {
// Return to the info tab
try {
RED.sidebar.show("info");
} catch (error) { }
var node = this;
this.sendMsgToKNXCode = this.sendMsgToKNXCodeEditor.getValue();
this.receiveMsgFromKNXCode = this.receiveMsgFromKNXCodeEditor.getValue();
if ($("#node-input-setTopicType").val() === "listenAllGA") {
this.listenallga = true;
} else {
this.listenallga = false;
}
//this.propertyType = $("#node-input-property").typedInput('type');
// 19/02/2020 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 ($("#isETSFileLoaded").val() === "no") {
// Notify the user
if ($("#node-input-setTopicType").val() === 'listenAllGA') {
var checkResult = node._("knxUltimate.advanced.notify-NoETSFile");
var myNotification = RED.notify(checkResult,
{
modal: true,
fixed: true,
type: 'error',
buttons: [
{
text