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.

309 lines (276 loc) 15.5 kB
<script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script> <script type="text/javascript"> RED.nodes.registerType('knxUltimateGarage', { category: 'KNX Ultimate', color: '#C7E9C0', defaults: { server: { type: 'knxUltimate-config', required: true }, name: { value: '' }, outputtopic: { value: '' }, gaCommand: { value: '', required: true }, nameCommand: { value: '' }, dptCommand: { value: '1.001' }, gaImpulse: { value: '' }, nameImpulse: { value: '' }, dptImpulse: { value: '1.017' }, gaHoldOpen: { value: '' }, nameHoldOpen: { value: '' }, dptHoldOpen: { value: '1.001' }, gaDisable: { value: '' }, nameDisable: { value: '' }, dptDisable: { value: '1.001' }, gaPhotocell: { value: '' }, namePhotocell: { value: '' }, dptPhotocell: { value: '1.001' }, gaMoving: { value: '' }, nameMoving: { value: '' }, dptMoving: { value: '1.001' }, gaObstruction: { value: '' }, nameObstruction: { value: '' }, dptObstruction: { value: '1.001' }, autoCloseEnable: { value: true }, autoCloseSeconds: { value: 120, validate: RED.validators.number() }, emitEvents: { value: false } }, inputs: 1, outputs: 1, icon: 'node-knx-icon.svg', label: function () { return this.name || 'KNX Garage'; }, paletteLabel: 'KNX Garage', 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, dpt }); }); 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.knxUltimateGarage click.knxUltimateGarage', 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 refreshKnxBindings = () => { if (!hasKnxServerSelected()) return; getGroupAddress('#node-input-gaCommand', '#node-input-nameCommand', '#node-input-dptCommand', BINARY_PREFIX); getGroupAddress('#node-input-gaImpulse', '#node-input-nameImpulse', '#node-input-dptImpulse', BINARY_PREFIX); getGroupAddress('#node-input-gaHoldOpen', '#node-input-nameHoldOpen', '#node-input-dptHoldOpen', BINARY_PREFIX); getGroupAddress('#node-input-gaDisable', '#node-input-nameDisable', '#node-input-dptDisable', BINARY_PREFIX); getGroupAddress('#node-input-gaPhotocell', '#node-input-namePhotocell', '#node-input-dptPhotocell', BINARY_PREFIX); getGroupAddress('#node-input-gaMoving', '#node-input-nameMoving', '#node-input-dptMoving', BINARY_PREFIX); getGroupAddress('#node-input-gaObstruction', '#node-input-nameObstruction', '#node-input-dptObstruction', BINARY_PREFIX); }; $knxServerInput.on('change', () => { KNX_GA_CACHE.clear(); refreshKnxBindings(); }); if (hasKnxServerSelected()) refreshKnxBindings(); const syncAutoClose = () => { const enabled = $('#node-input-autoCloseEnable').is(':checked'); const $seconds = $('#node-input-autoCloseSeconds').closest('.form-row'); $seconds.toggle(enabled); }; $('#node-input-autoCloseEnable').on('change', syncAutoClose); syncAutoClose(); } }); </script> <script type="text/html" data-template-name="knxUltimateGarage"> <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="knxUltimateGarage.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="knxUltimateGarage.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="knxUltimateGarage.node-input-outputtopic"></span></label> <input type="text" id="node-input-outputtopic" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.outputtopic"> </div> <hr> <div class="form-row" style="margin:4px 0 2px;"> <span style="font-weight:bold;" data-i18n="knxUltimateGarage.section_commands"></span> </div> <div class="form-row" style="display:flex; align-items:center; gap:8px;"> <label for="node-input-gaCommand" style="width:180px"><i class="fa fa-exchange"></i> <span data-i18n="knxUltimateGarage.command"></span></label> <input type="text" id="node-input-gaCommand" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga"> <input type="text" id="node-input-nameCommand" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.commandName"> <label for="node-input-dptCommand" style="width:60px; text-align:right">DPT</label> <input type="text" id="node-input-dptCommand" style="width:160px" readonly> </div> <div class="form-row" style="display:flex; align-items:center; gap:8px;"> <label for="node-input-gaImpulse" style="width:180px"><i class="fa fa-bolt"></i> <span data-i18n="knxUltimateGarage.impulse"></span></label> <input type="text" id="node-input-gaImpulse" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga"> <input type="text" id="node-input-nameImpulse" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.impulseName"> <label for="node-input-dptImpulse" style="width:60px; text-align:right">DPT</label> <input type="text" id="node-input-dptImpulse" style="width:160px" readonly> </div> <div class="form-row" style="display:flex; align-items:center; gap:8px;"> <label for="node-input-gaMoving" style="width:180px"><i class="fa fa-arrows-h"></i> <span data-i18n="knxUltimateGarage.moving"></span></label> <input type="text" id="node-input-gaMoving" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga"> <input type="text" id="node-input-nameMoving" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.movingName"> <label for="node-input-dptMoving" style="width:60px; text-align:right">DPT</label> <input type="text" id="node-input-dptMoving" style="width:160px" readonly> </div> <div class="form-row" style="display:flex; align-items:center; gap:8px;"> <label for="node-input-gaObstruction" style="width:180px"><i class="fa fa-exclamation-triangle"></i> <span data-i18n="knxUltimateGarage.obstruction"></span></label> <input type="text" id="node-input-gaObstruction" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga"> <input type="text" id="node-input-nameObstruction" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.obstructionName"> <label for="node-input-dptObstruction" style="width:60px; text-align:right">DPT</label> <input type="text" id="node-input-dptObstruction" 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="knxUltimateGarage.section_inputs"></span> </div> <div class="form-row" style="display:flex; align-items:center; gap:8px;"> <label for="node-input-gaHoldOpen" style="width:180px"><i class="fa fa-pause"></i> <span data-i18n="knxUltimateGarage.holdOpen"></span></label> <input type="text" id="node-input-gaHoldOpen" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga"> <input type="text" id="node-input-nameHoldOpen" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.holdOpenName"> <label for="node-input-dptHoldOpen" style="width:60px; text-align:right">DPT</label> <input type="text" id="node-input-dptHoldOpen" style="width:160px" readonly> </div> <div class="form-row" style="display:flex; align-items:center; gap:8px;"> <label for="node-input-gaDisable" style="width:180px"><i class="fa fa-ban"></i> <span data-i18n="knxUltimateGarage.disable"></span></label> <input type="text" id="node-input-gaDisable" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga"> <input type="text" id="node-input-nameDisable" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.disableName"> <label for="node-input-dptDisable" style="width:60px; text-align:right">DPT</label> <input type="text" id="node-input-dptDisable" style="width:160px" readonly> </div> <div class="form-row" style="display:flex; align-items:center; gap:8px;"> <label for="node-input-gaPhotocell" style="width:180px"><i class="fa fa-lightbulb-o"></i> <span data-i18n="knxUltimateGarage.photocell"></span></label> <input type="text" id="node-input-gaPhotocell" style="width:160px" data-i18n="[placeholder]knxUltimateGarage.placeholders.ga"> <input type="text" id="node-input-namePhotocell" style="flex:1" data-i18n="[placeholder]knxUltimateGarage.placeholders.photocellName"> <label for="node-input-dptPhotocell" style="width:60px; text-align:right">DPT</label> <input type="text" id="node-input-dptPhotocell" style="width:160px" readonly> </div> <hr> <div class="form-row" style="display:flex; align-items:center;"> <label for="node-input-autoCloseEnable" style="width:180px"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateGarage.autoCloseEnable"></span></label> <input type="checkbox" id="node-input-autoCloseEnable" style="width:auto"> </div> <div class="form-row" style="display:flex; align-items:center;"> <label for="node-input-autoCloseSeconds" style="width:180px"><i class="fa fa-hourglass-end"></i> <span data-i18n="knxUltimateGarage.autoCloseSeconds"></span></label> <input type="number" id="node-input-autoCloseSeconds" style="width:140px" min="1"> </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="knxUltimateGarage.node-input-emitEvents"></span></label> <input type="checkbox" id="node-input-emitEvents" style="width:auto"> </div> <br/><br/><br/><br/> </script> <script type="text/html" data-help-name="knxUltimateGarage"> <div data-i18n="knxUltimateGarage.help.intro"></div> <h3 data-i18n="knxUltimateGarage.help.commands"></h3> <ul> <li data-i18n="knxUltimateGarage.help.command_ga"></li> <li data-i18n="knxUltimateGarage.help.impulse_ga"></li> <li data-i18n="knxUltimateGarage.help.holdopen_ga"></li> <li data-i18n="knxUltimateGarage.help.disable_ga"></li> </ul> <h3 data-i18n="knxUltimateGarage.help.safety"></h3> <ul> <li data-i18n="knxUltimateGarage.help.photocell_ga"></li> <li data-i18n="knxUltimateGarage.help.moving_ga"></li> <li data-i18n="knxUltimateGarage.help.obstruction_ga"></li> </ul> <h3 data-i18n="knxUltimateGarage.help.auto"></h3> <ul> <li data-i18n="knxUltimateGarage.help.auto_close"></li> </ul> <h3 data-i18n="knxUltimateGarage.help.events"></h3> <p data-i18n="knxUltimateGarage.help.events_desc"></p> </script>