UNPKG

iobroker.rfxcom

Version:
763 lines (688 loc) 31 kB
<html> <link rel="stylesheet" type="text/css" href="../../lib/css/themes/jquery-ui/redmond/jquery-ui.min.css"/> <link rel="stylesheet" type="text/css" href="../../lib/css/jqGrid/ui.jqgrid-4.5.4.css"/> <link rel="stylesheet" type="text/css" href="../../lib/css/jquery.multiselect-1.13.css"/> <script type="text/javascript" src="../../lib/js/jquery-1.11.1.min.js"></script> <script type="text/javascript" src="../../socket.io/socket.io.js"></script> <script type="text/javascript" src="../../lib/js/jquery-ui-1.10.3.full.min.js"></script> <script type="text/javascript" src="../../lib/js/jqGrid/jquery.jqGrid-4.5.4.min.js"></script> <script type="text/javascript" src="../../lib/js/jqGrid/grid.locale-all.js"></script> <link rel="stylesheet" type="text/css" href="../../css/adapter.css"/> <script type="text/javascript" src="../../js/translate.js"></script> <script type="text/javascript" src="../../js/adapter-settings.js"></script> <script type="text/javascript" src="parse.js"></script> <script type="text/javascript" src="words.js"></script> <style> .oneWidth { width: 120px; } .pair-on { background: rgb(255, 153, 153) !important; } .no-address { color: rgb(255, 15, 15); font-weight: bold; } .auto-pair-problem { color: mediumvioletred; font-weight: bold; } #devices tr:nth-child(even) { background: lightgray; } @keyframes highlight { 0% { color: #00f900 } 80% { color: green } 100% { color: black; } } .highlight { animation: highlight 2s; } @keyframes show-pair-off { 0% { color: #F90000 } 80% { color: red } 100% { color: black; } } .show-pair-off { animation: show-pair-off 2s; } #drop-zone { width: calc(100% - 10px); height: calc(100% - 10px); position: absolute; opacity: 0.8; top: 0; left: 0; background: #eee; -webkit-border-radius: 15px; -moz-border-radius: 15px; border-radius: 15px; z-index: 1; font-size: 32px; font-weight: bold; text-align: center; border: 5px dashed darkgray; } .dropZone-error { background: #faa !important; color: #f00; } </style> <script type="text/javascript"> var devices = []; var timeout; var checkTimer; var channels = {}; var states = {}; var values = {}; var problems = []; var timers = []; var availableVersion = null; var isAlive = false; function getComPorts(actualValue) { timeout = setTimeout(function () { getComPorts(actualValue); }, 2000); sendTo(null, 'listUart', null, function (list) { if (timeout) { clearTimeout(timeout); timeout = null; } if (!list || !list.length) { setTimeout(function () { getComPorts(actualValue); }, 1000); return; } var text = '<option value="">' + _('Select port') + '</option>'; for (var j = 0; j < list.length; j++) { if (list[j].comName === 'Not available') { text += '<option value="" selected>' + _('Not available') + '</option>'; $('#comName').prop('disabled', true); break; } else { text += '<option value="' + list[j].path + '" ' + ((actualValue === list[j].path) ? 'selected' : '') + '>' + list[j].path + '</option>'; } } $('#comName').html(text); }); } function setValue(id, value, onChange) { var $value = $('#' + id + '.value'); if ($value.attr('type') === 'checkbox') { $value.prop('checked', value).change(function () { onChange(); }); } else { $value.val(value).change(function () { onChange(); }).keyup(function () { // Check that only numbers entered if ($(this).hasClass('number')) { var val = $(this).val(); if (val) { var newVal = ''; for (var i = 0; i < val.length; i++) { if (val[i] >= '0' && val[i] <= '9') { newVal += val[i]; } } if (val != newVal) $(this).val(newVal); } } onChange(); }); } } function deleteState(id) { socket.emit('delObject', id, function (err) { socket.emit('delState', id, function (err) { }); }); } function getIntervalAsText(ts) { var minutes = ts ? Math.floor((new Date().getTime() - ts) / 60000) : 0; if (minutes < 0) minutes = 0; if (minutes === 1) { return _('for one minute'); } else if (minutes) { return _('for <b>%s</b> minutes', minutes); } else { return _('just <b>now</b>'); } } function updateIntervals() { $('.last-value').each(function () { var id = $(this).data('id'); $(this).html(getIntervalAsText(values[id].ts)); }); } function showDevice(obj) { if (obj._id === 'rfxcom.' + instance + '.info') return; var text = ''; var _class = ''; var title = ''; if (obj.native.ID === null || obj.native.ID === undefined || obj.native.ID === '') { _class += 'no-address '; title += (title ? ', ' : '') + _('No address') } if (obj.native.autoPairProblem) { _class += 'auto-pair-problem '; title += (title ? ', ' : '') + _('More than one device with the same parameters'); } text += '<td style="text-align: right; white-space: nowrap;" class="' + _class + '" title="' + title + '">' + obj.native.brand + '</td>' ; text += '<td style="text-align: center;">' + obj.native.index + '</td>'; text += '<td style="text-align: center;"><input class="auto-repair" type="checkbox" data-id="' + obj._id + '" ' + (obj.native.autoRepair ? 'checked' : '') + ' /></td>'; // if (obj.native.offset !== undefined) { // text += '<td style="text-align: center;"><input class="offset" type="input" data-id="' + obj._id + '" value="' + (obj.native.offset || 0) + '" /></td>'; // } else { // text += '<td></td>'; // } // if (obj.native.factor !== undefined) { // text += '<td style="text-align: center;"><input class="factor" type="input" data-id="' + obj._id + '" value="' + (obj.native.factor || 1) + '" /></td>'; // } else { // text += '<td></td>'; // } //text += '<td style="text-align: right;">' + obj.native.ID + '</td>'; text += '<td style="text-align: left;" class="raw-value" data-id="' + obj._id + '">' + showValue(obj._id) + '</td>'; text += '<td style="text-align: center"><button class="pair" data-id="' + obj._id + '"></button></td>'; text += '<td style="text-align: center"><button class="del" data-id="' + obj._id + '"></button></td>'; if (channels[obj._id]) { $('tr[data-channel="' + obj._id + '"]').html(text).removeClass('show-pair-off'); } else { text = '<tr class="device" data-channel="' + obj._id + '">' + text + '</tr>'; $('#devices').append(text); } channels[obj._id] = obj; $('.del[data-id="' + obj._id + '"]').button({ text: false, icons: { primary: 'ui-icon-trash' } }).css({width: 22, height: 22}).unbind('click').click(function () { var id = $(this).data('id'); if (confirm(_('Are you sure?'))) { socket.emit('delObject', id, function () { socket.emit('getObjectView', 'system', 'state', {startkey: id + '.', endkey: id + '.\u9999', include_docs: true}, function (err, res) { for (var i = 0; i < res.rows.length; i++) { deleteState(res.rows[i].id); } $('tr[data-channel="' + id + '"]').remove(); delete channels[id]; }); }); } }); $('.auto-repair[data-id="' + obj._id + '"]').change(function () { var $this = $(this); var id = $this.data('id'); $this.prop('disabled', true); channels[id].native.autoRepair = !channels[id].native.autoRepair; socket.emit('setObject', id, channels[id], function (err) { // button will be re-drawn on change }); }); $('.offset[data-id="' + obj._id + '"]').change(function () { var $this = $(this); var id = $this.data('id'); $this.prop('disabled', true); states[id].native.offset = parseFloat($this.val()); socket.emit('setObject', id, states[id], function (err) { // button will be re-drawn on change }); }); $('.factor[data-id="' + obj._id + '"]').change(function () { var $this = $(this); var id = $this.data('id'); $this.prop('disabled', true); states[id].native.factor = parseFloat($this.val()); socket.emit('setObject', id, states[id], function (err) { // button will be re-drawn on change }); }); var $pair = $('.pair[data-id="' + obj._id + '"]'); $pair.button({ text: false, icons: { primary: 'ui-icon-transfer-e-w' } }).css({width: 22, height: 22}).unbind('click').click(function () { var $this = $(this); var id = $this.data('id'); $this.button('disable'); if (!channels[id].native.pair) { channels[id].native.pair = true; channels[id].native.ID = null; // Reset ID } else { channels[id].native.pair = false; } if (!channels[id].native.autoRepair && channels[id].native.autoPairProblem !== undefined) { delete channels[id].native.autoPairProblem; } socket.emit('setObject', id, channels[id], function (err) { // button will be re-drawn on change }); }); if (obj.native.pair) { $pair.addClass('pair-on'); } var pos; if ((pos = problems.indexOf(obj._id)) !== -1) { $('tr[data-channel="' + obj._id + '"]').addClass('show-pair-off'); problems.splice(pos, 1); } } function showValue(channelId) { // build last value var ids = []; var namespace = channelId + '.'; var len = namespace.length; for (var id in values) { if (values.hasOwnProperty(id) && values[id] && id.substring(0, len) === namespace) ids.push(id); } var value; if (ids.length) { ids.sort(); value = '<span class="last-value" data-id="' + ids[0] + '">' + getIntervalAsText(values[ids[0]].ts) + '</span>'; // build values string for (var j = 0; j < ids.length; j++) { value += (value ? ', ' : '') + '<b>' + states[ids[j]].native.attr + '</b>: ' + values[ids[j]].val; } } return value; } function checkAutoRepairs(callback) { if (checkTimer) clearTimeout(checkTimer); checkTimer = setTimeout(function () { _checkAutoRepairs(callback); }, 1000); } function _checkAutoRepairs(callback, ids) { if (!ids) { ids = []; for (var id in channels) { if (!channels.hasOwnProperty(id) || !channels[id].native || !channels[id].native.brand) continue; ids.push(id); } } if (!ids.length) { if (callback) callback(); return; } var _id = ids.shift(); checkAutoRepair(channels[_id], function () { setTimeout(function () { _checkAutoRepairs(callback, ids); }, 0); }); } function checkAutoRepair(obj, callback) { // autoPairs var pairs = []; var autoRepair = []; for (var __id in channels) { if (!channels.hasOwnProperty(__id) || !channels[__id].native) continue; // If device suits to it if (channels[__id].native.brand === obj.native.brand && channels[__id].native.attrs === obj.native.attrs ) { pairs.push(__id); if (channels[__id].native.autoRepair) autoRepair.push(__id); } } if (pairs.length === 1) { if (channels[pairs[0]].native.autoPairProblem) { delete channels[pairs[0]].native.autoPairProblem; // remove autoPairProblem socket.emit('setObject', pairs[0], channels[pairs[0]], function (err) { // button will be re-drawn on change if (callback) callback (); }); } else if (callback){ callback (); } } else if (pairs.length > 1 && autoRepair.length) { // disable autoRepair by all sensors var count = 0; for (var i = 0; i < autoRepair.length; i++) { channels[autoRepair[i]].native.autoRepair = false; if (channels[autoRepair[i]].native.autoPairProblem) delete channels[autoRepair[i]].native.autoPairProblem; var pos = pairs.indexOf(autoRepair[i]); if (pos !== -1) pairs.splice(pos, 1); count++; problems.push(autoRepair[i]); $('tr[data-channel="' + autoRepair[i] + '"]').removeClass('show-pair-off'); socket.emit('setObject', autoRepair[i], channels[autoRepair[i]], function (err) { // button will be re-drawn on change if (!--count && callback) callback(); }); } for (var j = 0; j < pairs.length; j++) { $('tr[data-channel="' + pairs[j] + '"]').removeClass('show-pair-off'); if (channels[pairs[j]].native.autoPairProblem) { delete channels[autoRepair[i]].native.autoPairProblem; problems.push(pairs[j]); count++; adapter.setForeignObject(pairs[j], channels[pairs[j]], function (err) { if (err) adapter.log.error('Cannot set object: ' + err); if (!--count && callback) callback(); }); } else { (function (__id) { setTimeout(function () { $('tr[data-channel="' + __id + '"]').addClass('show-pair-off'); }, 100); })(pairs[j]); } } if (!count && callback) callback(); } else if (callback){ callback (); } } function load(settings, onChange) { if (!settings) return; devices = settings.devices || []; for (var key in settings) { if (!settings.hasOwnProperty(key)) continue; setValue(key, settings[key], onChange); } $('#enableInclusion').button().hide(); $('#tabs').show().tabs({ activate: function (event, ui) { if (ui.newPanel.attr('id') === 'tabs-3') { $('#drop-zone').show().css({opacity: 1}).animate({opacity: 0}, 2000, function () { $('#drop-zone').hide().css({opacity: 1}); }); } } }); setTimeout(function () { socket.emit('getState', adapter + '.' + instance + '.inclusionOn', function (err, state) { if (err) window.alert(err); $('#enableInclusion').button('option', 'label', state && state.val ? _('Disable inclusion mode') : _('Enable inclusion mode')).show().click(function () { socket.emit('getState', adapter + '.' + instance + '.inclusionOn', function (err, state) { state = state || {val: false}; state.val = !state.val; $('#enableInclusion').button('option', 'label', state && state.val ? _('Disable inclusion mode') : _('Enable inclusion mode')); socket.emit('setState', adapter + '.' + instance + '.inclusionOn', { val: state.val, ack: false }, function (err) { if (!err) { showMessage(state.val ? _('Inclusion mode is active') : _('Inclusion mode is inactive')); } else { showError(err); } }); }); }); }); }, 1000); getIsAdapterAlive(function (_isAlive) { isAlive = _isAlive || common.enabled; if (isAlive) { $('.button-erase-all').button().click(function () { confirmMessage(_('All RFY Devices will be deleted from RfxCom!'), _('Are you sure?'), 'question', function (result) { if (result) { sendTo(null, 'eraseAll', null, function (result) { if (result && result.result) { showMessage(_(result.result), _('Result')); } }); } }); }); $('#not-started').hide(); getComPorts(settings.comName); } else { $('.button-erase-all').prop('disabled', true); $('#not-started').show(); } }); socket.emit('getObjectView', 'system', 'channel', {startkey: 'rfxcom.' + instance + '.', endkey: 'rfxcom.' + instance + '.\u9999', include_docs: true}, function (err, res) { socket.emit('getObjectView', 'system', 'state', {startkey: 'rfxcom.' + instance + '.', endkey: 'rfxcom.' + instance + '.\u9999', include_docs: true}, function (err, _states) { socket.emit('getStates', function (err, _values) { var namespace = 'rfxcom.' + instance + '.'; var len = namespace.length; for (var id in _values) { if (_values.hasOwnProperty(id) && id.substring(0, len) === namespace) { values[id] = _values[id]; } } if (_states && _states.rows && _states.rows.length) { for (var j = 0; j < _states.rows.length; j++) { states[_states.rows[j].id] = _states.rows[j].value; } } res.rows.sort(function (a, b) { if (a.value.native.brand > b.value.native.brand) return 1; if (a.value.native.brand < b.value.native.brand) return -1; return 0; }); for (var i = 0; i < res.rows.length; i++) { showDevice(res.rows[i].value); } updateVersion(); checkAutoRepairs(); setInterval(updateIntervals, 10000); }); }); }); socket.on('objectChange', function (id, obj) { if (!obj) { $('tr[data-channel="' + id + '"]').remove(); if (channels[id]) { console.log('Device deleted: ' + id); delete channels[id]; checkAutoRepairs(); } else if (states[id]) { console.log('State deleted: ' + id); delete states[id]; } } else { if (obj.type === 'channel' && obj.native && obj.native.brand) { if (channels[id]) { console.log('Device changed: ' + id + ': ' + JSON.stringify(obj.native)); } else { console.log('New device: ' + id + ': ' + JSON.stringify(obj.native)); } showDevice(obj); checkAutoRepairs(); } else if (obj.type === 'state' && obj.native && obj.native.brand) { if (states[id]) { console.log('State changed: ' + id + ': ' + JSON.stringify(obj.native)); } else { console.log('New State: ' + id + ': ' + JSON.stringify(obj.native)); } states[id] = obj; } } }); socket.on('stateChange', function (id, state) { if (id === 'rfxcom.' + instance + '.firmwareVersion' || id === 'rfxcom.' + instance + '.availableVersion') { values[id] = state; updateVersion(); } else if (id === 'rfxcom.' + instance + '.inclusionOn') { values[id] = state; $('#enableInclusion').button('option', 'label', state && state.val ? _('Disable inclusion mode') : _('Enable inclusion mode')); } else if (id === 'rfxcom.' + instance + '.rawData') { // analyse rawData if (!state || !state.val || !state.ack || typeof state.val !== 'string') return; // received something like: // UPM/Esic;ID=0001;TEMP=00cf;HUM=16;BAT=OK; /*var frame = parseString(state.val); if (!frame) return; console.log('Incoming data: ' + JSON.stringify(frame)); for (var _id in channels) { if (!channels.hasOwnProperty(_id) || !channels[_id].native) continue; if (channels[_id].native.brand === frame.brandRaw && channels[_id].native.ID === frame.ID && (frame.SWITCH === undefined || (channels[_id].native.switches && channels[_id].native.switches.indexOf(frame.SWITCH) !== -1)) ) { delete frame.ID; delete frame.brand; delete frame.brandRaw; var text = getIntervalAsText(_id) + ', '; text += '<span class="highlight">' + stringifyFrame(frame) + '</span>'; $('.raw-value[data-id="' + _id + '"]').html(text); return; } } */ } else { if (id.substring(0, ('rfxcom.' + instance + '.').length) !== 'rfxcom.' + instance + '.') return; values[id] = state; var pos = id.lastIndexOf('.'); id = id.substring(0, pos); if (id === 'rfxcom.' + instance + '.info' || id === 'rfxcom.' + instance + '.rawData') return; if (timers[id]) clearTimeout(timers[id]); (function (_id) { timers[_id] = setTimeout(function () { timers[_id] = null; $('.raw-value[data-id="' + _id + '"]').html('<span class="highlight">' + showValue(_id, true) + '</span>'); }, 200); })(id); } }); //socket.emit('subscribeObjects', 'rfxcom.' + instance + '.*'); //socket.emit('subscribeStates', 'rfxcom.' + instance + '.*'); $('#comName').change(function () { socket.emit('getState', adapter + '.' + instance + '.inclusionOn', function (err, state) { if (!state || !state.val) { $('#enableInclusion').button('option', 'label', _('Disable inclusion mode')); socket.emit('setState', adapter + '.' + instance + '.inclusionOn', { val: true, ack: false }, function (err) { if (!err) { showMessage(state.val ? _('Inclusion mode is active') : _('Inclusion mode is inactive')); } else { showError(err); } }); } }); }); values2table('values', devices, onChange); var $code = $('th[data-name="unitCode"]'); $code.attr('title', _($code.attr('title'))); $code = $('th[data-name="deviceId"]'); $code.attr('title', _($code.attr('title'))); onChange(false); } function tableEvents(id, elem, command) { if (!isAlive) { showMessage(_('Fisrt start the instance'), _('Message'), 'alert'); return; } if (command === 'pair') { sendTo(null, 'program', elem, function (result) { if (result && result.result) { showMessage(_(result.result), _('Result')); } }); } else if (command === 'unpair') { sendTo(null, 'erase', elem, function (result) { if (result && result.result) { showMessage(_(result.result), _('Result')); } }); } } function save(callback) { var obj = {}; $('.value').each(function () { var $this = $(this); if ($this.attr('type') === 'checkbox') { obj[$this.attr('id')] = $this.prop('checked'); } else { obj[$this.attr('id')] = $this.val(); } }); obj.devices = table2values('values'); callback(obj); } </script> <style type="text/css"> .tab1 { border: 1px solid blue; width: 95%; overflow: auto; } .help { font-size: small; } </style> <div id="adapter-container" style="width: 100%; height: 100%; overflow: hidden"> <div id="tabs" style="width: 100%; height: 100%; overflow: hidden; display: none;"> <ul> <li><a href="#tabs-1" class="translate">Options</a></li> <li><a href="#tabs-2" class="translate">Devices</a></li> <li><a href="#tabs-3" class="translate">Control only devices</a></li> </ul> <div id="tabs-1"> <table><tr><td><img src="rfxcom.png"></td><td><h3 class="translate">RfxCom adapter settings</h3></td></tr></table> <table> <tr class="serial"><td><label for="comName" class="translate">Com port:</label></td><td><select class="value oneWidth" id="comName"></select></td><td></td></tr> <tr><td><label for="inclusionTimeout" class="translate">Inclusion mode timeout:</label></td><td><input class="value oneWidth" id="inclusionTimeout" /></td><td><span class="translate">inclusion help</span></td></tr> <tr><td></td><td colspan="2"><button id="enableInclusion" class="translateB">Enable inclusion mode</button><div id="inclusionState"></div></td></tr> </table> </div> <div id="tabs-2" style="width: 100%; height: calc(100% - 40px); overflow-x: hidden; overflow-y: auto; padding: 0"> <table style="width: 100%;"> <thead> <tr class="ui-widget-header"> <td style="width: 100px; text-align: center; white-space: nowrap" class="translate">Brand</td> <td style="width: 70px; text-align: center; white-space: nowrap" class="translate">Number</td> <td style="width: 50px; text-align: center; white-space: nowrap" class="translate">Auto re-pair</td> <td style="width: 200px; text-align: left; white-space: nowrap" class="translate">Values</td> <td style="width: 30px; text-align: center; white-space: nowrap" class="translate">Pair</td> <td style="width: 30px; text-align: center; white-space: nowrap" class="translate"></td> </tr> </thead> <tbody id="devices"> </tbody> </table> </div> <div id="tabs-3" style="width: 100%; height: calc(100% - 40px); overflow-x: hidden; overflow-y: auto; padding: 0"> <button class="button-erase-all" style="margin-left: 10px"></button> <div id="values" style="width: 100%; height: calc(100% - 230px)"> <button class="table-button-add" style="margin-left: 10px; width: 1.5em; height: 1.5em"></button> <div style="width: 100%; height: calc(100% - 30px); overflow: auto;"> <table class="table-values" style="width: 100%;"> <thead> <tr> <th data-name="_index" style="width: 40px" class="translate"></th> <th data-name="name" style="width: 30%" class="translate">Name</th> <th data-name="type" style="width: 100px" data-type="select" data-options="RFY;Lighting3;Curtain1" class="translate">Type</th> <th data-name="deviceId" style="width: 60px" class="translate" title="Device ID for RFY or House code for Curtain or System code for lighting 3">Device ID</th> <th data-name="unitCode" style="width: 30px" class="translate" title="Unit code for RFY and Curtain or channel code for Lighting3">Code</th> <th data-name="subType" style="width: 100px" data-type="select" data-options="RFY;RFYEXT;ASA" class="translate">Sub-type</th> <th data-buttons="pair unpair delete" style="width: 120px"></th> </tr> </thead> </table> </div> </div> </div> </div> </div> </html>