From 1eae45805d6e85f47413a4b1cef7e37be1d50829 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Tue, 2 Jul 2019 14:04:25 +0200 Subject: [PATCH] Added new sensors plugin to webpanel --- .../webpanel/plugins/sensors/index.scss | 5 + platypush/backend/http/static/js/media.js | 756 ------------------ .../http/static/js/plugins/sensors/index.js | 61 ++ .../http/static/js/switch.switchbot.js | 33 - .../backend/http/static/js/switch.tplink.js | 77 -- .../backend/http/static/js/switch.wemo.js | 78 -- .../backend/http/static/js/tts.google.js | 34 - platypush/backend/http/static/js/tts.js | 34 - platypush/backend/http/templates/nav.html | 1 + .../http/templates/plugins/sensors/index.html | 24 + platypush/backend/sensor/__init__.py | 20 +- platypush/backend/sensor/leap.py | 39 +- platypush/backend/sensor/mcp3008.py | 1 - platypush/backend/sensor/serial.py | 3 +- platypush/message/event/sensor/__init__.py | 1 - platypush/plugins/gpio/__init__.py | 66 +- .../gpio/sensor/accelerometer/__init__.py | 18 +- .../plugins/gpio/sensor/distance/__init__.py | 1 - .../plugins/gpio/sensor/mcp3008/__init__.py | 7 +- platypush/plugins/gpio/zeroborg/__init__.py | 41 +- platypush/plugins/serial/__init__.py | 23 +- 21 files changed, 203 insertions(+), 1120 deletions(-) create mode 100644 platypush/backend/http/static/css/source/webpanel/plugins/sensors/index.scss delete mode 100644 platypush/backend/http/static/js/media.js create mode 100644 platypush/backend/http/static/js/plugins/sensors/index.js delete mode 100644 platypush/backend/http/static/js/switch.switchbot.js delete mode 100644 platypush/backend/http/static/js/switch.tplink.js delete mode 100644 platypush/backend/http/static/js/switch.wemo.js delete mode 100644 platypush/backend/http/static/js/tts.google.js delete mode 100644 platypush/backend/http/static/js/tts.js create mode 100644 platypush/backend/http/templates/plugins/sensors/index.html 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 """ -import threading -import time - from platypush.plugins import Plugin, action @@ -16,7 +13,7 @@ class GpioPlugin(Plugin): * **RPi.GPIO** (`pip install RPi.GPIO`) """ - def __init__(self, pins=None, *args, **kwargs): + def __init__(self, pins=None, **kwargs): """ :param pins: Configuration for the GPIO PINs as a name -> pin_number map. :type pins: dict @@ -31,10 +28,10 @@ class GpioPlugin(Plugin): } """ - super().__init__(*args, **kwargs) + super().__init__(**kwargs) self.pins_by_name = pins if pins else {} - self.pins_by_number = { number:name - for (name, number) in self.pins_by_name.items() } + self.pins_by_number = {number: name + for (name, number) in self.pins_by_name.items()} def _get_pin_number(self, pin): try: @@ -46,99 +43,106 @@ class GpioPlugin(Plugin): return pin - @action - def write(self, pin, val): + def write(self, pin, value, name=None): """ Write a byte value to a pin. :param pin: PIN number or configured name :type pin: int or str - :param val: Value to write - :type val: int + :param name: Optional name for the written value (e.g. "temperature" or "humidity") + :type name: str + + :param value: Value to write + :type value: int :returns: dict Response:: output = { + "name": , "pin": , - "val": , + "value": , "method": "write" } """ import RPi.GPIO as gpio + name = name or pin pin = self._get_pin_number(pin) gpio.setmode(gpio.BCM) gpio.setup(pin, gpio.OUT) - gpio.output(pin, val) + gpio.output(pin, value) return { + 'name': name, 'pin': pin, - 'val': val, + 'value': value, 'method': 'write', } @action - def read(self, pin): + def read(self, pin, name=None): """ Reads a value from a PIN. :param pin: PIN number or configured name. :type pin: int or str + + :param name: Optional name for the read value (e.g. "temperature" or "humidity") + :type name: str + :returns: dict Response:: output = { + "name": , "pin": , - "val": , + "value": , "method": "read" } """ import RPi.GPIO as gpio + name = name or pin pin = self._get_pin_number(pin) gpio.setmode(gpio.BCM) gpio.setup(pin, gpio.IN) val = gpio.input(pin) return { + 'name': name, 'pin': pin, - 'val': val, + 'value': val, 'method': 'read', } + @action + def get_measurement(self, pin=None): + if pin is None: + return self.read_all() + return self.read(pin) + @action def read_all(self): """ - Reads the values from all the configured PINs and returns them as a list. It will raise a RuntimeError if no PIN mappings were configured. - :returns: list + Reads the values from all the configured PINs and returns them as a list. It will raise a RuntimeError if no + PIN mappings were configured. """ - import RPi.GPIO as gpio - if not self.pins_by_number: raise RuntimeError("No PIN mappings were provided/configured") values = [] for (pin, name) in self.pins_by_number.items(): - gpio.setmode(gpio.BCM) - gpio.setup(pin, gpio.IN) - val = gpio.input(pin) - - values.append({ - 'pin': pin, - 'name': name, - 'val': val, - }) + values.append(self.read(pin=pin, name=name).output) return values # vim:sw=4:ts=4:et: - diff --git a/platypush/plugins/gpio/sensor/accelerometer/__init__.py b/platypush/plugins/gpio/sensor/accelerometer/__init__.py index c4d4527f..cb4b4bd2 100644 --- a/platypush/plugins/gpio/sensor/accelerometer/__init__.py +++ b/platypush/plugins/gpio/sensor/accelerometer/__init__.py @@ -1,6 +1,3 @@ -import threading -import time - from platypush.plugins import action from platypush.plugins.gpio.sensor import GpioSensorPlugin @@ -23,7 +20,8 @@ class GpioSensorAccelerometerPlugin(GpioSensorPlugin): :param g: Accelerometer range as a multiple of G - can be 2G, 4G, 8G or 16G :type g: int - :param precision: If set, the position values will be rounded to the specified number of decimal digits (default: no rounding) + :param precision: If set, the position values will be rounded to the specified number of decimal digits + (default: no rounding) :type precision: int """ @@ -45,13 +43,13 @@ class GpioSensorAccelerometerPlugin(GpioSensorPlugin): self.sensor = LIS3DH() self.sensor.setRange(self.g) - @action def get_measurement(self): """ Extends :func:`.GpioSensorPlugin.get_measurement` - :returns: The sensor's current position as a dictionary with the three components (x,y,z) in degrees, each between -90 and 90 + :returns: The sensor's current position as a dictionary with the three components (x,y,z) in degrees, each + between -90 and 90 """ values = [ @@ -59,8 +57,12 @@ class GpioSensorAccelerometerPlugin(GpioSensorPlugin): for pos in (self.sensor.getX(), self.sensor.getY(), self.sensor.getZ()) ] - return { 'x': values[0], 'y': values[1], 'z': values[2] } + return { + 'name': 'position', + 'value': { + 'x': values[0], 'y': values[1], 'z': values[2] + } + } # vim:sw=4:ts=4:et: - diff --git a/platypush/plugins/gpio/sensor/distance/__init__.py b/platypush/plugins/gpio/sensor/distance/__init__.py index 78d81a39..5d2c0dca 100644 --- a/platypush/plugins/gpio/sensor/distance/__init__.py +++ b/platypush/plugins/gpio/sensor/distance/__init__.py @@ -1,4 +1,3 @@ -import threading import time from platypush.plugins import action diff --git a/platypush/plugins/gpio/sensor/mcp3008/__init__.py b/platypush/plugins/gpio/sensor/mcp3008/__init__.py index 80ffc8be..b946bac6 100644 --- a/platypush/plugins/gpio/sensor/mcp3008/__init__.py +++ b/platypush/plugins/gpio/sensor/mcp3008/__init__.py @@ -1,5 +1,4 @@ import enum -import time from platypush.plugins import action from platypush.plugins.gpio.sensor import GpioSensorPlugin @@ -105,14 +104,13 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin): self.channels = channels if channels else {} self.mcp = None - def _get_mcp(self): import Adafruit_GPIO.SPI as SPI import Adafruit_MCP3008 if self.mode == MCP3008Mode.SOFTWARE: self.mcp = Adafruit_MCP3008.MCP3008(clk=self.CLK, cs=self.CS, - miso=self.MISO, mosi=self.MOSI) + miso=self.MISO, mosi=self.MOSI) elif self.mode == MCP3008Mode.HARDWARE: self.mcp = Adafruit_MCP3008.MCP3008(spi=SPI.SpiDev(self.spi_port, self.spi_device)) else: @@ -120,11 +118,9 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin): return self.mcp - def _convert_to_voltage(self, value): return (value * self.Vdd) / 1023.0 if value is not None else None - @action def get_measurement(self): """ @@ -169,4 +165,3 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin): # vim:sw=4:ts=4:et: - diff --git a/platypush/plugins/gpio/zeroborg/__init__.py b/platypush/plugins/gpio/zeroborg/__init__.py index 34180802..5a0d036d 100644 --- a/platypush/plugins/gpio/zeroborg/__init__.py +++ b/platypush/plugins/gpio/zeroborg/__init__.py @@ -4,7 +4,6 @@ import time from platypush.plugins import Plugin, action from platypush.context import get_plugin -from platypush.config import Config class Direction(enum.Enum): @@ -16,6 +15,7 @@ class Direction(enum.Enum): DIR_AUTO_TOGGLE = 'auto_toggle' +# noinspection PyPep8Naming class GpioZeroborgPlugin(Plugin): """ ZeroBorg plugin. It allows you to control a ZeroBorg @@ -28,10 +28,12 @@ class GpioZeroborgPlugin(Plugin): _direction = None _init_in_progress = threading.Lock() - - def __init__(self, directions = {}, *args, **kwargs): + def __init__(self, directions=None, **kwargs): """ - :param directions: Configuration for the motor directions. A direction is basically a configuration of the power delivered to each motor to allow whichever object you're controlling (wheels, robotic arms etc.) to move in a certain direction. In my experience the ZeroBorg always needs a bit of calibration, depending on factory defaults and the mechanical properties of the load it controls. + :param directions: Configuration for the motor directions. A direction is basically a configuration of the + power delivered to each motor to allow whichever object you're controlling (wheels, robotic arms etc.) to + move in a certain direction. In my experience the ZeroBorg always needs a bit of calibration, depending on + factory defaults and the mechanical properties of the load it controls. Example configuration that I use to control a simple 4WD robot:: @@ -73,13 +75,23 @@ class GpioZeroborgPlugin(Plugin): } } - Note that the special direction "auto" can contain a configuration that allows your device to move autonomously based on the inputs it gets from some sensors. In this case, I set the sensors configuration (a list) to periodically poll a GPIO-based ultrasound distance sensor plugin. ``timeout`` says after how long a poll attempt should fail. The plugin package is specified through ``plugin`` (``gpio.sensor.distance``) in this case, note that the plugin must be configured as well in order to work). The ``threshold`` value says around which value your logic should trigger. In this case, threshold=400 (40 cm). When the distance value is above that threshold (``above_threshold_direction``), then go "up" (no obstacles ahead). Otherwise (``below_threshold_direction``), turn "left" (avoid the obstacle). + Note that the special direction "auto" can contain a configuration that allows your device to move autonomously + based on the inputs it gets from some sensors. In this case, I set the sensors configuration (a list) to + periodically poll a GPIO-based ultrasound distance sensor plugin. ``timeout`` says after how long a poll + attempt should fail. The plugin package is specified through ``plugin`` (``gpio.sensor.distance``) in this + case, note that the plugin must be configured as well in order to work). The ``threshold`` value says + around which value your logic should trigger. In this case, threshold=400 (40 cm). When the distance value + is above that threshold (``above_threshold_direction``), then go "up" (no obstacles ahead). Otherwise + (``below_threshold_direction``), turn "left" (avoid the obstacle). :type directions: dict """ + if directions is None: + directions = {} + import platypush.plugins.gpio.zeroborg.lib as ZeroBorg - super().__init__(*args, **kwargs) + super().__init__(**kwargs) self.directions = directions self.auto_mode = False @@ -90,8 +102,8 @@ class GpioZeroborgPlugin(Plugin): self.zb.SetCommsFailsafe(True) self.zb.ResetEpo() - - def _get_measurement(self, plugin, timeout): + @staticmethod + def _get_measurement(plugin, timeout): measure_start_time = time.time() value = None @@ -123,11 +135,10 @@ class GpioZeroborgPlugin(Plugin): direction = sensor['below_threshold_direction'] self.logger.info('Sensor: {}\tMeasurement: {}\tDirection: {}' - .format(sensor['plugin'], value, direction)) + .format(sensor['plugin'], value, direction)) return direction - @action def drive(self, direction): """ @@ -140,17 +151,12 @@ class GpioZeroborgPlugin(Plugin): """ - prev_direction = self._direction - self._can_run = True self._direction = direction.lower() self.logger.info('Received ZeroBorg drive command: {}'.format(direction)) def _run(): while self._can_run and self._direction: - left = 0.0 - right = 0.0 - if self._direction == Direction.DIR_AUTO_TOGGLE.value: if self.auto_mode: self._direction = None @@ -182,13 +188,11 @@ class GpioZeroborgPlugin(Plugin): self.auto_mode = False - self._drive_thread = threading.Thread(target=_run) self._drive_thread.start() return {'status': 'running', 'direction': direction} - @action def stop(self): """ @@ -202,8 +206,7 @@ class GpioZeroborgPlugin(Plugin): self.zb.MotorsOff() self.zb.ResetEpo() - return {'status':'stopped'} + return {'status': 'stopped'} # vim:sw=4:ts=4:et: - diff --git a/platypush/plugins/serial/__init__.py b/platypush/plugins/serial/__init__.py index b88ee1bd..5b667092 100644 --- a/platypush/plugins/serial/__init__.py +++ b/platypush/plugins/serial/__init__.py @@ -4,10 +4,11 @@ import serial import threading import time -from platypush.plugins import Plugin, action +from platypush.plugins import action from platypush.plugins.gpio.sensor import GpioSensorPlugin +# noinspection PyBroadException class SerialPlugin(GpioSensorPlugin): """ The serial plugin can read data from a serial device, as long as the serial @@ -35,8 +36,8 @@ class SerialPlugin(GpioSensorPlugin): self.serial_lock = threading.Lock() self.last_measurement = None - - def _read_json(self, serial_port): + @staticmethod + def _read_json(serial_port): n_brackets = 0 is_escaped_ch = False parse_start = False @@ -122,10 +123,10 @@ class SerialPlugin(GpioSensorPlugin): serial_available = self.serial_lock.acquire(timeout=2) if serial_available: try: - ser = self._get_serial(device=device) + ser = self._get_serial(device=device, baud_rate=baud_rate) except: time.sleep(1) - ser = self._get_serial(device=device, reset=True) + ser = self._get_serial(device=device, baud_rate=baud_rate, reset=True) data = self._read_json(ser) @@ -147,7 +148,6 @@ class SerialPlugin(GpioSensorPlugin): return data - @action def read(self, device=None, baud_rate=None, size=None, end=None): """ @@ -179,7 +179,7 @@ class SerialPlugin(GpioSensorPlugin): if (size is None and end is None) or (size is not None and end is not None): raise RuntimeError('Either size or end must be specified') - if end and len(end) > 1: + if end and isinstance(end, str) and len(end) > 1: raise RuntimeError('The serial end must be a single character, not a string') data = bytes() @@ -188,10 +188,10 @@ class SerialPlugin(GpioSensorPlugin): serial_available = self.serial_lock.acquire(timeout=2) if serial_available: try: - ser = self._get_serial(device=device) + ser = self._get_serial(device=device, baud_rate=baud_rate) except: time.sleep(1) - ser = self._get_serial(device=device, reset=True) + ser = self._get_serial(device=device, baud_rate=baud_rate, reset=True) if size is not None: for _ in range(0, size): @@ -255,10 +255,10 @@ class SerialPlugin(GpioSensorPlugin): serial_available = self.serial_lock.acquire(timeout=2) if serial_available: try: - ser = self._get_serial(device=device) + ser = self._get_serial(device=device, baud_rate=baud_rate) except: time.sleep(1) - ser = self._get_serial(device=device, reset=True) + ser = self._get_serial(device=device, baud_rate=baud_rate, reset=True) self.logger.info('Writing {} to {}'.format(data, self.device)) ser.write(data) @@ -270,4 +270,3 @@ class SerialPlugin(GpioSensorPlugin): # vim:sw=4:ts=4:et: -