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.
897 lines (839 loc) • 36.2 kB
HTML
<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">
(function () {
let $tabs = null;
let $requiresBridgeElems = null;
let $knxSections = null;
let $deviceName = null;
let $refreshButton = null;
let $loadingIndicator = null;
let $enablePinsSelect = null;
let $outputInfo = null;
let $modeHiddenInput = null;
let $dptSceneSelect = null;
let $dptSceneStatusSelect = null;
let $dptSceneMultiSelect = null;
let $sceneValueRow = null;
let cachedScenes = [];
let defaultDevicePlaceholder = '';
let showingNoDevicesPlaceholder = false;
let currentNode = null;
const EMPTY_SERVER_VALUES = new Set(['', 'none', '_add_', '__none__', '__null__', 'null', 'undefined']);
const ensureVerticalTabsStyle = () => {
if ($('#knxUltimateHueSceneVerticalTabs').length) return;
const style = `
<style id="knxUltimateHueSceneVerticalTabs">
.hue-vertical-tabs.ui-tabs.ui-widget.ui-widget-content.ui-corner-all {
display: flex;
border: none;
padding: 0;
}
.hue-vertical-tabs > ul.ui-tabs-nav {
flex: 0 0 160px;
border-right: 1px solid #ccc;
border-left: none;
border-top: none;
border-bottom: none;
padding: 0.5em 0.3em;
}
.hue-vertical-tabs > ul.ui-tabs-nav li {
float: none;
width: 100%;
margin: 0 0 2px 0;
}
.hue-vertical-tabs > ul.ui-tabs-nav li a {
display: block;
width: 100%;
white-space: nowrap;
position: relative;
border-bottom: none !important;
}
.hue-vertical-tabs > ul.ui-tabs-nav li.ui-tabs-active {
border-bottom: none !important;
}
.hue-vertical-tabs > ul.ui-tabs-nav li.ui-tabs-active a::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 50%;
height: 3px;
background: currentColor;
}
.hue-vertical-tabs .ui-tabs-panel {
flex: 1;
padding: 0.8em 1em;
box-sizing: border-box;
border: none;
background: transparent;
}
.hue-vertical-tabs .form-row {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: 4px;
}
.hue-vertical-tabs .node-input-rule-container-row {
align-items: stretch;
}
.hue-vertical-tabs #node-input-rule-container {
width: 100%;
min-height: 200px;
}
.hue-vertical-tabs .hue-form-tip {
display: flex;
align-items: center;
gap: 6px;
width: 100%;
margin-left: 0 !important;
max-width: none;
color: #1b7d33;
margin-bottom: 6px;
padding: 6px 10px;
box-sizing: border-box;
}
.hue-vertical-tabs .hue-form-tip .fa {
color: forestgreen;
flex: 0 0 auto;
}
.hue-vertical-tabs .hue-form-tip span {
flex: 1 1 auto;
min-width: 0;
white-space: normal;
}
</style>`;
$('head').append(style);
};
const detachHandlers = () => {
$('#node-input-server').off('.knxUltimateHueScene');
$('#node-input-serverHue').off('.knxUltimateHueScene');
if ($deviceName) {
$deviceName.off('.knxUltimateHueScene');
if ($deviceName.data('ui-autocomplete')) {
try { $deviceName.autocomplete('destroy'); } catch (error) { /* empty */ }
}
}
if ($refreshButton) {
$refreshButton.off('.knxUltimateHueScene');
}
['#node-input-GAscene', '#node-input-GAsceneStatus', '#node-input-GAsceneMulti'].forEach((selector) => {
const $input = $(selector);
if ($input.length) {
$input.off('.knxUltimateHueScene');
if ($input.data('ui-autocomplete')) {
try { $input.autocomplete('destroy'); } catch (error) { /* empty */ }
}
}
});
if ($enablePinsSelect) {
$enablePinsSelect.off('.knxUltimateHueScene');
}
if ($tabs && $tabs.data('ui-tabs')) {
try { $tabs.tabs('destroy'); } catch (error) { /* empty */ }
}
};
const ensureConfigSelection = (selector) => {
const $select = $(selector);
if (!$select.length) return;
if ($select.val() !== '_ADD_') return;
try { $select.prop('selectedIndex', 0); } catch (error) { /* empty */ }
};
const resolveServerId = (value) => {
if (value === undefined || value === null) return null;
if (value === false) return null;
if (typeof value === 'string') {
const trimmed = value.trim();
if (trimmed === '') return null;
if (EMPTY_SERVER_VALUES.has(trimmed.toLowerCase())) return null;
return trimmed;
}
const asString = String(value).trim();
if (asString === '' || EMPTY_SERVER_VALUES.has(asString.toLowerCase())) return null;
return value;
};
const getKnxServer = (allowFallback = true) => {
const resolved = resolveServerId($('#node-input-server').val());
if (resolved) return RED.nodes.node(resolved);
if (!allowFallback) return null;
const fallback = resolveServerId(currentNode ? currentNode.server : null);
return fallback ? RED.nodes.node(fallback) : null;
};
const getHueServer = (allowFallback = true) => {
const resolved = resolveServerId($('#node-input-serverHue').val());
if (resolved) return RED.nodes.node(resolved);
if (!allowFallback) return null;
const fallback = resolveServerId(currentNode ? currentNode.serverHue : null);
return fallback ? RED.nodes.node(fallback) : null;
};
const hasHueSelection = () => {
const resolved = resolveServerId($('#node-input-serverHue').val());
if (resolved) return true;
if ($('#node-input-serverHue').length) return false;
return resolveServerId(currentNode ? currentNode.serverHue : null) !== null;
};
const hasKnxSelection = () => {
const resolved = resolveServerId($('#node-input-server').val());
if (resolved) return true;
if ($('#node-input-server').length) return false;
return resolveServerId(currentNode ? currentNode.server : null) !== null;
};
const normalizePinsValue = (value) => {
if (value === undefined || value === null) return 'yes';
if (value === true || value === 'true') return 'yes';
if (value === false || value === 'false') return 'no';
return value === 'no' ? 'no' : 'yes';
};
const applyNoDevicesPlaceholder = (hasDevices) => {
if (!$deviceName) return;
const noDevicesText = RED._('node-red-contrib-knx-ultimate/knxUltimateHueScene:knxUltimateHueScene.no_scenes');
if (!hasDevices) {
if (!showingNoDevicesPlaceholder) {
$deviceName.attr('placeholder', noDevicesText);
showingNoDevicesPlaceholder = true;
}
} else if (showingNoDevicesPlaceholder) {
$deviceName.attr('placeholder', defaultDevicePlaceholder);
showingNoDevicesPlaceholder = false;
}
};
const filterScenes = (list, term) => {
const matcher = (term || '').replace(/exactmatch/gi, '').trim().toLowerCase();
return list
.filter((item) => (item.name || '').toLowerCase().includes(matcher))
.map((item) => ({
hueDevice: item.id,
value: item.name,
}));
};
const fetchScenes = (hueServer, term, reply, { forceRefresh = false } = {}) => {
if (!hueServer) {
applyNoDevicesPlaceholder(false);
reply([]);
return;
}
if (!forceRefresh && cachedScenes.length > 0) {
applyNoDevicesPlaceholder(cachedScenes.length > 0);
reply(filterScenes(cachedScenes, term));
return;
}
if ($loadingIndicator) $loadingIndicator.show();
const refreshQuery = forceRefresh ? '&forceRefresh=1' : '';
$.getJSON(`KNXUltimateGetResourcesHUE?rtype=scene&serverId=${encodeURIComponent(hueServer.id)}${refreshQuery}&_=${Date.now()}`, (data) => {
const devices = Array.isArray(data) ? data : (Array.isArray(data?.devices) ? data.devices : []);
cachedScenes = devices.map((value) => ({
id: value.id || value.rid,
name: value.name || value.metadata?.name || '',
}));
if (currentNode) currentNode._cachedSceneDevices = cachedScenes;
applyNoDevicesPlaceholder(cachedScenes.length > 0);
reply(filterScenes(cachedScenes, term));
}).always(() => {
if ($loadingIndicator) $loadingIndicator.hide();
}).fail(() => {
cachedScenes = [];
if (currentNode) currentNode._cachedSceneDevices = cachedScenes;
applyNoDevicesPlaceholder(false);
reply([]);
});
};
const populateSceneValues = (node) => {
const $valSelect = $('#node-input-valscene');
if (!$valSelect.length) return;
$valSelect.empty();
for (let index = 1; index <= 64; index += 1) {
$valSelect.append($('<option></option>').attr('value', index).text(`Scene ${index}`));
}
const target = node?.valscene || '1';
$valSelect.val(target);
};
const toggleSceneValueVisibility = () => {
if (!$dptSceneSelect || !$sceneValueRow) return;
const current = $dptSceneSelect.val();
if (!current) {
$sceneValueRow.hide();
return;
}
if (current.startsWith('1.')) {
$sceneValueRow.hide();
} else {
$sceneValueRow.show();
}
};
const loadDPTOptions = (serverCandidate, nodeRef) => {
const server = (() => {
const resolved = resolveServerId(serverCandidate);
if (resolved) return RED.nodes.node(resolved);
return getKnxServer(false);
})();
if (!server) return;
const selects = [
{ element: $dptSceneSelect, filter: (value) => value.startsWith('1.') || value.startsWith('18.'), target: nodeRef?.dptscene },
{ element: $dptSceneStatusSelect, filter: (value) => value.startsWith('1.'), target: nodeRef?.dptsceneStatus },
{ element: $dptSceneMultiSelect, filter: (value) => value.startsWith('18.'), target: nodeRef?.dptsceneMulti },
];
selects.forEach(({ element }) => { if (element) element.empty(); });
$.getJSON(`knxUltimateDpts?serverId=${server.id}`, (data) => {
data.forEach((dpt) => {
selects.forEach(({ element, filter }) => {
if (!element || !filter(dpt.value)) return;
element.append($('<option></option>').attr('value', dpt.value).text(dpt.text));
});
});
selects.forEach(({ element, target }) => {
if (!element || !element.children().length) return;
const resolved = target && target !== '' ? target : element.children().first().attr('value');
if (resolved !== undefined) element.val(resolved);
});
toggleSceneValueVisibility();
});
};
const setupKnxAutocomplete = (options) => {
const {
inputSelector,
nameSelector,
dptSelector,
allowedPrefixes,
} = options;
const $input = $(inputSelector);
if (!$input.length) return;
const $name = nameSelector ? $(nameSelector) : null;
const $dptSelect = dptSelector ? $(dptSelector) : null;
if ($input.data('ui-autocomplete')) {
try { $input.autocomplete('destroy'); } catch (error) { /* empty */ }
}
$input.autocomplete({
minLength: 0,
source(request, response) {
const server = getKnxServer(false);
if (!server) {
response([]);
return;
}
$.getJSON(`knxUltimatecsv?nodeID=${server.id}`, (data) => {
const matches = [];
data.forEach((value) => {
if (!value.dpt) return;
if (Array.isArray(allowedPrefixes) && !allowedPrefixes.some((prefix) => value.dpt.startsWith(prefix))) return;
const sSearch = `${value.ga} (${value.devicename}) DPT${value.dpt}`;
if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
matches.push({
label: `${value.ga} # ${value.devicename} # ${value.dpt}`,
value: value.ga,
});
}
});
response(matches);
});
},
select(event, ui) {
if ($name) {
let sDevName = ui.item.label.split('#')[1]?.trim() || '';
try {
sDevName = sDevName.substr(sDevName.indexOf(')') + 1).trim();
} catch (error) { /* empty */ }
$name.val(sDevName);
}
if ($dptSelect) {
const dptLabel = ui.item.label.split('#')[2]?.trim();
const optVal = dptLabel ? $dptSelect.find(`option:contains('${dptLabel}')`).attr('value') : undefined;
if (optVal !== undefined && optVal !== null) {
$dptSelect.val(optVal).trigger('change');
} else {
$dptSelect.trigger('change');
}
}
},
});
$input.on('focus.knxUltimateHueScene', function () {
$(this).autocomplete('search', `${$(this).val()}exactmatch`);
});
const server = getKnxServer(false);
if (server && server.id) {
try { KNX_enableSecureFormatting($input, server.id); } catch (error) { /* empty */ }
}
};
const setupEditableList = (node) => {
const $list = $('#node-input-rule-container');
if (!$list.length) return;
if ($list.data('editableList')) {
try { $list.editableList('destroy'); } catch (error) { /* empty */ }
}
const resizeRule = () => { /* empty */ };
$list.editableList({
addButton: true,
removable: true,
sortable: true,
scrollOnAdd: true,
addItem(container, i, opt) {
if (!opt.hasOwnProperty('r')) opt.r = {};
const rule = opt.r;
const row = $('<div class="form-row"/>').appendTo(container);
const rowRuleKNXSceneNumber = $('<select/>', {
class: 'rowRuleKNXSceneNumber',
style: 'width:25%; margin-left:5px; text-align:left;',
}).appendTo(row);
const rowRuleHUESceneName = $('<input/>', {
class: 'rowRuleHUESceneName',
type: 'text',
placeholder: RED._('node-red-contrib-knx-ultimate/knxUltimateHueScene:knxUltimateHueScene.multi_scene_placeholder'),
style: 'width:45%; margin-left:5px; text-align:left;',
}).appendTo(row);
const rowRuleHUESceneID = $('<input/>', {
class: 'rowRuleHUESceneID',
type: 'hidden',
}).appendTo(row);
const rowRuleRecallAs = $('<select/>', {
class: 'rowRuleRecallAs',
style: 'width:25%; margin-left:5px; text-align:left;',
}).appendTo(row);
const finalspan = $('<span/>').appendTo(row);
finalspan.append('<span class="node-input-rule-index"></span> ');
for (let index = 1; index <= 64; index += 1) {
rowRuleKNXSceneNumber.append(
$('<option></option>')
.val(index)
.text(node._('knxUltimateHueScene.knx_scene_n') + index.toString()),
);
}
rowRuleRecallAs.append(
$('<option></option>').val('active').text(node._('knxUltimateHueScene.recall_active')),
);
rowRuleRecallAs.append(
$('<option></option>').val('dynamic_palette').text(node._('knxUltimateHueScene.recall_dynamic')),
);
rowRuleRecallAs.append(
$('<option></option>').val('static').text(node._('knxUltimateHueScene.recall_static')),
);
rowRuleKNXSceneNumber.val(rule.rowRuleKNXSceneNumber);
rowRuleRecallAs.val(rule.rowRuleRecallAs);
rowRuleHUESceneName.val(rule.rowRuleHUESceneName);
rowRuleHUESceneID.val(rule.rowRuleHUESceneID);
rowRuleHUESceneName.autocomplete({
minLength: 0,
source(request, response) {
const hueServer = getHueServer(false);
if (!hueServer) { response([]); return; }
fetchScenes(hueServer, request.term, response);
},
select(event, ui) {
rowRuleHUESceneID.val(ui.item.hueDevice);
},
});
rowRuleHUESceneName.on('focus.knxUltimateHueScene', function () {
$(this).autocomplete('search', `${$(this).val()}exactmatch`);
});
},
removeItem() {},
resizeItem: resizeRule,
sortItems() {},
});
$list.editableList('empty');
if (Array.isArray(node.rules)) {
node.rules.forEach((rule, index) => {
$list.editableList('addItem', { r: rule, i: index });
});
}
};
const updateTabsVisibility = () => {
if (!$tabs) return;
const hueSelected = hasHueSelection();
const knxSelected = hasKnxSelection();
if ($requiresBridgeElems) {
if (hueSelected) {
$requiresBridgeElems.show();
} else {
$requiresBridgeElems.hide();
}
}
if (hueSelected && knxSelected) {
$tabs.show();
$tabs.tabs('refresh');
} else {
$tabs.hide();
}
if ($outputInfo) {
if (knxSelected) {
$outputInfo.hide();
} else {
$outputInfo.show();
}
}
if ($enablePinsSelect && $enablePinsSelect.length) {
const desiredPins = knxSelected ? 'no' : 'yes';
if ($enablePinsSelect.val() !== desiredPins) {
$enablePinsSelect.val(desiredPins).trigger('change');
}
}
};
const updateKnxVisibility = () => {
const knxSelected = hasKnxSelection();
if ($knxSections) {
if (knxSelected) {
$knxSections.show();
} else {
$knxSections.hide();
}
}
updateTabsVisibility();
};
const updatePinsState = () => {
if (!$enablePinsSelect || !currentNode) return;
const val = normalizePinsValue($enablePinsSelect.val());
currentNode.enableNodePINS = val;
currentNode.outputs = val === 'yes' ? 1 : 0;
currentNode.inputs = currentNode.outputs;
};
RED.nodes.registerType('knxUltimateHueScene', {
category: 'KNX Ultimate',
color: '#C0C7E9',
defaults: {
server: { type: 'knxUltimate-config', required: false },
serverHue: { type: 'hue-config', required: true },
name: { value: '' },
namescene: { value: '' },
GAscene: { value: '' },
dptscene: { value: '' },
valscene: { value: '1' },
namesceneStatus: { value: '' },
GAsceneStatus: { value: '' },
dptsceneStatus: { value: '' },
enableNodePINS: { value: 'no' },
outputs: { value: 0 },
inputs: { value: 0 },
hueDevice: { value: '' },
hueSceneRecallType: { value: 'active' },
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() {
if (this.selectedModeTabNumber === undefined) return this.name;
if (Number(this.selectedModeTabNumber) === 0) return this.name || this.namescene || 'Hue Scene';
if (Number(this.selectedModeTabNumber) === 1) return this.namesceneMulti || this.name || 'Hue Scene';
return this.name || 'Hue Scene';
},
paletteLabel: 'Hue Scene',
oneditprepare() {
const node = this;
try {
onEditPrepareCore.call(node);
} catch (error) {
try {
console.error('knxUltimateHueScene oneditprepare error', error);
RED.notify(`Hue Scene editor error: ${error.message || error}`, { type: 'error', timeout: 8000 });
} catch (notifyError) {
console.error('knxUltimateHueScene notify failure', notifyError);
}
throw error;
}
},
oneditsave() {
try { onEditSaveCore.call(this); } catch (error) {
console.error('knxUltimateHueScene oneditsave error', error);
throw error;
}
},
oneditcancel() {
try { onEditCancelCore.call(this); } catch (error) {
console.error('knxUltimateHueScene oneditcancel error', error);
throw error;
}
},
oneditresize() {},
});
function onEditPrepareCore() {
const node = this;
try { RED.sidebar.show('help'); } catch (error) { /* empty */ }
currentNode = node;
ensureConfigSelection('#node-input-serverHue');
ensureVerticalTabsStyle();
$tabs = $('#hue-scene-tabs');
$requiresBridgeElems = $('.hue-requires-bridge');
$knxSections = $('.hue-knx-section');
$deviceName = $('#node-input-name');
$refreshButton = $('.hue-refresh-devices');
$loadingIndicator = $('.hue-devices-loading');
$enablePinsSelect = $('#node-input-enableNodePINS');
$outputInfo = $('.hue-output-info');
$modeHiddenInput = $('#node-input-selectedModeTabNumber');
$dptSceneSelect = $('#node-input-dptscene');
$dptSceneStatusSelect = $('#node-input-dptsceneStatus');
$dptSceneMultiSelect = $('#node-input-dptsceneMulti');
$sceneValueRow = $('#divValScene');
cachedScenes = Array.isArray(node._cachedSceneDevices) ? node._cachedSceneDevices : [];
node._cachedSceneDevices = cachedScenes;
defaultDevicePlaceholder = $deviceName.attr('placeholder') || '';
showingNoDevicesPlaceholder = false;
applyNoDevicesPlaceholder(cachedScenes.length > 0);
populateSceneValues(node);
$tabs.addClass('hue-vertical-tabs');
const initialTab = Number(node.selectedModeTabNumber || 0);
if ($modeHiddenInput) {
$modeHiddenInput.val(Number.isNaN(initialTab) ? 0 : initialTab);
}
$tabs.tabs({
activate(event, ui) {
const index = ui.newTab.index();
if ($modeHiddenInput) $modeHiddenInput.val(index);
node.selectedModeTabNumber = index;
},
active: Number.isNaN(initialTab) ? 0 : initialTab,
});
$tabs.find('li').removeClass('ui-corner-top').addClass('ui-corner-left');
$('#node-input-hueSceneRecallType').val(node.hueSceneRecallType || 'active');
const initialServerDomValue = $('#node-input-server').val();
const initialServerId = initialServerDomValue === undefined ? node.server : initialServerDomValue;
loadDPTOptions(initialServerId, node);
setupKnxAutocomplete({
inputSelector: '#node-input-GAscene',
nameSelector: '#node-input-namescene',
dptSelector: '#node-input-dptscene',
allowedPrefixes: ['1.', '18.'],
});
setupKnxAutocomplete({
inputSelector: '#node-input-GAsceneStatus',
nameSelector: '#node-input-namesceneStatus',
dptSelector: '#node-input-dptsceneStatus',
allowedPrefixes: ['1.'],
});
setupKnxAutocomplete({
inputSelector: '#node-input-GAsceneMulti',
nameSelector: '#node-input-namesceneMulti',
dptSelector: '#node-input-dptsceneMulti',
allowedPrefixes: ['18.'],
});
$('#node-input-dptscene').on('change.knxUltimateHueScene', toggleSceneValueVisibility);
toggleSceneValueVisibility();
setupEditableList(node);
if ($deviceName) {
$deviceName.autocomplete({
minLength: 0,
source(request, response) {
const hueServer = getHueServer(false);
if (!hueServer) { response([]); return; }
fetchScenes(hueServer, request.term, response);
},
select(event, ui) {
$('#node-input-hueDevice').val(ui.item.hueDevice);
updateTabsVisibility();
},
});
$deviceName.on('focus.knxUltimateHueScene', function () {
$(this).autocomplete('search', `${$(this).val()}exactmatch`);
});
}
if ($refreshButton) {
$refreshButton.on('click.knxUltimateHueScene', () => {
cachedScenes = [];
if (currentNode) currentNode._cachedSceneDevices = cachedScenes;
const hueServer = getHueServer(false);
if (!hueServer) return;
fetchScenes(hueServer, '', () => {
if ($deviceName) {
$deviceName.autocomplete('search', `${$deviceName.val()}exactmatch`);
}
}, { forceRefresh: true });
});
}
if ($enablePinsSelect) {
$enablePinsSelect.val(normalizePinsValue(node.enableNodePINS));
$enablePinsSelect.on('change.knxUltimateHueScene', updatePinsState);
updatePinsState();
}
$('#node-input-server').on('change.knxUltimateHueScene', function () {
const serverId = $(this).val();
loadDPTOptions(serverId, node);
setupKnxAutocomplete({
inputSelector: '#node-input-GAscene',
nameSelector: '#node-input-namescene',
dptSelector: '#node-input-dptscene',
allowedPrefixes: ['1.', '18.'],
});
setupKnxAutocomplete({
inputSelector: '#node-input-GAsceneStatus',
nameSelector: '#node-input-namesceneStatus',
dptSelector: '#node-input-dptsceneStatus',
allowedPrefixes: ['1.'],
});
setupKnxAutocomplete({
inputSelector: '#node-input-GAsceneMulti',
nameSelector: '#node-input-namesceneMulti',
dptSelector: '#node-input-dptsceneMulti',
allowedPrefixes: ['18.'],
});
updateKnxVisibility();
});
$('#node-input-serverHue').on('change.knxUltimateHueScene', () => {
cachedScenes = [];
if (currentNode) currentNode._cachedSceneDevices = cachedScenes;
if ($deviceName) {
$deviceName.val('');
$('#node-input-hueDevice').val('');
applyNoDevicesPlaceholder(false);
}
updateTabsVisibility();
});
updateKnxVisibility();
}
function onEditSaveCore() {
try { RED.sidebar.show('info'); } catch (error) { /* empty */ }
detachHandlers();
cachedScenes = cachedScenes || [];
const pinsSelection = $enablePinsSelect ? normalizePinsValue($enablePinsSelect.val()) : 'no';
this.enableNodePINS = pinsSelection;
this.outputs = pinsSelection === 'yes' ? 1 : 0;
this.inputs = this.outputs;
this._cachedSceneDevices = cachedScenes;
if ($modeHiddenInput) {
const idx = parseInt($modeHiddenInput.val(), 10);
this.selectedModeTabNumber = Number.isNaN(idx) ? 0 : idx;
}
const self = this;
const rules = $('#node-input-rule-container').editableList('items');
self.rules = [];
rules.each(function () {
const rule = $(this);
const rowRuleKNXSceneNumber = rule.find('.rowRuleKNXSceneNumber').val();
const rowRuleHUESceneName = rule.find('.rowRuleHUESceneName').val();
const rowRuleHUESceneID = rule.find('.rowRuleHUESceneID').val();
const rowRuleRecallAs = rule.find('.rowRuleRecallAs').val();
self.rules.push({
rowRuleKNXSceneNumber,
rowRuleHUESceneName,
rowRuleHUESceneID,
rowRuleRecallAs,
});
});
currentNode = null;
}
function onEditCancelCore() {
try { RED.sidebar.show('info'); } catch (error) { /* empty */ }
detachHandlers();
cachedScenes = [];
if (currentNode) currentNode._cachedSceneDevices = cachedScenes;
currentNode = null;
}
}());
</script>
<script type="text/html" data-template-name="knxUltimateHueScene">
<input type="hidden" id="node-input-selectedModeTabNumber">
<div class="form-row">
<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" />
<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>
<div class="form-row hue-requires-bridge">
<label for="node-input-name">
<i class="fa fa-play-circle"></i> <span data-i18n="knxUltimateHueScene.hue_scene"></span>
</label>
<input type="text" id="node-input-name" placeholder="Hue scene" style="flex:1 1 240px; min-width:240px; max-width:240px;">
<button type="button" class="red-ui-button hue-refresh-devices" style="margin-left:6px; color:#1b7d33; border-color:#1b7d33;">
<i class="fa fa-sync"></i>
</button>
<span class="hue-devices-loading" style="margin-left:6px; display:none; color:#1b7d33;">
<i class="fa fa-circle-notch fa-spin"></i>
</span>
</div>
<div id="hue-scene-tabs">
<ul>
<li><a href="#hue-scene-tab-single"><i class="fa fa-map"></i> <span data-i18n="knxUltimateHueScene.tabs.single"></span></a></li>
<li><a href="#hue-scene-tab-multi"><i class="fa fa-list"></i> <span data-i18n="knxUltimateHueScene.tabs.multi"></span></a></li>
<li><a href="#hue-scene-tab-behaviour"><i class="fa fa-gear"></i> <span data-i18n="knxUltimateHueScene.tabs.behaviour"></span></a></li>
</ul>
<div id="hue-scene-tab-single">
<div class="form-tips hue-form-tip hue-requires-bridge">
<i class="fa fa-circle-info"></i>
<span data-i18n="knxUltimateHueScene.single_info"></span>
</div>
<div class="form-row hue-requires-bridge">
<label for="node-input-hueSceneRecallType" style="width:220px;">
<i class="fa fa-bolt"></i> <span data-i18n="knxUltimateHueScene.recall_as"></span>
</label>
<select id="node-input-hueSceneRecallType" style="width:200px;">
<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>
<div class="form-tips hue-form-tip hue-knx-section">
<i class="fa fa-circle-info"></i>
<span data-i18n="knxUltimateHueScene.mapping_info"></span>
</div>
<div class="form-row hue-knx-section">
<label for="node-input-GAscene" style="width:70px;"><span data-i18n="common.ga"></span></label>
<input type="text" id="node-input-GAscene" placeholder="1/1/1" style="width:80px; text-align:left;">
<label for="node-input-dptscene" style="width:40px; text-align:right;"><span data-i18n="common.dpt"></span></label>
<select id="node-input-dptscene" style="width:130px;"></select>
<label for="node-input-namescene" style="width:50px; text-align:right;"><span data-i18n="common.name"></span></label>
<input type="text" id="node-input-namescene" style="flex:1 1 140px; min-width:120px; text-align:left;" placeholder="Scene recall">
</div>
<div class="form-row hue-knx-section" id="divValScene" style="display:none;">
<label for="node-input-valscene" style="width:70px;">#</label>
<select id="node-input-valscene" style="width:130px;"></select>
</div>
<div class="form-row hue-knx-section">
<label for="node-input-GAsceneStatus" style="width:70px;"><span data-i18n="knxUltimateHueScene.status_ga"></span></label>
<input type="text" id="node-input-GAsceneStatus" placeholder="1/1/1" style="width:80px; text-align:left;">
<label for="node-input-dptsceneStatus" style="width:40px; text-align:right;"><span data-i18n="common.dpt"></span></label>
<select id="node-input-dptsceneStatus" style="width:130px;"></select>
<label for="node-input-namesceneStatus" style="width:50px; text-align:right;"><span data-i18n="common.name"></span></label>
<input type="text" id="node-input-namesceneStatus" style="flex:1 1 140px; min-width:120px; text-align:left;" placeholder="Scene status">
</div>
</div>
<div id="hue-scene-tab-multi">
<div class="form-tips hue-form-tip hue-knx-section">
<i class="fa fa-circle-info"></i>
<span data-i18n="knxUltimateHueScene.multi_info"></span>
</div>
<div class="form-row hue-knx-section">
<label for="node-input-GAsceneMulti" style="width:70px;"><span data-i18n="common.ga"></span></label>
<input type="text" id="node-input-GAsceneMulti" placeholder="1/1/1" style="width:80px; text-align:left;">
<label for="node-input-dptsceneMulti" style="width:40px; text-align:right;"><span data-i18n="common.dpt"></span></label>
<select id="node-input-dptsceneMulti" style="width:130px;"></select>
<label for="node-input-namesceneMulti" style="width:50px; text-align:right;"><span data-i18n="common.name"></span></label>
<input type="text" id="node-input-namesceneMulti" style="flex:1 1 140px; min-width:120px; text-align:left;" placeholder="Multi-scene">
</div>
<div class="form-row hue-knx-section">
<label style="width:100%;"><i class="fa fa-code-fork"></i> <span data-i18n="knxUltimateHueScene.scene_selector"></span></label>
</div>
<div class="form-row hue-knx-section node-input-rule-container-row">
<ol id="node-input-rule-container"></ol>
</div>
</div>
<div id="hue-scene-tab-behaviour">
<div class="form-tips hue-form-tip">
<i class="fa fa-circle-info"></i>
<span data-i18n="knxUltimateHueScene.behaviour_info"></span>
</div>
<div class="form-row">
<label for="node-input-enableNodePINS" style="width:220px;">
<i class="fa fa-code"></i> <span data-i18n="knxUltimateHueScene.node_pins"></span>
</label>
<select id="node-input-enableNodePINS" style="width:200px;">
<option value="yes" data-i18n="knxUltimateHueScene.node_pins_show"></option>
<option value="no" data-i18n="knxUltimateHueScene.node_pins_hide"></option>
</select>
</div>
</div>
</div>
<div class="form-tips hue-form-tip hue-output-info" style="display:none;">
<i class="fa fa-circle-info"></i>
<span data-i18n="knxUltimateHueScene.output_info"></span>
</div>
<input type="hidden" id="node-input-hueDevice">
</script>