diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/sensors/index.scss b/platypush/backend/http/static/css/source/webpanel/plugins/sensors/index.scss new file mode 100644 index 00000000..586551ff --- /dev/null +++ b/platypush/backend/http/static/css/source/webpanel/plugins/sensors/index.scss @@ -0,0 +1,5 @@ +@import 'common/vars'; + +.sensors { +} + diff --git a/platypush/backend/http/static/js/media.js b/platypush/backend/http/static/js/media.js deleted file mode 100644 index e5588ebb..00000000 --- a/platypush/backend/http/static/js/media.js +++ /dev/null @@ -1,756 +0,0 @@ -$(document).ready(function() { - var $container = $('#video-container'), - $searchForm = $('#video-search'), - $videoResults = $('#video-results'), - $volumeCtrl = $('#video-volume-ctrl'), - $ctrlForm = $('#video-ctrl'), - $devsPanel = $('#media-devices-panel'), - $devsList = $devsPanel.find('.devices-list'), - $devsBtn = $('button[data-panel="#media-devices-panel"]'), - $devsBtnIcon = $('#media-devices-panel-icon'), - $devsRefreshBtn = $devsPanel.find('.refresh-devices'), - $searchBarContainer = $('#media-search-bar-container'), - $mediaBtnsContainer = $('#media-btns-container'), - $mediaItemPanel = $('#media-item-panel'), - $mediaSubtitlesModal = $('#media-subtitles-modal'), - $mediaSubtitlesResultsContainer = $mediaSubtitlesModal.find('.media-subtitles-results-container'), - $mediaSubtitlesResults = $mediaSubtitlesModal.find('.media-subtitles-results'), - $mediaSubtitlesMessage = $mediaSubtitlesModal.find('.media-subtitles-message'), - $mediaSearchSubtitles = $ctrlForm.find('[data-modal="#media-subtitles-modal"]'), - prevVolume = undefined, - selectedResource = undefined, - browserVideoWindow = undefined, - browserVideoElement = undefined; - - const onEvent = (event) => { - switch (event.args.type) { - case 'platypush.message.event.media.MediaPlayRequestEvent': - createNotification({ - 'icon': 'stream', - 'html': 'Processing media' + ('resource' in event.args - ? ' ' + event.args.resource : ''), - }); - break; - - case 'platypush.message.event.media.MediaPlayEvent': - createNotification({ - 'icon': 'play', - 'html': 'Starting media playback' + ('resource' in event.args - ? ' for ' + event.args.resource : ''), - }); - break; - - case 'platypush.message.event.media.MediaPauseEvent': - createNotification({ - 'icon': 'pause', - 'html': 'Media playback paused', - }); - break; - - case 'platypush.message.event.media.MediaStopEvent': - createNotification({ - 'icon': 'stop', - 'html': 'Media playback stopped', - }); - $mediaSearchSubtitles.hide(); - break; - } - }; - - const updateVideoResults = function(videos) { - $videoResults.html(''); - for (var video of videos) { - var $videoResult = $('
') - .addClass('video-result') - .attr('data-url', video['url']) - .attr('data-panel', '#media-item-panel') - .html('title' in video ? video['title'] : video['url']); - - var $icon = getVideoIconByUrl(video['url']); - $icon.prependTo($videoResult); - $videoResult.appendTo($videoResults); - } - }; - - const getVideoIconByUrl = function(url) { - var $icon = $(''); - - if (url.startsWith('file://')) { - $icon.addClass('fa-download'); - } else if (url.startsWith('https://www.youtube.com/')) { - $icon.addClass('fa-youtube'); - } else if (url.startsWith('magnet:?')) { - $icon.addClass('fa-film'); - } else { - $icon.addClass('fa-video'); - } - - var $iconContainer = $('').addClass('video-icon-container'); - $icon.appendTo($iconContainer); - return $iconContainer; - }; - - const getSelectedDevice = function() { - var device = { isBrowser: false, isRemote: false, name: undefined }; - var $remoteDevice = $devsPanel.find('.cast-device.selected') - .filter((i, dev) => !$(dev).data('local') && !$(dev).data('browser') && $(dev).data('name')); - var $browserDevice = $devsPanel.find('.cast-device.selected') - .filter((i, dev) => $(dev).data('browser')); - - if ($remoteDevice.length) { - device.isRemote = true; - device.name = $remoteDevice.data('name'); - } else if ($browserDevice.length) { - device.isBrowser = true; - } - - return device; - }; - - const startStreamingTorrent = function(torrent) { - // TODO support for subtitles download on torrent metadata received - return new Promise((resolve, reject) => { - execute( - { - type: 'request', - action: 'media.webtorrent.play', - args: { - resource: torrent, - download_only: true, - } - }, - - (response) => { - resolve(response.response.output.url); - }, - - (error) => { - reject(error); - } - ); - }); - }; - - const startStreaming = function(media, subtitles, relativeURIs) { - if (media.startsWith('magnet:?')) { - return new Promise((resolve, reject) => { - startStreamingTorrent(media) - .then((url) => { resolve({'url':url}); }) - .catch((error) => { reject(error); }); - }); - } - - return new Promise((resolve, reject) => { - $.ajax({ - type: 'PUT', - url: '/media', - contentType: 'application/json', - data: JSON.stringify({ - 'source': media, - 'subtitles': subtitles, - }) - }).done((response) => { - var url = response.url; - var subs; - if ('subtitles_url' in response) { - subs = response.subtitles_url; - } - - if (relativeURIs) { - url = url.match(/https?:\/\/[^\/]+(\/media\/.*)/)[1]; - if (subs) { - subs = subs.match(/https?:\/\/[^\/]+(\/media\/.*)/)[1]; - } - } - - var ret = { 'url': url, 'subtitles': undefined }; - if (subs) { - ret.subtitles = subs; - } - - resolve(ret); - }).fail((xhr) => { - reject(xhr.responseText); - }); - }); - }; - - const stopStreaming = function(media_id) { - return new Promise((resolve, reject) => { - $.ajax({ - type: 'DELETE', - url: '/media/' + media_id, - contentType: 'application/json', - }); - }); - }; - - const getSubtitles = function(resource) { - return new Promise((resolve, reject) => { - if (!window.config.media.subtitles || resource.startsWith('magnet:?')) { - resolve(); // media.subtitles plugin not configured - } - - run({ - action: 'media.subtitles.get_subtitles', - args: { 'resource': resource } - }).then((response) => { - resolve(response.response.output); - }).catch((error) => { - reject(error.message); - }); - }); - }; - - const downloadSubtitles = function(link, mediaResource, vtt=false) { - return new Promise((resolve, reject) => { - run({ - action: 'media.subtitles.download', - args: { - 'link': link, - 'media_resource': mediaResource, - 'convert_to_vtt': vtt, - } - }).then((response) => { - resolve(response.response.output.filename); - }).catch((error) => { - reject(error.message); - }); - }); - }; - - const setSubtitles = (filename) => { - return new Promise((resolve, reject) => { - run({ - action: 'media.set_subtitles', - args: { 'filename': filename } - }).then((response) => { - resolve(response.response.output); - }).catch((error) => { - reject(error.message); - }); - }); - }; - - const playOnChromecast = (resource, device, subtitles) => { - return new Promise((resolve, reject) => { - var requestArgs = { - action: 'media.chromecast.play', - args: { - 'chromecast': device, - }, - }; - - startStreaming(resource, subtitles).then((response) => { - requestArgs.args.resource = response.url; - // XXX subtitles currently break the Chromecast playback, - // see https://github.com/balloob/pychromecast/issues/74 - // if (response.subtitles) { - // requestArgs.args.subtitles = response.subtitles; - // } - - return run(requestArgs); - }).then((response) => { - if ('subtitles' in requestArgs) { - resolve(requestArgs.args.resource, requestArgs.args.subtitles); - } else { - resolve(requestArgs.args.resource); - } - }).catch((error) => { - reject(error); - }); - }); - }; - - const playInBrowser = (resource, subtitles) => { - return new Promise((resolve, reject) => { - startStreaming(resource, subtitles, true).then((response) => { - browserVideoWindow = window.open( - response.url + '?webplayer', '_blank'); - - browserVideoWindow.addEventListener('load', () => { - browserVideoElement = browserVideoWindow.document - .querySelector('#video-player'); - }); - - resolve(response.url, response.subtitles); - }).catch((error) => { - reject(error); - }); - }); - }; - - const playOnServer = (resource, subtitles) => { - return new Promise((resolve, reject) => { - var requestArgs = { - action: 'media.play', - args: { 'resource': resource }, - }; - - if (subtitles) { - requestArgs.args.subtitles = subtitles; - } - - run(requestArgs).then((response) => { - resolve(resource); - }).catch((error) => { - reject(error.message); - }); - }); - }; - - const _play = (resource, subtitles) => { - return new Promise((resolve, reject) => { - var playHndl; - var selectedDevice = getSelectedDevice(); - - if (selectedDevice.isBrowser) { - playHndl = playInBrowser(resource, subtitles); - } else if (selectedDevice.isRemote) { - playHndl = playOnChromecast(resource, selectedDevice.name, subtitles); - } else { - playHndl = playOnServer(resource, subtitles); - } - - playHndl.then((response) => { - resolve(resource); - }).catch((error) => { - showError('Playback error: ' + (error ? error : 'undefined')); - reject(error); - }); - }); - }; - - const play = (resource) => { - return new Promise((resolve, reject) => { - var results = $videoResults.html(); - var onVideoLoading = () => { $videoResults.text('Loading video...'); }; - var onVideoReady = () => { - $videoResults.html(results); - resolve(resource); - }; - - var defaultPlay = () => { _play(resource).finally(onVideoReady); }; - $mediaSearchSubtitles.data('resource', resource); - onVideoLoading(); - - var subtitlesConf = window.config.media.subtitles; - - if (subtitlesConf) { - populateSubtitlesModal(resource).then((subs) => { - if ('language' in subtitlesConf) { - if (subs && subs.length) { - downloadSubtitles(subs[0].SubDownloadLink, resource).then((subtitles) => { - _play(resource, subtitles).finally(onVideoReady); - resolve(resource, subtitles); - }).catch((error) => { - defaultPlay(); - resolve(resource); - }); - } else { - defaultPlay(); - resolve(resource); - } - } else { - defaultPlay(); - resolve(resource); - } - }); - } else { - defaultPlay(); - resolve(resource); - } - }); - }; - - const download = function(resource) { - return new Promise((resolve, reject) => { - var results = $videoResults.html(); - var onVideoLoading = function() { - $videoResults.text('Loading video...'); - }; - - var onVideoReady = function() { - $videoResults.html(results); - }; - - onVideoLoading(); - startStreaming(resource, undefined, true) - .then((response) => { - var url = response.url + '?download' - window.open(url, '_blank'); - resolve(url); - }) - .catch((error) => { - reject(error); - }) - .finally(() => { - onVideoReady(); - }); - }); - }; - - const populateSubtitlesModal = (resource) => { - return new Promise((resolve, reject) => { - $mediaSubtitlesMessage.text('Loading subtitles...'); - $mediaSubtitlesResults.text(''); - $mediaSubtitlesMessage.show(); - $mediaSubtitlesResultsContainer.hide(); - - getSubtitles(resource).then((subs) => { - if (!subs || !subs.length) { - $mediaSubtitlesMessage.text('No subtitles found'); - resolve(); - } - - $mediaSearchSubtitles.show(); - for (var sub of subs) { - var flagCode; - if ('ISO639' in sub) { - switch(sub.ISO639) { - case 'en': flagCode = 'gb'; break; - default: flagCode = sub.ISO639; break; - } - } - - var $subContainer = $('').addClass('row media-subtitle-container') - .data('download-link', sub.SubDownloadLink) - .data('resource', resource); - - var $subFlagIconContainer = $('').addClass('one column'); - var $subFlagIcon = $('') - .addClass(flagCode ? 'flag-icon flag-icon-' + flagCode : ( - sub.IsLocal ? 'fa fa-download' : '')) - .text(!(flagCode || sub.IsLocal) ? '?' : ''); - - var $subMovieName = $('').addClass('five columns') - .text(sub.MovieName); - - var $subFileName = $('').addClass('six columns') - .text(sub.SubFileName); - - $subFlagIcon.appendTo($subFlagIconContainer); - $subFlagIconContainer.appendTo($subContainer); - $subMovieName.appendTo($subContainer); - $subFileName.appendTo($subContainer); - $subContainer.appendTo($mediaSubtitlesResults); - } - - $mediaSubtitlesMessage.hide(); - $mediaSubtitlesResultsContainer.show(); - resolve(subs); - }).catch((error) => { - $mediaSubtitlesMessage.text('Unable to load subtitles: ' + error); - reject(error); - }); - }); - }; - - const setBrowserPlayerSubs = (resource, subtitles) => { - var mediaId; - if (!browserVideoElement) { - showError('No video is currently playing in the browser'); - return; - } - - return new Promise((resolve, reject) => { - $.get('/media').then((media) => { - for (var m of media) { - if (m.source === resource) { - mediaId = m.media_id; - break; - } - } - - if (!mediaId) { - reject(resource + ' is not a registered media'); - return; - } - - return $.ajax({ - type: 'POST', - url: '/media/subtitles/' + mediaId + '.vtt', - contentType: 'application/json', - data: JSON.stringify({ - 'filename': subtitles, - }), - }); - }).then(() => { - resolve(resource, subtitles); - }).catch((error) => { - reject('Cannot set subtitles for ' + resource + ': ' + error); - }); - }); - }; - - const setLocalPlayerSubs = (resource, subtitles) => { - return new Promise((resolve, reject) => { - run({ - action: 'media.remove_subtitles' - }).then((response) => { - return setSubtitles(subtitles); - }).then((response) => { - resolve(response); - }).catch((error) => { - reject(error); - }); - }); - }; - - const initBindings = function() { - window.registerEventListener(onEvent); - $searchForm.on('submit', function(event) { - var $input = $(this).find('input[name=video-search-text]'); - var resource = $input.val(); - var request = {} - var onSuccess = function() {}; - var onError = function() {}; - var onComplete = function() { - $input.prop('disabled', false); - }; - - $input.prop('disabled', true); - $mediaItemPanel.find('[data-action]').removeClass('disabled'); - $videoResults.text('Searching...'); - - if (resource.match(new RegExp('^https?://')) || - resource.match(new RegExp('^file://'))) { - var videos = [{ url: resource }]; - updateVideoResults(videos); - - request = { - type: 'request', - action: 'media.play', - args: { resource: resource } - }; - } else { - request = { - type: 'request', - action: 'media.search', - args: { query: resource } - }; - - onSuccess = function(response) { - var videos = response.response.output; - updateVideoResults(videos); - }; - } - - execute(request, onSuccess, onError, onComplete) - return false; - }); - - $ctrlForm.on('submit', function() { return false; }); - $ctrlForm.find('button[data-action]').on('click touch', function(evt) { - var action = $(this).data('action'); - var $btn = $(this); - - var requestArgs = { - type: 'request', - action: 'media.' + action, - }; - - var selectedDevice = getSelectedDevice(); - if (selectedDevice.isBrowser) { - return; // The in-browser player can be used to control media - } - - if (selectedDevice.isRemote) { - requestArgs.action = 'media.chromecast.' + action; - requestArgs.args = { 'chromecast': selectedDevice.name }; - } - - execute(requestArgs); - }); - - $volumeCtrl.on('mousedown touchstart', function(event) { - prevVolume = $(this).val(); - }); - - $volumeCtrl.on('mouseup touchend', function(event) { - var requestArgs = { - type: 'request', - action: 'media.set_volume', - args: { volume: $(this).val() }, - }; - - var selectedDevice = getSelectedDevice(); - if (selectedDevice.isRemote) { - requestArgs.action = 'media.chromecast.set_volume', - requestArgs.args.chromecast = selectedDevice.name; - } - - execute(requestArgs, - onSuccess=undefined, - onError = function() { - $volumeCtrl.val(prevVolume); - } - ); - }); - - $videoResults.on('mousedown touchstart', '.video-result', function() { - selectedResource = $(this).data('url'); - }); - - $videoResults.on('mouseup touchend', '.video-result', function(event) { - var $item = $(this); - var resource = $item.data('url'); - if (resource !== selectedResource) { - return; // Did not really click this item - } - - $item.siblings().removeClass('selected'); - $item.addClass('selected'); - - $mediaItemPanel.css('top', (event.clientY + $(window).scrollTop()) + 'px'); - $mediaItemPanel.css('left', (event.clientX + $(window).scrollLeft()) + 'px'); - $mediaItemPanel.data('resource', resource); - }); - - $devsBtn.on('click touch', function() { - $(this).toggleClass('selected'); - $devsPanel.css('top', ($(this).position().top + $(this).outerHeight()) + 'px'); - $devsPanel.css('left', ($(this).position().left) + 'px'); - return false; - }); - - $devsPanel.on('mouseup touchend', '.cast-device', function() { - if ($(this).hasClass('disabled')) { - return; - } - - var $devices = $devsPanel.find('.cast-device'); - var $curSelected = $devices.filter((i, d) => $(d).hasClass('selected')); - - if ($curSelected.data('name') !== $(this).data('name')) { - $curSelected.removeClass('selected'); - $(this).addClass('selected'); - $devsBtnIcon.attr('class', $(this).find('.fa').attr('class')); - - if ($(this).data('browser') || $(this).data('local')) { - $devsBtn.removeClass('remote'); - } else { - $devsBtn.addClass('remote'); - } - - // TODO Logic for switching destination on the fly - } - - $devsPanel.hide(); - $devsBtn.removeClass('selected'); - }); - - $devsRefreshBtn.on('click', function() { - if ($(this).hasClass('disabled')) { - return; - } - - $(this).addClass('disabled'); - initRemoteDevices(); - }); - - $mediaItemPanel.on('click', '[data-action]', function() { - var $action = $(this); - if ($action.hasClass('disabled')) { - return; - } - - var action = $action.data('action'); - var resource = $mediaItemPanel.data('resource'); - if (!resource) { - return; - } - - $mediaItemPanel.hide(); - $mediaItemPanel.find('[data-action]').addClass('disabled'); - - eval(action)($mediaItemPanel.data('resource')) - .finally(() => { - $mediaItemPanel.find('[data-action]').removeClass('disabled'); - }); - }); - - $mediaSubtitlesModal.on('mouseup touchend', '.media-subtitle-container', (event) => { - var resource = $(event.currentTarget).data('resource'); - var link = $(event.currentTarget).data('downloadLink'); - var selectedDevice = getSelectedDevice(); - - if (selectedDevice.isRemote) { - showError('Changing subtitles at runtime on Chromecast is not yet supported'); - return; - } - - var convertToVTT = selectedDevice.isBrowser; - - downloadSubtitles(link, resource, convertToVTT).then((subtitles) => { - if (selectedDevice.isBrowser) { - return setBrowserPlayerSubs(resource, subtitles); - } else { - return setLocalPlayerSubs(resource, subtitles); - } - }).catch((error) => { - console.warning('Could not load subtitles ' + link + - ' to the player: ' + (error || 'undefined error')) - }); - }); - }; - - const initRemoteDevices = function() { - $devsList.find('.cast-device[data-remote]').addClass('disabled'); - - execute( - { - type: 'request', - action: 'media.chromecast.get_chromecasts', - }, - - onSuccess = function(results) { - $devsList.find('.cast-device[data-remote]').remove(); - $devsRefreshBtn.removeClass('disabled'); - - if (!results || results.response.errors.length) { - return; - } - - results = results.response.output; - for (var cast of results) { - var $cast = $('').addClass('row cast-device') - .addClass('cast-device-' + cast.type).attr('data-remote', true) - .data('name', cast.name); - - var icon = 'question'; - switch (cast.type) { - case 'cast': icon = 'tv'; break; - case 'audio': icon = 'volume-up'; break; - } - - var $castIcon = $('').addClass('fa fa-' + icon) - .addClass('cast-device-icon'); - var $castName = $('').addClass('cast-device-name') - .text(cast.name); - - var $iconContainer = $('').addClass('two columns'); - var $nameContainer = $('').addClass('ten columns'); - $castIcon.appendTo($iconContainer); - $castName.appendTo($nameContainer); - - $iconContainer.appendTo($cast); - $nameContainer.appendTo($cast); - $cast.appendTo($devsList); - } - }, - - onComplete = function() { - $devsRefreshBtn.removeClass('disabled'); - } - ); - }; - - const init = function() { - initRemoteDevices(); - initBindings(); - }; - - init(); -}); - diff --git a/platypush/backend/http/static/js/plugins/sensors/index.js b/platypush/backend/http/static/js/plugins/sensors/index.js new file mode 100644 index 00000000..87dc1d89 --- /dev/null +++ b/platypush/backend/http/static/js/plugins/sensors/index.js @@ -0,0 +1,61 @@ +Vue.component('sensor-metric', { + template: '#tmpl-sensor-metric', + props: { + bus: { + type: Object, + }, + + device: { + type: Object, + default: () => {}, + }, + }, +}); + +Vue.component('sensors', { + template: '#tmpl-sensors', + props: ['config'], + + data: function() { + return { + bus: new Vue({}), + metrics: {}, + }; + }, + + methods: { + refresh: async function() { + if (!this.config.plugins) { + console.warn('Please specify a list of sensor plugins in your sensors section configuration'); + return; + } + + const promises = this.config.plugins.map(plugin => { + return new Promise((resolve, reject) => { + request(plugin + '.get_measurement').then(metrics => { + resolve(metrics); + }); + }); + }); + + this.metrics = (await Promise.all(promises)).reduce((obj, metrics) => { + for (const [name, value] of Object.entries(metrics)) { + obj[name] = value; + } + + return obj; + }, {}); + }, + + onSensorEvent: function(event) { + const data = event.data; + this.metrics[data.name] = data.value; + }, + }, + + mounted: function() { + this.refresh(); + registerEventHandler(this.onSensorEvent, 'platypush.message.event.sensor.SensorDataChangeEvent'); + }, +}); + diff --git a/platypush/backend/http/static/js/switch.switchbot.js b/platypush/backend/http/static/js/switch.switchbot.js deleted file mode 100644 index a3e7b1e6..00000000 --- a/platypush/backend/http/static/js/switch.switchbot.js +++ /dev/null @@ -1,33 +0,0 @@ -$(document).ready(function() { - var switches, - $switchbotContainer = $('#switchbot-container'); - - var initBindings = function() { - $switchbotContainer.on('click touch', '.switch-ctrl-container', function() { - var $input = $(this).find('.switch-ctrl'); - var addr = $input.attr('name'); - $input.prop('checked', true); - - execute( - { - type: 'request', - action: 'switch.switchbot.press', - args: { - device: addr - } - }, - - onSuccess = function(response) { - $input.prop('checked', false); - } - ); - }); - }; - - var init = function() { - initBindings(); - }; - - init(); -}); - diff --git a/platypush/backend/http/static/js/switch.tplink.js b/platypush/backend/http/static/js/switch.tplink.js deleted file mode 100644 index 8bca00e4..00000000 --- a/platypush/backend/http/static/js/switch.tplink.js +++ /dev/null @@ -1,77 +0,0 @@ -$(document).ready(function() { - var switches, - $tplinkContainer = $('#tplink-container'); - - var createPowerToggleElement = function(dev) { - var $powerToggle = $('').addClass('toggle toggle--push switch-ctrl-container'); - var $input = $('').attr('type', 'checkbox') - .data('name', dev.host).attr('name', dev.alias).addClass('toggle--checkbox switch-ctrl'); - - var $label = $('').attr('for', dev.alias).addClass('toggle--btn'); - - $input.appendTo($powerToggle); - $label.appendTo($powerToggle); - - if (dev.on) { - $input.prop('checked', true); - } - - return $powerToggle; - }; - - var updateDevices = function(devices) { - for (var dev of devices) { - var $dev = $('').addClass('row tplink-device').data('name', dev.alias); - var $devName = $('').addClass('ten columns name').text(dev.alias); - var $toggleContainer = $('').addClass('two columns toggle-container'); - var $toggle = createPowerToggleElement(dev); - - $toggle.appendTo($toggleContainer); - $devName.appendTo($dev); - $toggleContainer.appendTo($dev); - $dev.appendTo($tplinkContainer); - } - }; - - var initWidget = function() { - execute( - { - type: 'request', - action: 'switch.tplink.status' - }, - - onSuccess = function(response) { - updateDevices(Object.values(response.response.output.devices)); - } - ); - }; - - var initBindings = function() { - $tplinkContainer.on('click touch', '.switch-ctrl-container', function() { - var $input = $(this).find('.switch-ctrl'); - var devAddr = $input.data('name'); - var action = $input.prop('checked') ? 'off' : 'on'; - - execute( - { - type: 'request', - action: 'switch.tplink.' + action, - args: { device: devAddr } - }, - - onSuccess = function(response) { - var status = response.response.output.status; - $input.prop('checked', status == 'on' ? true : false); - } - ); - }); - }; - - var init = function() { - initWidget(); - initBindings(); - }; - - init(); -}); - diff --git a/platypush/backend/http/static/js/switch.wemo.js b/platypush/backend/http/static/js/switch.wemo.js deleted file mode 100644 index 01b2a93d..00000000 --- a/platypush/backend/http/static/js/switch.wemo.js +++ /dev/null @@ -1,78 +0,0 @@ -$(document).ready(function() { - var switches, - $wemoContainer = $('#wemo-container'); - - var createPowerToggleElement = function(dev) { - var $powerToggle = $('').addClass('toggle toggle--push switch-ctrl-container'); - var $input = $('').attr('type', 'checkbox') - .attr('name', dev.name).addClass('toggle--checkbox switch-ctrl'); - - var $label = $('').attr('for', dev.name).addClass('toggle--btn'); - - $input.appendTo($powerToggle); - $label.appendTo($powerToggle); - - if (dev.state == 1) { - $input.prop('checked', true); - } - - return $powerToggle; - }; - - var updateDevices = function(devices) { - for (var dev of devices) { - var $dev = $('').addClass('row wemo-device').data('name', dev.name); - var $devName = $('').addClass('ten columns name').text(dev.name); - var $toggleContainer = $('').addClass('two columns toggle-container'); - var $toggle = createPowerToggleElement(dev); - - $toggle.appendTo($toggleContainer); - $devName.appendTo($dev); - $toggleContainer.appendTo($dev); - $dev.appendTo($wemoContainer); - } - }; - - var initWidget = function() { - execute( - { - type: 'request', - action: 'switch.wemo.get_devices' - }, - - onSuccess = function(response) { - updateDevices(response.response.output.devices); - } - ); - }; - - var initBindings = function() { - $wemoContainer.on('click touch', '.switch-ctrl-container', function() { - var $input = $(this).find('.switch-ctrl'); - var devName = $input.attr('name'); - - execute( - { - type: 'request', - action: 'switch.wemo.toggle', - args: { - device: devName - } - }, - - onSuccess = function(response) { - var state = response.response.output.state; - $input.prop('checked', !!state); - } - ); - }); - }; - - var init = function() { - initWidget(); - initBindings(); - }; - - init(); -}); - diff --git a/platypush/backend/http/static/js/tts.google.js b/platypush/backend/http/static/js/tts.google.js deleted file mode 100644 index 5330d2b6..00000000 --- a/platypush/backend/http/static/js/tts.google.js +++ /dev/null @@ -1,34 +0,0 @@ -$(document).ready(function() { - var $container = $('#tts-container'), - $ttsForm = $('#tts-form'); - - var initBindings = function() { - $ttsForm.on('submit', function(event) { - var formData = $(this).serializeArray().reduce(function(obj, item) { - var value = item.value.trim(); - if (value.length > 0) { - obj[item.name] = item.value; - } - - return obj; - }, {}); - - execute( - { - type: 'request', - action: 'tts.google.say', - args: formData, - } - ); - - return false; - }); - }; - - var init = function() { - initBindings(); - }; - - init(); -}); - diff --git a/platypush/backend/http/static/js/tts.js b/platypush/backend/http/static/js/tts.js deleted file mode 100644 index bac32f32..00000000 --- a/platypush/backend/http/static/js/tts.js +++ /dev/null @@ -1,34 +0,0 @@ -$(document).ready(function() { - var $container = $('#tts-container'), - $ttsForm = $('#tts-form'); - - var initBindings = function() { - $ttsForm.on('submit', function(event) { - var formData = $(this).serializeArray().reduce(function(obj, item) { - var value = item.value.trim(); - if (value.length > 0) { - obj[item.name] = item.value; - } - - return obj; - }, {}); - - execute( - { - type: 'request', - action: 'tts.say', - args: formData, - } - ); - - return false; - }); - }; - - var init = function() { - initBindings(); - }; - - init(); -}); - diff --git a/platypush/backend/http/templates/nav.html b/platypush/backend/http/templates/nav.html index 8cf60afd..c26ccf3a 100644 --- a/platypush/backend/http/templates/nav.html +++ b/platypush/backend/http/templates/nav.html @@ -7,6 +7,7 @@ 'media.vlc': 'fa fa-film', 'music.mpd': 'fa fa-music', 'music.snapcast': 'fa fa-volume-up', + 'sensors': 'fa fa-temperature-half', 'switches': 'fa fa-toggle-on', 'tts': 'fa fa-comment', 'tts.google': 'fa fa-comment', diff --git a/platypush/backend/http/templates/plugins/sensors/index.html b/platypush/backend/http/templates/plugins/sensors/index.html new file mode 100644 index 00000000..43a1403e --- /dev/null +++ b/platypush/backend/http/templates/plugins/sensors/index.html @@ -0,0 +1,24 @@ + + + diff --git a/platypush/backend/sensor/__init__.py b/platypush/backend/sensor/__init__.py index 1ce1f86f..f9a92a2c 100644 --- a/platypush/backend/sensor/__init__.py +++ b/platypush/backend/sensor/__init__.py @@ -12,13 +12,18 @@ class SensorBackend(Backend): Triggers: * :class:`platypush.message.event.sensor.SensorDataChangeEvent` if the measurements of a sensor have changed - * :class:`platypush.message.event.sensor.SensorDataAboveThresholdEvent` if the measurements of a sensor have gone above a configured threshold - * :class:`platypush.message.event.sensor.SensorDataBelowThresholdEvent` if the measurements of a sensor have gone below a configured threshold + * :class:`platypush.message.event.sensor.SensorDataAboveThresholdEvent` if the measurements of a sensor have + gone above a configured threshold + * :class:`platypush.message.event.sensor.SensorDataBelowThresholdEvent` if the measurements of a sensor have + gone below a configured threshold """ - def __init__(self, thresholds=None, poll_seconds=None, *args, **kwargs): + def __init__(self, thresholds=None, poll_seconds=None, **kwargs): """ - :param thresholds: Thresholds can be either a scalar value or a dictionary (e.g. ``{"temperature": 20.0}``). Sensor threshold events will be fired when measurements get above or below these values. Set it as a scalar if your get_measurement() code returns a scalar, as a dictionary if it returns a dictionary of values. + :param thresholds: Thresholds can be either a scalar value or a dictionary (e.g. ``{"temperature": 20.0}``). + Sensor threshold events will be fired when measurements get above or below these values. + Set it as a scalar if your get_measurement() code returns a scalar, as a dictionary if it returns a + dictionary of values. For instance, if your sensor code returns both humidity and temperature in a format like ``{'humidity':60.0, 'temperature': 25.0}``, @@ -26,7 +31,8 @@ class SensorBackend(Backend): ``{'temperature':20.0}`` to trigger events when the temperature goes above/below 20 degrees. - :param poll_seconds: If set, the thread will wait for the specificed number of seconds between a read and the next one. + :param poll_seconds: If set, the thread will wait for the specified number of seconds between a read and the + next one. :type poll_seconds: float """ @@ -37,7 +43,7 @@ class SensorBackend(Backend): self.poll_seconds = poll_seconds def get_measurement(self): - """ To be implemented in the derived classes """ + """ To be implemented by derived classes """ raise NotImplementedError('To be implemented in a derived class') def run(self): @@ -75,7 +81,6 @@ class SensorBackend(Backend): if data_above_threshold: self.bus.post(SensorDataAboveThresholdEvent(data=data_above_threshold)) - self.data = new_data if self.poll_seconds: @@ -83,4 +88,3 @@ class SensorBackend(Backend): # vim:sw=4:ts=4:et: - diff --git a/platypush/backend/sensor/leap.py b/platypush/backend/sensor/leap.py index 9cab2f14..5b8c09d1 100644 --- a/platypush/backend/sensor/leap.py +++ b/platypush/backend/sensor/leap.py @@ -42,11 +42,7 @@ class SensorLeapBackend(Backend): _listener_proc = None def __init__(self, - position_ranges=[ - [-300.0, 300.0], # x axis - [25.0, 600.0], # y axis - [-300.0, 300.0], # z axis - ], + position_ranges=None, position_tolerance=0.0, # Position variation tolerance in % frames_throttle_secs=None, *args, **kwargs): @@ -76,11 +72,17 @@ class SensorLeapBackend(Backend): super().__init__(*args, **kwargs) + if position_ranges is None: + position_ranges = [ + [-300.0, 300.0], # x axis + [25.0, 600.0], # y axis + [-300.0, 300.0], # z axis + ] + self.position_ranges = position_ranges self.position_tolerance = position_tolerance self.frames_throttle_secs = frames_throttle_secs - def run(self): super().run() @@ -123,8 +125,8 @@ class LeapFuture(Timer): class LeapListener(Leap.Listener): def __init__(self, position_ranges, position_tolerance, logger, - frames_throttle_secs=None, *args, **kwargs): - super().__init__(*args, **kwargs) + frames_throttle_secs=None): + super().__init__() self.prev_frame = None self.position_ranges = position_ranges @@ -133,26 +135,24 @@ class LeapListener(Leap.Listener): self.logger = logger self.running_future = None - def _send_event(self, event): backend = get_backend('redis') if not backend: - self.logger.warning('Redis backend not configured, I cannot propagate the following event: {}'.format(event)) + self.logger.warning('Redis backend not configured, I cannot propagate the following event: {}'. + format(event)) return backend.send_message(event) - def send_event(self, event): if self.frames_throttle_secs: if not self.running_future or not self.running_future.is_alive(): self.running_future = LeapFuture(seconds=self.frames_throttle_secs, - listener=self, event=event) + listener=self, event=event) self.running_future.start() else: self._send_event(event) - def on_init(self, controller): self.prev_frame = None self.logger.info('Leap controller listener initialized') @@ -216,7 +216,7 @@ class LeapListener(Leap.Listener): ] def _normalize_position(self, position): - # Normalize absolute position onto a semisphere centered in (0,0) + # Normalize absolute position onto a hemisphere centered in (0,0) # having x_range = z_range = [-100, 100], y_range = [0, 100] return [ @@ -225,13 +225,15 @@ class LeapListener(Leap.Listener): self._scale_scalar(value=position[2], range=self.position_ranges[2], new_range=[-100.0, 100.0]), ] + @staticmethod + def _scale_scalar(value, range, new_range): + if value < range[0]: + value=range[0] + if value > range[1]: + value=range[1] - def _scale_scalar(self, value, range, new_range): - if value < range[0]: value=range[0] - if value > range[1]: value=range[1] return ((new_range[1]-new_range[0])/(range[1]-range[0]))*(value-range[0]) + new_range[0] - def _position_changed(self, old_position, new_position): return ( abs(old_position[0]-new_position[0]) > self.position_tolerance or @@ -240,4 +242,3 @@ class LeapListener(Leap.Listener): # vim:sw=4:ts=4:et: - diff --git a/platypush/backend/sensor/mcp3008.py b/platypush/backend/sensor/mcp3008.py index b6e2144e..dc08ef4a 100644 --- a/platypush/backend/sensor/mcp3008.py +++ b/platypush/backend/sensor/mcp3008.py @@ -20,4 +20,3 @@ class SensorMcp3008Backend(SensorBackend): # vim:sw=4:ts=4:et: - diff --git a/platypush/backend/sensor/serial.py b/platypush/backend/sensor/serial.py index 0c3e7976..722f8f6d 100644 --- a/platypush/backend/sensor/serial.py +++ b/platypush/backend/sensor/serial.py @@ -13,10 +13,9 @@ class SensorSerialBackend(SensorBackend): """ def get_measurement(self): - """ Implemnetation of ``get_measurement`` """ + """ Implementation of ``get_measurement`` """ plugin = get_plugin('serial') return plugin.get_data().output # vim:sw=4:ts=4:et: - diff --git a/platypush/message/event/sensor/__init__.py b/platypush/message/event/sensor/__init__.py index e3944bf5..c145e075 100644 --- a/platypush/message/event/sensor/__init__.py +++ b/platypush/message/event/sensor/__init__.py @@ -47,4 +47,3 @@ class SensorDataBelowThresholdEvent(Event): # vim:sw=4:ts=4:et: - diff --git a/platypush/plugins/gpio/__init__.py b/platypush/plugins/gpio/__init__.py index bc413e35..919986c1 100644 --- a/platypush/plugins/gpio/__init__.py +++ b/platypush/plugins/gpio/__init__.py @@ -2,9 +2,6 @@ .. moduleauthor:: Fabio Manganiello