diff --git a/phytopi/__init__.py b/phytopi/__init__.py index ee384c0..51df99e 100644 --- a/phytopi/__init__.py +++ b/phytopi/__init__.py @@ -3,7 +3,6 @@ from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from phytopi.config import Config -# from phytopi.camera.camera_pi import Camera app = Flask(__name__, instance_relative_config=True) @@ -11,6 +10,11 @@ app.config.from_object(Config) db = SQLAlchemy(app) migrate = Migrate(app, db) -# camera = Camera(app=app) + +from phytopi.errors import bp as errors_bp +from phytopi.camera import bp as camera_bp + +app.register_blueprint(errors_bp) +app.register_blueprint(camera_bp) from phytopi import routes, models diff --git a/phytopi/camera/__init__.py b/phytopi/camera/__init__.py index e69de29..9614332 100644 --- a/phytopi/camera/__init__.py +++ b/phytopi/camera/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +bp = Blueprint('camera', __name__) + +from phytopi.camera import camera \ No newline at end of file diff --git a/phytopi/camera/camera.py b/phytopi/camera/camera.py new file mode 100644 index 0000000..2e001f6 --- /dev/null +++ b/phytopi/camera/camera.py @@ -0,0 +1,90 @@ +import io +import os +import sys +import stat +import subprocess +import signal + +from time import sleep +from enum import Enum +from PIL import Image +from phytopi import db, models +from phytopi.models import CameraSettings + +class CameraActionError(Exception): + pass + +class CameraStatus(Enum): + STOPPED = 0 + IDLE = 1 + TIMELAPSE = 2 + VIDEO = 3 + SHOT = 4 + +class CameraWorker(object): + def __init__(self, pidfile='/tmp/raspimjpeg.pid', executable='/usr/local/bin/raspimjpeg', fifo='/dev/shm/mjpeg/FIFO'): + self.pidfile = pidfile + self.executable = executable + self.fifo = fifo + self.proc = None + self.pid = None + self.status = CameraStatus.STOPPED + + def start(self): + # check if process is already running + if os.path.isfile(self.pidfile): + with open(self.pidfile, 'r') as fh: + self.pid = int(fh.read()) + self.status = CameraStatus.IDLE + return + + # create FIFO, if not existent + try: + os.mkfifo(self.fifo) + except FileExistsError: + pass + + # if not, spawn new process and create pid file + self.proc = subprocess.Popen([self.executable], preexec_fn=os.setsid) + self.pid = self.proc.pid + with open(self.pidfile, 'w') as fh: + fh.write(str(self.pid)) + self.status = CameraStatus.IDLE + + def stop(self): + os.killpg(os.getpgid(self.pid), signal.SIGTERM) + os.unlink(self.fifo) + os.unlink(self.pidfile) + self.status = CameraStatus.STOPPED + + def _send_cmd(self, cmd): + with open(self.fifo, 'w') as fh: + fh.write('{}\n'.format(cmd)) + + def _send_cmds(self, cmds): + for cmd in cmds: + self._send_cmd(cmd) + # don't flood the fifo + sleep(1) + + def start_timelapse(self, interval=300): + if self.status == CameraStatus.IDLE: + cmds = ['tv {}'.format(interval), 'tl 1'] + self._send_cmds(cmds) + self.status = CameraStatus.TIMELAPSE + else: + raise CameraActionError('Camera not idle!') + + def stop_timelapse(self): + if self.status == CameraStatus.TIMELAPSE: + self._send_cmd('tl 0') + self.status = CameraStatus.IDLE + else: + raise CameraActionError('Camera not in timelapse mode!') + + def get_frame(self, thumbnail=False): + output = io.BytesIO() + img = Image.open('/dev/shm/mjpeg/cam.jpg') + img.save(output, format='JPEG') + output.seek(0, 0) + return output.getvalue() \ No newline at end of file diff --git a/phytopi/camera_daemon/__init__.py b/phytopi/camera_old/__init__.py similarity index 100% rename from phytopi/camera_daemon/__init__.py rename to phytopi/camera_old/__init__.py diff --git a/phytopi/camera/base_camera.py b/phytopi/camera_old/base_camera.py similarity index 100% rename from phytopi/camera/base_camera.py rename to phytopi/camera_old/base_camera.py diff --git a/phytopi/camera/camera_pi.py b/phytopi/camera_old/camera_pi.py similarity index 100% rename from phytopi/camera/camera_pi.py rename to phytopi/camera_old/camera_pi.py diff --git a/phytopi/errors/__init__.py b/phytopi/errors/__init__.py new file mode 100644 index 0000000..5d1beb0 --- /dev/null +++ b/phytopi/errors/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +bp = Blueprint('errors', __name__) + +from phytopi.errors import handlers \ No newline at end of file diff --git a/phytopi/camera_daemon/camera_daemon.py b/phytopi/errors/handlers.py similarity index 100% rename from phytopi/camera_daemon/camera_daemon.py rename to phytopi/errors/handlers.py diff --git a/phytopi/models.py b/phytopi/models.py index b49e45f..828ddc2 100644 --- a/phytopi/models.py +++ b/phytopi/models.py @@ -1,6 +1,8 @@ from datetime import datetime from phytopi import db + + class Dataset(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String(64), index=True) @@ -29,17 +31,16 @@ class CameraSettings(db.Model): fps = db.Column(db.Integer, default=5) width = db.Column(db.Integer, default=3280) height = db.Column(db.Integer, default=2464) - fix_shutter = db.Column(db.Boolean, default=False) - fix_wb = db.Column(db.Boolean, default=False) + exposure_mode = db.Column(db.String(64), default='auto') - def __init__(self, name, iso=100, fps=5, width=3280, height=2464, fix_shutter=False, fix_wb=False): + def __init__(self, name, iso=100, fps=5, width=3280, height=2464, exposure_mode='auto'): self.name = name self.iso = iso self.fps = fps self.width = width self.height = height - self.fix_shutter = fix_shutter - self.fix_wb = fix_wb + self.exposure_mode = exposure_mode + def __repr__(self): return ''.format(self.name) \ No newline at end of file diff --git a/phytopi/phytopi.db b/phytopi/phytopi.db deleted file mode 100644 index 0519c72..0000000 Binary files a/phytopi/phytopi.db and /dev/null differ diff --git a/phytopi/routes.py b/phytopi/routes.py index 35e73b6..a8a6010 100644 --- a/phytopi/routes.py +++ b/phytopi/routes.py @@ -1,6 +1,6 @@ from flask import render_template, Response, flash, jsonify, request, stream_with_context, send_file -from phytopi.camera.camera_pi import Camera +from phytopi.camera.camera import CameraWorker, CameraStatus from phytopi.forms import CameraSettingsForm from phytopi import app @@ -10,46 +10,59 @@ from phytopi.models import Dataset from io import BytesIO import zipstream -camera = Camera(app=app) -# camera = app.camera -save_frames = False +camera = CameraWorker(app=app) +camera.start() + @app.route('/') @app.route('/index') def index(): - content = {'timelapse': save_frames} + global camera + if camera.status == CameraStatus.TIMELAPSE: + timelapse = True + else: + timelapse = False + content = {'timelapse': timelapse} return render_template('index.html', content=content) @app.route('/toggle_timelapse', methods=['GET', 'POST']) def start_stop_timelapse(): - global save_frames + # global save_frames global current_dataset global camera - if save_frames: - save_frames = False - timelapse_interval = None - btn_text = "Start" - btn_class = 'btn-primary' - camera.current_dataset = None - camera.last_saved = None - print(" > switched off timelapse mode") - else: - save_frames = True - timelapse_interval = 1200 + # if save_frames: + # save_frames = False + # timelapse_interval = None + # btn_text = "Start" + # btn_class = 'btn-primary' + # camera.current_dataset = None + # camera.last_saved = None + # print(" > switched off timelapse mode") + # else: + # save_frames = True + # timelapse_interval = 1200 + # btn_text = "Stop" + # btn_class = 'btn-danger' + # dataset = Dataset() + # db.session.add(dataset) + # db.session.commit() + # camera.current_dataset = dataset.id + # print(" > switched on timelapse mode") + + # camera.set_timelapse_interval(timelapse_interval) + if camera.status == CameraStatus.IDLE: btn_text = "Stop" btn_class = 'btn-danger' - dataset = Dataset() - db.session.add(dataset) - db.session.commit() - camera.current_dataset = dataset.id - print(" > switched on timelapse mode") + camera.start_timelapse() + elif camera.status == CameraStatus.TIMELAPSE: + btn_text = "Start" + btn_class = 'btn-primary' + camera.stop_timelapse() - camera.set_timelapse_interval(timelapse_interval) return jsonify(btn_text=btn_text, btn_class=btn_class) def gen(camera): -# def gen(): """Video streaming generator function.""" while True: frame = camera.get_frame(thumbnail=True) diff --git a/requirements_pi.txt b/requirements_pi.txt deleted file mode 100644 index abee2bc..0000000 --- a/requirements_pi.txt +++ /dev/null @@ -1,19 +0,0 @@ -alembic>=1.0.1 -Click>=7.0 -Flask>=1.0.2 -Flask-Migrate>=2.3.0 -Flask-SQLAlchemy>=2.3.2 -Flask-WTF>=0.14.2 -itsdangerous>=1.1.0 -Jinja2>=2.10 -Mako>=1.0.7 -MarkupSafe>=1.0 -picamera>=1.13 -Pillow>=5.3.0 -python-dateutil>=2.7.5 -python-editor>=1.0.3 -six>=1.11.0 -SQLAlchemy>=1.2.12 -Werkzeug>=0.14.1 -WTForms>=2.2.1 -zipstream>=1.1.4