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, KNX AI for diagnosticsand KNX routing between interfaces. Easy to use and highly configurable.

727 lines (680 loc) 30.9 kB
<script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script> <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script> <script type="text/javascript"> (function () { let ui = null; let cachedDevices = []; let defaultDevicePlaceholder = ''; let showingNoDevicesPlaceholder = false; let currentNode = null; const EMPTY_SERVER_VALUES = new Set(['', 'none', '_add_', '__none__', '__null__', 'null', 'undefined']); const coerceBool = (value, defaultValue = false) => { if (value === undefined || value === null) return defaultValue; if (typeof value === 'boolean') return value; if (typeof value === 'number') return value !== 0; if (typeof value === 'string') { const cleaned = value.trim().toLowerCase(); if (cleaned === '' || cleaned === '0' || cleaned === 'false' || cleaned === 'no' || cleaned === 'off') return false; if (cleaned === '1' || cleaned === 'true' || cleaned === 'yes' || cleaned === 'on') return true; } return Boolean(value); }; const ensureVerticalTabsStyle = () => { if ($('#knxUltimateHueButtonVerticalTabs').length) return; const style = ` <style id="knxUltimateHueButtonVerticalTabs"> .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 144px; 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 .form-row > dt { flex: 1 1 auto; margin: 0; } .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('.knxUltimateHueButton'); $('#node-input-serverHue').off('.knxUltimateHueButton'); if (ui?.deviceName) { ui.deviceName.off('.knxUltimateHueButton'); if (ui.deviceName.data('ui-autocomplete')) { try { ui.deviceName.autocomplete('destroy'); } catch (error) { /* empty */ } } } if (ui?.refreshButton) { ui.refreshButton.off('.knxUltimateHueButton'); } if (ui?.toggleCheckbox) { ui.toggleCheckbox.off('.knxUltimateHueButton'); } const autocompleteTargets = [ui?.gaShortRelease, ui?.gaShortReleaseStatus, ui?.gaRepeat]; autocompleteTargets.forEach(($input) => { if ($input) { $input.off('.knxUltimateHueButton'); if ($input.data('ui-autocomplete')) { try { $input.autocomplete('destroy'); } catch (error) { /* empty */ } } } }); if (ui?.switchSendInput && ui.switchSendInput.data('typedInput')) { try { ui.switchSendInput.typedInput('destroy'); } catch (error) { /* empty */ } } if (ui?.dimSendInput && ui.dimSendInput.data('typedInput')) { try { ui.dimSendInput.typedInput('destroy'); } catch (error) { /* empty */ } } }; const ensureConfigSelection = (selector) => { if ($(selector).val() !== '_ADD_') return; try { $(selector).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 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 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 applyNoDevicesPlaceholder = (hasDevices) => { if (!ui?.deviceName) return; if (hasDevices) { if (showingNoDevicesPlaceholder) { showingNoDevicesPlaceholder = false; ui.deviceName.attr('placeholder', defaultDevicePlaceholder); } return; } const message = RED._('node-red-contrib-knx-ultimate/knxUltimateHueButton:knxUltimateHueButton.no_devices'); showingNoDevicesPlaceholder = true; ui.deviceName.attr('placeholder', message); if ((ui.deviceName.val() || '').trim() === '') { ui.deviceName.val(''); } }; const filterDevices = (devices, term) => { const cleaned = (term || '').replace(/exactmatch/gi, '').trim(); return $.map(devices, (value) => { const sSearch = value.name; if (cleaned === '' || htmlUtilsfullCSVSearch(sSearch, cleaned)) { return { hueDevice: value.id, value: value.name, deviceObject: value.deviceObject || value, }; } return null; }); }; const fetchDevices = (hueServer, term, response, { forceRefresh = false } = {}) => { if (!hueServer) { applyNoDevicesPlaceholder(true); response([]); return; } if (!forceRefresh && cachedDevices.length > 0) { applyNoDevicesPlaceholder(cachedDevices.length > 0); response(filterDevices(cachedDevices, term)); return; } if (ui?.loadingIndicator) ui.loadingIndicator.show(); const refreshQuery = forceRefresh ? '&forceRefresh=1' : ''; $.getJSON(`KNXUltimateGetResourcesHUE?rtype=button&serverId=${encodeURIComponent(hueServer.id)}${refreshQuery}&_=${Date.now()}`, (data) => { const listCandidates = Array.isArray(data) ? data : (Array.isArray(data?.devices) ? data.devices : []); cachedDevices = listCandidates.map((value) => { if (value.deviceObject) return value; return { id: value.id || value.rid, name: value.name || value.metadata?.name || '', deviceObject: value, }; }); if (currentNode) currentNode._cachedButtonDevices = cachedDevices; applyNoDevicesPlaceholder(cachedDevices.length > 0); response(filterDevices(cachedDevices, term)); }).always(() => { if (ui?.loadingIndicator) ui.loadingIndicator.hide(); }).fail(() => { cachedDevices = []; if (currentNode) currentNode._cachedButtonDevices = cachedDevices; applyNoDevicesPlaceholder(false); response([]); }); }; const loadDptOptions = (serverId, nodeRef) => { if (!ui?.dptShortRelease || !ui?.dptShortReleaseStatus || !ui?.dptRepeat) return; ui.dptShortRelease.empty(); ui.dptShortReleaseStatus.empty(); ui.dptRepeat.empty(); const validId = resolveServerId(serverId); if (!validId) { return; } $.getJSON(`knxUltimateDpts?serverId=${validId}`, (data) => { const referenceNode = nodeRef || currentNode || {}; const targetShort = referenceNode.dptshort_release || '1.001'; const targetShortStatus = referenceNode.dptshort_releaseStatus || referenceNode.dptshort_release || '1.001'; const targetRepeat = referenceNode.dptrepeat || '3.007'; data.forEach((dpt) => { if (dpt.value.startsWith('1.')) { const option = $('<option></option>').attr('value', dpt.value).text(dpt.text); const optionStatus = option.clone(); ui.dptShortRelease.append(option); ui.dptShortReleaseStatus.append(optionStatus); } if (dpt.value.startsWith('3.007')) { ui.dptRepeat.append($('<option></option>').attr('value', dpt.value).text(dpt.text)); } }); if (ui.dptShortRelease.children().length) { ui.dptShortRelease.val(targetShort); } if (ui.dptShortReleaseStatus.children().length) { ui.dptShortReleaseStatus.val(targetShortStatus); } if (ui.dptRepeat.children().length) { ui.dptRepeat.val(targetRepeat); } }); }; const attachGroupAddressAutocomplete = ({ $input, $name, $dptSelect, filterFn }) => { if (!$input || !$input.length) return; $input.autocomplete({ minLength: 0, source(request, response) { const rawValue = $('#node-input-server').val(); const serverId = resolveServerId(rawValue === undefined ? (currentNode ? currentNode.server : null) : rawValue); const server = serverId ? RED.nodes.node(serverId) : null; if (!server) { response([]); return; } $.getJSON(`knxUltimatecsv?nodeID=${server.id}`, (data) => { const matches = []; data.forEach((value) => { if (filterFn && !filterFn(value)) 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) { let sDevName = ui.item.label.split('#')[1]?.trim() || ''; try { sDevName = sDevName.substr(sDevName.indexOf(')') + 1).trim(); } catch (error) { /* empty */ } if ($name) $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.knxUltimateHueButton', function () { $(this).autocomplete('search', `${$(this).val()}exactmatch`); }); try { const serverId = resolveServerId($('#node-input-server').val() || (currentNode ? currentNode.server : null)); const server = serverId ? RED.nodes.node(serverId) : null; if (server && server.id) KNX_enableSecureFormatting($input, server.id); } catch (error) { /* empty */ } }; const hasKNXServerSelected = () => { let domValue = $('#node-input-server').val(); if (domValue === undefined && currentNode) domValue = currentNode.server; const knxServerId = resolveServerId(domValue); return Boolean(knxServerId); }; const updateToggleSections = () => { const toggled = ui?.toggleCheckbox ? ui.toggleCheckbox.is(':checked') : false; if (ui?.statusRows) { if (toggled) { ui.statusRows.show(); } else { ui.statusRows.hide(); } } if (ui?.fixedValueSection) { if (toggled) { ui.fixedValueSection.hide(); } else { ui.fixedValueSection.show(); } } }; const updateTabsVisibility = () => { if (!ui?.tabs) return; const hueDomValue = $('#node-input-serverHue').val(); const hueServerId = resolveServerId(hueDomValue === undefined ? (currentNode ? currentNode.serverHue : null) : hueDomValue); const knxSelected = hasKNXServerSelected(); if (hueServerId) { ui.requiresBridgeElems?.show(); } else { ui.requiresBridgeElems?.hide(); } if (hueServerId && knxSelected) { ui.tabs.show(); ui.tabs.tabs('refresh'); } else { ui.tabs.hide(); } if (ui?.outputInfo) { if (knxSelected) { ui.outputInfo.hide(); } else { ui.outputInfo.show(); } } }; const updateKNXVisibility = () => { const knxSelected = hasKNXServerSelected(); if (knxSelected) { ui?.knxSections?.show(); } else { ui?.knxSections?.hide(); } updateTabsVisibility(); }; RED.nodes.registerType('knxUltimateHueButton', { category: 'KNX Ultimate HUE', color: '#C0C7E9', defaults: { server: { type: 'knxUltimate-config', required: false }, serverHue: { type: 'hue-config', required: true }, name: { value: '' }, nameDim: { value: '' }, GArepeat: { value: '' }, dptrepeat: { value: '3.007' }, nameshort_release: { value: '' }, GAshort_release: { value: '' }, dptshort_release: { value: '1.001' }, nameshort_releaseStatus: { value: '' }, GAshort_releaseStatus: { value: '' }, dptshort_releaseStatus: { value: '1.001' }, toggleValues: { value: true }, hueDevice: { value: '' }, switchSend: { value: true }, dimSend: { value: 'up' }, }, inputs: 0, outputs: 1, icon: 'node-hue-icon.svg', label() { return this.name || 'Hue Button'; }, paletteLabel: 'Hue Button', oneditprepare() { try { RED.sidebar.show('help'); } catch (error) { /* empty */ } const node = this; currentNode = node; ensureConfigSelection('#node-input-serverHue'); ensureVerticalTabsStyle(); ui = { tabs: $('#hue-button-tabs'), requiresBridgeElems: $('.hue-requires-bridge'), knxSections: $('.hue-knx-section'), deviceName: $('#node-input-name'), refreshButton: $('.hue-refresh-devices'), loadingIndicator: $('.hue-devices-loading'), outputInfo: $('.hue-output-info'), toggleCheckbox: $('#node-input-toggleValues'), statusRows: $('.hue-status-row'), fixedValueSection: $('.hue-fixed-values'), switchSendInput: $('#node-input-switchSend'), dimSendInput: $('#node-input-dimSend'), dptShortRelease: $('#node-input-dptshort_release'), dptShortReleaseStatus: $('#node-input-dptshort_releaseStatus'), dptRepeat: $('#node-input-dptrepeat'), gaShortRelease: $('#node-input-GAshort_release'), gaShortReleaseStatus: $('#node-input-GAshort_releaseStatus'), gaRepeat: $('#node-input-GArepeat'), }; cachedDevices = Array.isArray(node._cachedButtonDevices) ? node._cachedButtonDevices : []; node._cachedButtonDevices = cachedDevices; defaultDevicePlaceholder = ui.deviceName.attr('placeholder') || ''; showingNoDevicesPlaceholder = false; ui.tabs.addClass('hue-vertical-tabs'); ui.tabs.tabs(); ui.tabs.find('li').removeClass('ui-corner-top').addClass('ui-corner-left'); const initialServerDomValue = $('#node-input-server').val(); const initialServerId = initialServerDomValue === undefined ? node.server : initialServerDomValue; loadDptOptions(initialServerId, node); attachGroupAddressAutocomplete({ $input: ui.gaShortRelease, $name: $('#node-input-nameshort_release'), $dptSelect: ui.dptShortRelease, filterFn: (value) => value.dpt && value.dpt.startsWith('1.'), }); attachGroupAddressAutocomplete({ $input: ui.gaShortReleaseStatus, $name: $('#node-input-nameshort_releaseStatus'), $dptSelect: ui.dptShortReleaseStatus, filterFn: (value) => value.dpt && value.dpt.startsWith('1.'), }); attachGroupAddressAutocomplete({ $input: ui.gaRepeat, $name: $('#node-input-nameDim'), $dptSelect: ui.dptRepeat, filterFn: (value) => value.dpt && value.dpt.startsWith('3.007'), }); if (ui.switchSendInput) { ui.switchSendInput.typedInput({ type: 'bool', types: ['bool'], }); const initialSwitch = coerceBool(node.switchSend, true); ui.switchSendInput.typedInput('value', initialSwitch ? 'true' : 'false'); } if (ui.dimSendInput) { ui.dimSendInput.typedInput({ type: 'direction', types: [{ value: 'direction', options: [ { value: 'up', label: RED._('node-red-contrib-knx-ultimate/knxUltimateHueButton:knxUltimateHueButton.dim_up') || 'Up' }, { value: 'down', label: RED._('node-red-contrib-knx-ultimate/knxUltimateHueButton:knxUltimateHueButton.dim_down') || 'Down' }, { value: 'stop', label: RED._('node-red-contrib-knx-ultimate/knxUltimateHueButton:knxUltimateHueButton.dim_stop') || 'Stop' }, ], }], }); const initialDim = typeof node.dimSend === 'string' ? node.dimSend : 'up'; ui.dimSendInput.typedInput('value', initialDim || 'up'); } // If the stored value is missing/legacy, default to false so UI matches runtime truthiness. const initialToggle = coerceBool(node.toggleValues, false); if (ui.toggleCheckbox) { ui.toggleCheckbox.prop('checked', initialToggle); ui.toggleCheckbox.on('change.knxUltimateHueButton', () => { updateToggleSections(); }); } updateToggleSections(); if (ui.deviceName) { ui.deviceName.autocomplete({ minLength: 0, source(request, response) { const hueDomValue = $('#node-input-serverHue').val(); const hueServerId = resolveServerId(hueDomValue === undefined ? node.serverHue : hueDomValue); const hueServer = hueServerId ? RED.nodes.node(hueServerId) : null; if (!hueServer) { response([]); return; } fetchDevices(hueServer, request.term, response); }, select(event, ui) { $('#node-input-hueDevice').val(ui.item.hueDevice); }, }); ui.deviceName.on('focus.knxUltimateHueButton', function () { $(this).autocomplete('search', `${$(this).val()}exactmatch`); }); } if (ui.refreshButton) { ui.refreshButton.on('click.knxUltimateHueButton', () => { cachedDevices = []; node._cachedButtonDevices = cachedDevices; const hueDomValue = $('#node-input-serverHue').val(); const hueServerId = resolveServerId(hueDomValue === undefined ? node.serverHue : hueDomValue); const hueServer = hueServerId ? RED.nodes.node(hueServerId) : null; if (!hueServer) return; fetchDevices(hueServer, '', () => { if (ui?.deviceName) { ui.deviceName.autocomplete('search', `${ui.deviceName.val()}exactmatch`); } }, { forceRefresh: true }); }); } $('#node-input-server').on('change.knxUltimateHueButton', function () { const serverId = $(this).val(); loadDptOptions(serverId, node); updateKNXVisibility(); }); $('#node-input-serverHue').on('change.knxUltimateHueButton', function () { const hueServerId = resolveServerId($(this).val()); cachedDevices = []; node._cachedButtonDevices = cachedDevices; if (ui?.loadingIndicator) ui.loadingIndicator.hide(); showingNoDevicesPlaceholder = false; if (ui?.deviceName) { ui.deviceName.attr('placeholder', defaultDevicePlaceholder); } if (!hueServerId) { applyNoDevicesPlaceholder(true); } updateTabsVisibility(); }); updateKNXVisibility(); }, oneditsave() { try { RED.sidebar.show('info'); } catch (error) { /* empty */ } // Persist values explicitly because typedInput/checkbox widgets are not always serialised reliably by Node-RED. this.toggleValues = ui?.toggleCheckbox ? ui.toggleCheckbox.is(':checked') : coerceBool(this.toggleValues, false); const switchSendRaw = ui?.switchSendInput ? ui.switchSendInput.typedInput('value') : this.switchSend; this.switchSend = (switchSendRaw === true || String(switchSendRaw).toLowerCase() === 'true') ? 'true' : 'false'; if (ui?.dimSendInput) { const dimRaw = ui.dimSendInput.typedInput('value'); this.dimSend = typeof dimRaw === 'string' && dimRaw !== '' ? dimRaw : (this.dimSend || 'up'); } detachHandlers(); cachedDevices = []; this._cachedButtonDevices = []; currentNode = null; ui = null; }, oneditcancel() { try { RED.sidebar.show('info'); } catch (error) { /* empty */ } detachHandlers(); cachedDevices = []; this._cachedButtonDevices = []; currentNode = null; ui = null; }, }); }()); </script> <script type="text/html" data-template-name="knxUltimateHueButton"> <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-tag"></i> <span data-i18n="knxUltimateHueButton.hue_sensor"></span> </label> <input type="text" id="node-input-name" placeholder="Hue button" 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-button-tabs"> <ul> <li><a href="#hue-button-tab-switch"><i class="fa fa-toggle-on"></i> <span data-i18n="knxUltimateHueButton.tabs.switch"></span></a></li> <li><a href="#hue-button-tab-dim"><i class="fa fa-sun"></i> <span data-i18n="knxUltimateHueButton.tabs.dim"></span></a></li> <li><a href="#hue-button-tab-behaviour"><i class="fa fa-gear"></i> <span data-i18n="knxUltimateHueButton.tabs.behaviour"></span></a></li> </ul> <div id="hue-button-tab-switch"> <div class="form-tips hue-form-tip hue-knx-section"> <i class="fa fa-circle-info"></i> <span data-i18n="knxUltimateHueButton.switch_info"></span> </div> <div class="form-row hue-knx-section"> <label for="node-input-GAshort_release" style="width:70px;"><span data-i18n="common.ga"></span></label> <input type="text" id="node-input-GAshort_release" style="width:80px; text-align:left;" placeholder="1/1/1"> <label for="node-input-dptshort_release" style="width:40px; text-align:right;"><span data-i18n="common.dpt"></span></label> <select id="node-input-dptshort_release" style="width:120px;"></select> <label for="node-input-nameshort_release" style="width:50px; text-align:right;"><span data-i18n="common.name"></span></label> <input type="text" id="node-input-nameshort_release" style="flex:1 1 140px; min-width:120px; text-align:left;" placeholder="Switch action"> </div> <div class="form-row hue-knx-section hue-status-row"> <label for="node-input-GAshort_releaseStatus" style="width:70px;"><span data-i18n="knxUltimateHueButton.switch_status"></span></label> <input type="text" id="node-input-GAshort_releaseStatus" style="width:80px; text-align:left;" placeholder="1/1/2"> <label for="node-input-dptshort_releaseStatus" style="width:40px; text-align:right;"><span data-i18n="common.dpt"></span></label> <select id="node-input-dptshort_releaseStatus" style="width:120px;"></select> <label for="node-input-nameshort_releaseStatus" style="width:50px; text-align:right;"><span data-i18n="common.name"></span></label> <input type="text" id="node-input-nameshort_releaseStatus" style="flex:1 1 140px; min-width:120px; text-align:left;" placeholder="Switch status"> </div> </div> <div id="hue-button-tab-dim"> <div class="form-tips hue-form-tip hue-knx-section"> <i class="fa fa-circle-info"></i> <span data-i18n="knxUltimateHueButton.dim_info"></span> </div> <div class="form-row hue-knx-section"> <label for="node-input-GArepeat" style="width:70px;"><span data-i18n="common.ga"></span></label> <input type="text" id="node-input-GArepeat" style="width:80px; text-align:left;" placeholder="1/1/3"> <label for="node-input-dptrepeat" style="width:40px; text-align:right;"><span data-i18n="common.dpt"></span></label> <select id="node-input-dptrepeat" style="width:120px;"></select> <label for="node-input-nameDim" style="width:50px; text-align:right;"><span data-i18n="common.name"></span></label> <input type="text" id="node-input-nameDim" style="flex:1 1 140px; min-width:120px; text-align:left;" placeholder="Dim action"> </div> </div> <div id="hue-button-tab-behaviour"> <div class="form-tips hue-form-tip"> <i class="fa fa-circle-info"></i> <span data-i18n="knxUltimateHueButton.behaviour_info"></span> </div> <div class="form-row"> <input type="checkbox" id="node-input-toggleValues" style="width:auto;"> <label for="node-input-toggleValues" style="flex:1 1 auto;"> <span data-i18n="knxUltimateHueButton.toggle_values"></span> </label> </div> <div class="form-row hue-status-row" style="margin-left:24px;"> <span data-i18n="knxUltimateHueButton.toggle_values_hint"></span> </div> <div class="hue-fixed-values" style="margin-top:8px;"> <div class="form-row"> <label for="node-input-switchSend" style="width:130px;"><span data-i18n="knxUltimateHueButton.switch_send"></span></label> <input type="text" id="node-input-switchSend" style="width:160px;"> </div> <div class="form-row"> <label for="node-input-dimSend" style="width:130px;"><span data-i18n="knxUltimateHueButton.dim_send"></span></label> <input type="text" id="node-input-dimSend" style="width:160px;"> </div> </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="knxUltimateHueButton.output_info"></span> </div> <input type="hidden" id="node-input-hueDevice"> </script>