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.

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>