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.

326 lines (287 loc) 16.6 kB
<script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script> <script type="text/javascript"> RED.nodes.registerType('knxUltimateStaircase', { category: 'KNX Ultimate', color: '#C7E9C0', defaults: { server: { type: 'knxUltimate-config', required: true }, name: { value: '' }, outputtopic: { value: '' }, gaTrigger: { value: '', required: true }, nameTrigger: { value: '' }, dptTrigger: { value: '1.001' }, gaOutput: { value: '', required: true }, nameOutput: { value: '' }, dptOutput: { value: '1.001' }, gaStatus: { value: '' }, nameStatus: { value: '' }, dptStatus: { value: '1.001' }, gaOverride: { value: '' }, nameOverride: { value: '' }, dptOverride: { value: '1.001' }, gaBlock: { value: '' }, nameBlock: { value: '' }, dptBlock: { value: '1.001' }, timerSeconds: { value: 120, required: true, validate: RED.validators.number() }, extendMode: { value: 'restart' }, triggerOffCancels: { value: 'yes' }, preWarnEnable: { value: false }, preWarnSeconds: { value: 15 }, preWarnMode: { value: 'status' }, preWarnFlashMs: { value: 400 }, blockAction: { value: 'off' }, emitEvents: { value: false } }, inputs: 1, outputs: 1, icon: 'node-knx-icon.svg', label: function () { return this.name || 'KNX Staircase'; }, paletteLabel: 'KNX Staircase', oneditprepare: function () { const node = this; const $knxServerInput = $('#node-input-server'); try { RED.sidebar.show('help'); } catch (error) { /* ignore */ } const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none']); const resolveKnxServerValue = () => { const domValue = $knxServerInput.val(); if (domValue !== undefined && domValue !== null && domValue !== '') return domValue; if (node.server !== undefined && node.server !== null && node.server !== '') return node.server; return ''; }; const hasKnxServerSelected = () => { const val = resolveKnxServerValue(); return !(val === undefined || val === null || KNX_EMPTY_VALUES.has(String(val))); }; const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map()); const fetchGroupAddresses = (serverId) => { if (!serverId) return Promise.resolve([]); if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId)); return new Promise((resolve) => { $.getJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => { const list = Array.isArray(data) ? data : []; KNX_GA_CACHE.set(serverId, list); resolve(list); }).fail(() => resolve([])); }); }; const getGroupAddress = (gaSelector, nameSelector, dptSelector, prefixes) => { const $gaInput = $(gaSelector); const $nameInput = $(nameSelector); const $dptInput = $(dptSelector); if (!$gaInput.length) return; const ensureAutocomplete = () => { const sourceFn = (request, response) => { if (!hasKnxServerSelected()) { response([]); return; } const serverId = resolveKnxServerValue(); fetchGroupAddresses(serverId).then((data) => { const items = []; data.forEach((entry) => { const dpt = entry.dpt || ''; const allowed = prefixes.some((prefix) => prefix === '' || dpt.startsWith(prefix)); if (!allowed) return; const devName = entry.devicename || ''; const searchStr = `${entry.ga} (${devName}) DPT${dpt}`; if (!htmlUtilsfullCSVSearch(searchStr, request.term || '')) return; items.push({ label: `${entry.ga} # ${devName} # ${dpt}`, value: entry.ga, ga: entry.ga }); }); response(items); }); }; if ($gaInput.data('knx-ga-initialised')) { $gaInput.autocomplete('option', 'source', sourceFn); } else { $gaInput .autocomplete({ minLength: 0, source: sourceFn, select: (event, ui) => { let deviceName = ''; try { deviceName = ui.item.label.split('#')[1].trim(); deviceName = deviceName.replace(/^\)/, '').trim(); } catch (error) { deviceName = ''; } if ($nameInput.length) { if (deviceName && deviceName !== '') { $nameInput.val(deviceName); } else if (!$nameInput.val()) { $nameInput.val(''); } } try { const parts = ui.item.label.split('#'); const dptFromLabel = parts.length >= 3 ? parts[2].trim() : ''; if (dptFromLabel !== '') { $dptInput.val(dptFromLabel); } } catch (error) { /* ignore */ } } }) .on('focus.knxUltimateStaircase click.knxUltimateStaircase', function () { const currentValue = $(this).val() || ''; try { $(this).autocomplete('search', `${currentValue} exactmatch`); } catch (error) { /* ignore */ } }); $gaInput.data('knx-ga-initialised', true); } try { if (hasKnxServerSelected()) { const srv = RED.nodes.node(resolveKnxServerValue()); if (srv && srv.id) KNX_enableSecureFormatting($gaInput, srv.id); } } catch (error) { /* ignore */ } }; ensureAutocomplete(); }; const BINARY_PREFIX = ['1.']; const ANY_ANALOG_PREFIX = ['1.', '2.', '5.', '6.', '7.', '8.', '9.', '12.', '13.', '14.', '16.', '20.']; const refreshKnxBindings = () => { if (!hasKnxServerSelected()) { return; } getGroupAddress('#node-input-gaTrigger', '#node-input-nameTrigger', '#node-input-dptTrigger', BINARY_PREFIX); getGroupAddress('#node-input-gaOutput', '#node-input-nameOutput', '#node-input-dptOutput', ANY_ANALOG_PREFIX); getGroupAddress('#node-input-gaStatus', '#node-input-nameStatus', '#node-input-dptStatus', ANY_ANALOG_PREFIX); getGroupAddress('#node-input-gaOverride', '#node-input-nameOverride', '#node-input-dptOverride', BINARY_PREFIX); getGroupAddress('#node-input-gaBlock', '#node-input-nameBlock', '#node-input-dptBlock', BINARY_PREFIX); }; $knxServerInput.on('change', () => { KNX_GA_CACHE.clear(); refreshKnxBindings(); }); refreshKnxBindings(); const $preWarnRows = $('.knx-staircase-prewarn'); const $flashRow = $('.knx-staircase-prewarn-flash'); function syncPreWarnMode() { const enabled = $('#node-input-preWarnEnable').is(':checked'); $preWarnRows.toggle(enabled); if (!enabled) { $flashRow.hide(); return; } const mode = $('#node-input-preWarnMode').val(); $flashRow.toggle(mode === 'flash'); } $('#node-input-preWarnEnable').on('change', syncPreWarnMode); $('#node-input-preWarnMode').on('change', syncPreWarnMode); syncPreWarnMode(); } }); </script> <script type="text/html" data-template-name="knxUltimateStaircase"> <div class="form-row" style="display:flex; align-items:center;"> <label for="node-input-server" style="width:180px"><i class="fa fa-circle-o"></i> <span data-i18n="knxUltimateStaircase.node-input-server"></span></label> <input type="text" id="node-input-server" style="flex:1"> </div> <div class="form-row" style="display:flex; align-items:center;"> <label for="node-input-name" style="width:180px"><i class="fa fa-tag"></i> <span data-i18n="knxUltimateStaircase.node-input-name"></span></label> <input type="text" id="node-input-name" style="flex:1"> </div> <div class="form-row" style="display:flex; align-items:center;"> <label for="node-input-outputtopic" style="width:180px"><i class="fa fa-comment"></i> <span data-i18n="knxUltimateStaircase.node-input-outputtopic"></span></label> <input type="text" id="node-input-outputtopic" style="flex:1" placeholder="events/staircase" data-i18n="[placeholder]knxUltimateStaircase.placeholders.outputtopic"> </div> <hr> <div class="form-row" style="margin:4px 0 2px;"> <span style="font-weight:bold;" data-i18n="knxUltimateStaircase.section_commands"></span> </div> <div class="form-row" style="display:flex; align-items:center; gap:8px;"> <label for="node-input-gaOutput" style="width:180px"><i class="fa fa-lightbulb-o"></i> <span data-i18n="knxUltimateStaircase.output"></span></label> <input type="text" id="node-input-gaOutput" style="width:160px" placeholder="1/1/2" data-i18n="[placeholder]knxUltimateStaircase.placeholders.ga"> <input type="text" id="node-input-nameOutput" style="flex:1" placeholder="Staircase actuator" data-i18n="[placeholder]knxUltimateStaircase.placeholders.outputName"> <label for="node-input-dptOutput" style="width:60px; text-align:right">DPT</label> <input type="text" id="node-input-dptOutput" style="width:160px" readonly> </div> <div class="form-row" style="display:flex; align-items:center; gap:8px;"> <label for="node-input-gaStatus" style="width:180px"><i class="fa fa-info-circle"></i> <span data-i18n="knxUltimateStaircase.status"></span></label> <input type="text" id="node-input-gaStatus" style="width:160px" placeholder="1/1/3" data-i18n="[placeholder]knxUltimateStaircase.placeholders.ga"> <input type="text" id="node-input-nameStatus" style="flex:1" placeholder="Status LED" data-i18n="[placeholder]knxUltimateStaircase.placeholders.statusName"> <label for="node-input-dptStatus" style="width:60px; text-align:right">DPT</label> <input type="text" id="node-input-dptStatus" style="width:160px" readonly> </div> <div style="border-top:1px solid #ccc; margin:10px 0 6px;"></div> <div class="form-row" style="margin:4px 0 2px;"> <span style="font-weight:bold;" data-i18n="knxUltimateStaircase.section_inputs"></span> </div> <div class="form-row" style="display:flex; align-items:center; gap:8px;"> <label for="node-input-gaTrigger" style="width:180px"><i class="fa fa-bolt"></i> <span data-i18n="knxUltimateStaircase.trigger"></span></label> <input type="text" id="node-input-gaTrigger" style="width:160px" placeholder="1/1/1" data-i18n="[placeholder]knxUltimateStaircase.placeholders.ga"> <input type="text" id="node-input-nameTrigger" style="flex:1" placeholder="Living room switch" data-i18n="[placeholder]knxUltimateStaircase.placeholders.triggerName"> <label for="node-input-dptTrigger" style="width:60px; text-align:right">DPT</label> <input type="text" id="node-input-dptTrigger" style="width:160px" readonly> </div> <div class="form-row" style="display:flex; align-items:center; gap:8px;"> <label for="node-input-gaOverride" style="width:180px"><i class="fa fa-toggle-on"></i> <span data-i18n="knxUltimateStaircase.override"></span></label> <input type="text" id="node-input-gaOverride" style="width:160px" placeholder="1/1/4" data-i18n="[placeholder]knxUltimateStaircase.placeholders.ga"> <input type="text" id="node-input-nameOverride" style="flex:1" placeholder="Maintenance override" data-i18n="[placeholder]knxUltimateStaircase.placeholders.overrideName"> <label for="node-input-dptOverride" style="width:60px; text-align:right">DPT</label> <input type="text" id="node-input-dptOverride" style="width:160px" readonly> </div> <div class="form-row" style="display:flex; align-items:center; gap:8px;"> <label for="node-input-gaBlock" style="width:180px"><i class="fa fa-hand-paper-o"></i> <span data-i18n="knxUltimateStaircase.block"></span></label> <input type="text" id="node-input-gaBlock" style="width:160px" placeholder="1/1/5" data-i18n="[placeholder]knxUltimateStaircase.placeholders.ga"> <input type="text" id="node-input-nameBlock" style="flex:1" placeholder="Block command" data-i18n="[placeholder]knxUltimateStaircase.placeholders.blockName"> <label for="node-input-dptBlock" style="width:60px; text-align:right">DPT</label> <input type="text" id="node-input-dptBlock" style="width:160px" readonly> </div> <hr> <div class="form-row" style="display:flex; align-items:center;"> <label for="node-input-timerSeconds" style="width:180px"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateStaircase.node-input-timerSeconds"></span></label> <input type="number" id="node-input-timerSeconds" style="width:140px" min="1"> </div> <div class="form-row" style="display:flex; align-items:center;"> <label for="node-input-extendMode" style="width:180px"><i class="fa fa-repeat"></i> <span data-i18n="knxUltimateStaircase.node-input-extendMode"></span></label> <select id="node-input-extendMode" style="flex:1"> <option value="restart" data-i18n="knxUltimateStaircase.extend_restart"></option> <option value="extend" data-i18n="knxUltimateStaircase.extend_extend"></option> <option value="ignore" data-i18n="knxUltimateStaircase.extend_ignore"></option> </select> </div> <div class="form-row" style="display:flex; align-items:center;"> <label for="node-input-triggerOffCancels" style="width:180px"><i class="fa fa-power-off"></i> <span data-i18n="knxUltimateStaircase.node-input-triggerOffCancels"></span></label> <select id="node-input-triggerOffCancels" style="flex:1"> <option value="yes" data-i18n="knxUltimateStaircase.opt_yes"></option> <option value="no" data-i18n="knxUltimateStaircase.opt_no"></option> </select> </div> <div class="form-row" style="display:flex; align-items:center;"> <label for="node-input-blockAction" style="width:180px"><i class="fa fa-ban"></i> <span data-i18n="knxUltimateStaircase.node-input-blockAction"></span></label> <select id="node-input-blockAction" style="flex:1"> <option value="off" data-i18n="knxUltimateStaircase.block_off"></option> <option value="keep" data-i18n="knxUltimateStaircase.block_keep"></option> </select> </div> <hr> <div class="form-row" style="display:flex; align-items:center;"> <label for="node-input-preWarnEnable" style="width:180px"><i class="fa fa-exclamation-triangle"></i> <span data-i18n="knxUltimateStaircase.node-input-preWarnEnable"></span></label> <input type="checkbox" id="node-input-preWarnEnable" style="width:auto"> </div> <div class="form-row knx-staircase-prewarn" style="display:flex; align-items:center;"> <label for="node-input-preWarnSeconds" style="width:180px"><i class="fa fa-hourglass-end"></i> <span data-i18n="knxUltimateStaircase.node-input-preWarnSeconds"></span></label> <input type="number" id="node-input-preWarnSeconds" style="width:140px" min="1"> </div> <div class="form-row knx-staircase-prewarn" style="display:flex; align-items:center;"> <label for="node-input-preWarnMode" style="width:180px"><i class="fa fa-bullhorn"></i> <span data-i18n="knxUltimateStaircase.node-input-preWarnMode"></span></label> <select id="node-input-preWarnMode" style="flex:1"> <option value="status" data-i18n="knxUltimateStaircase.prewarn_status"></option> <option value="flash" data-i18n="knxUltimateStaircase.prewarn_flash"></option> </select> </div> <div class="form-row knx-staircase-prewarn knx-staircase-prewarn-flash" style="display:flex; align-items:center;"> <label for="node-input-preWarnFlashMs" style="width:180px"><i class="fa fa-lightbulb-o"></i> <span data-i18n="knxUltimateStaircase.node-input-preWarnFlashMs"></span></label> <input type="number" id="node-input-preWarnFlashMs" style="width:140px" min="50"> </div> <hr> <div class="form-row" style="display:flex; align-items:center;"> <label for="node-input-emitEvents" style="width:180px"><i class="fa fa-sign-out"></i> <span data-i18n="knxUltimateStaircase.node-input-emitEvents"></span></label> <input type="checkbox" id="node-input-emitEvents" style="width:auto"> </div> <br/><br/><br/><br/> </script>