diff --git a/platypush/backend/http/app/routes/plugins/camera/ir/__init__.py b/platypush/backend/http/app/routes/plugins/camera/ir/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/platypush/backend/http/app/routes/plugins/camera/ir/mlx90640.py b/platypush/backend/http/app/routes/plugins/camera/ir/mlx90640.py new file mode 100644 index 0000000000..cd27877137 --- /dev/null +++ b/platypush/backend/http/app/routes/plugins/camera/ir/mlx90640.py @@ -0,0 +1,60 @@ +import base64 +import os +import tempfile +import time + +from flask import Response, request, Blueprint, send_from_directory + +from platypush.backend.http.app import template_folder +from platypush.backend.http.app.utils import authenticate, send_request + +camera_ir_mlx90640 = Blueprint('camera.ir.mlx90640', __name__, template_folder=template_folder) + +# Declare routes list +__routes__ = [ + camera_ir_mlx90640, +] + + +def get_feed(**args): + try: + while True: + frame = send_request(action='camera.ir.mlx90640.capture', **args).output[0] + frame = base64.decodebytes(frame.encode()) + yield (b'--frame\r\n' + b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') + finally: + send_request(action='camera.ir.mlx90640.stop') + + +@camera_ir_mlx90640.route('/camera/ir/mlx90640/frame', methods=['GET']) +@authenticate() +def get_frame_route(): + f = tempfile.NamedTemporaryFile(prefix='ir_camera_frame_', suffix='.jpg', delete=False) + args = { + 'grayscale': bool(int(request.args.get('grayscale', 0))), + 'scale_factor': int(request.args.get('scale_factor', 1)), + 'rotate': int(request.args.get('rotate', 0)), + 'output_file': f.name, + } + + send_request(action='camera.ir.mlx90640.capture', **args) + return send_from_directory(os.path.dirname(f.name), + os.path.basename(f.name)) + + +@camera_ir_mlx90640.route('/camera/ir/mlx90640/stream', methods=['GET']) +@authenticate() +def get_feed_route(): + args = { + 'grayscale': bool(int(request.args.get('grayscale', 0))), + 'scale_factor': int(request.args.get('scale_factor', 1)), + 'rotate': int(request.args.get('rotate', 0)), + 'format': 'jpeg', + } + + return Response(get_feed(**args), + mimetype='multipart/x-mixed-replace; boundary=frame') + + +# vim:sw=4:ts=4:et: diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/camera/index.scss b/platypush/backend/http/static/css/source/webpanel/plugins/camera/index.scss index 9b36934998..43387330c9 100644 --- a/platypush/backend/http/static/css/source/webpanel/plugins/camera/index.scss +++ b/platypush/backend/http/static/css/source/webpanel/plugins/camera/index.scss @@ -1,9 +1,9 @@ @import 'common/vars'; .camera { - height: 90%; - margin-top: 7%; - overflow: hidden; + min-height: 90%; + margin-top: 4%; + overflow: auto; display: flex; flex-direction: column; align-items: center; diff --git a/platypush/backend/http/static/js/plugins/camera.ir.mlx90640/index.js b/platypush/backend/http/static/js/plugins/camera.ir.mlx90640/index.js index d081c70f74..f26fe528f0 100644 --- a/platypush/backend/http/static/js/plugins/camera.ir.mlx90640/index.js +++ b/platypush/backend/http/static/js/plugins/camera.ir.mlx90640/index.js @@ -17,22 +17,43 @@ Vue.component('camera-ir-mlx90640', { return; this.capturing = true; - - while (this.capturing) { - const img = await request('camera.ir.mlx90640.capture', { - format: 'png', - rotate: this.rotate, - grayscale: this.grayscale, - }); - - this.$refs.frame.setAttribute('src', 'data:image/png;base64,' + img); - } + this.$refs.frame.setAttribute('src', '/camera/ir/mlx90640/stream?rotate=' + + this.rotate + '&grayscale=' + (this.grayscale ? 1 : 0) + '&t=' + + (new Date()).getTime()); }, stopStreaming: async function() { await request('camera.ir.mlx90640.stop'); + this.$refs.frame.removeAttribute('src'); this.capturing = false; }, + + onRotationChange: function() { + this.rotate = parseInt(this.$refs.rotate.value); + const cameraContainer = this.$el.querySelector('.camera-container'); + + switch (this.rotate) { + case 0: + case 180: + cameraContainer.style.width = '640px'; + cameraContainer.style.minWidth = '640px'; + cameraContainer.style.height = '480px'; + cameraContainer.style.minHeight = '480px'; + break; + + case 90: + case 270: + cameraContainer.style.width = '480px'; + cameraContainer.style.minWidth = '480px'; + cameraContainer.style.height = '640px'; + cameraContainer.style.minHeight = '640px'; + break; + } + }, + }, + + mounted: function() { + this.onRotationChange(); }, }); diff --git a/platypush/backend/http/templates/plugins/camera.ir.mlx90640/index.html b/platypush/backend/http/templates/plugins/camera.ir.mlx90640/index.html index 6b7df9226a..d09e0ba749 100644 --- a/platypush/backend/http/templates/plugins/camera.ir.mlx90640/index.html +++ b/platypush/backend/http/templates/plugins/camera.ir.mlx90640/index.html @@ -14,7 +14,7 @@   Stop streaming - diff --git a/platypush/plugins/camera/ir/mlx90640/__init__.py b/platypush/plugins/camera/ir/mlx90640/__init__.py index 2b4563a680..3949142e61 100644 --- a/platypush/plugins/camera/ir/mlx90640/__init__.py +++ b/platypush/plugins/camera/ir/mlx90640/__init__.py @@ -36,6 +36,11 @@ class CameraIrMlx90640Plugin(Plugin): """ _img_size = (32, 24) + _rotate_values = { + 90: Image.ROTATE_90, + 180: Image.ROTATE_180, + 270: Image.ROTATE_270, + } def __init__(self, fps=16, skip_frames=2, scale_factor=1, rotate=0, rawrgb_path=None, **kwargs): """ @@ -74,7 +79,7 @@ class CameraIrMlx90640Plugin(Plugin): return self._capture_proc @action - def capture(self, output_file=None, frames=1, grayscale=False, fps=None, skip_frames=None, scale_factor=None, rotate=None, format=None): + def capture(self, output_file=None, frames=1, grayscale=False, fps=None, skip_frames=None, scale_factor=None, rotate=None, format='jpeg'): """ Capture one or multiple frames and return them as raw RGB @@ -100,18 +105,18 @@ class CameraIrMlx90640Plugin(Plugin): :param rotate: If set it overrides the rotate parameter specified on the object (default: None) :type rotate: int - :param format: Output image format if output_file is not specified(default: None, raw RGB). + :param format: Output image format if output_file is not specified (default: jpeg). It can be jpg, png, gif or any format supported by PIL :type format: str - :returns: list[str]. Each item is a base64 encoded raw RGB representation of a frame if output_file is not set, otherwise a list with + :returns: list[str]. Each item is a base64 encoded representation of a frame in the specified format if output_file is not set, otherwise a list with the captured image files will be returned. """ fps = self.fps if fps is None else fps skip_frames = self.skip_frames if skip_frames is None else skip_frames scale_factor = self.scale_factor if scale_factor is None else scale_factor - rotate = self.rotate if rotate is None else rotate + rotate = self._rotate_values.get(self.rotate if rotate is None else rotate, 0) size = self._img_size sleep_time = 1.0 / self.fps @@ -132,21 +137,16 @@ class CameraIrMlx90640Plugin(Plugin): if grayscale: image = self._convert_to_grayscale(image) - if rotate: - image = image.rotate(rotate) if scale_factor != 1: size = tuple(i*scale_factor for i in size) image = image.resize(size, Image.ANTIALIAS) - - frame = image.getdata() + if rotate: + image = image.transpose(rotate) if not output_file: - if format: - temp = io.BytesIO() - image.save(temp, format=format) - frame = temp.getvalue() - - frame = base64.encodebytes(frame).decode() + temp = io.BytesIO() + image.save(temp, format=format) + frame = base64.encodebytes(temp.getvalue()).decode() captured_frames.append(frame) else: image_file = os.path.abspath(os.path.expanduser(output_file.format(n_captured_frames)))