Compare commits
12 commits
master
...
raspimjpeg
Author | SHA1 | Date | |
---|---|---|---|
173f1ca5d1 | |||
8830e17f2f | |||
6ae1f9f22f | |||
cd2484a8d8 | |||
20e1d696a0 | |||
fb7f77b3a8 | |||
416f6ec2a6 | |||
c13cb3355e | |||
7558c6d196 | |||
a8c7d8e74f | |||
e65ed89053 | |||
30bb211324 |
12 changed files with 149 additions and 50 deletions
|
@ -3,7 +3,6 @@ from flask import Flask
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from phytopi.config import Config
|
from phytopi.config import Config
|
||||||
# from phytopi.camera.camera_pi import Camera
|
|
||||||
|
|
||||||
app = Flask(__name__, instance_relative_config=True)
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
|
|
||||||
|
@ -11,6 +10,11 @@ app.config.from_object(Config)
|
||||||
|
|
||||||
db = SQLAlchemy(app)
|
db = SQLAlchemy(app)
|
||||||
migrate = Migrate(app, db)
|
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
|
from phytopi import routes, models
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('camera', __name__)
|
||||||
|
|
||||||
|
from phytopi.camera import camera
|
90
phytopi/camera/camera.py
Normal file
90
phytopi/camera/camera.py
Normal file
|
@ -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()
|
5
phytopi/errors/__init__.py
Normal file
5
phytopi/errors/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('errors', __name__)
|
||||||
|
|
||||||
|
from phytopi.errors import handlers
|
|
@ -1,6 +1,8 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from phytopi import db
|
from phytopi import db
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Dataset(db.Model):
|
class Dataset(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
title = db.Column(db.String(64), index=True)
|
title = db.Column(db.String(64), index=True)
|
||||||
|
@ -29,17 +31,16 @@ class CameraSettings(db.Model):
|
||||||
fps = db.Column(db.Integer, default=5)
|
fps = db.Column(db.Integer, default=5)
|
||||||
width = db.Column(db.Integer, default=3280)
|
width = db.Column(db.Integer, default=3280)
|
||||||
height = db.Column(db.Integer, default=2464)
|
height = db.Column(db.Integer, default=2464)
|
||||||
fix_shutter = db.Column(db.Boolean, default=False)
|
exposure_mode = db.Column(db.String(64), default='auto')
|
||||||
fix_wb = db.Column(db.Boolean, default=False)
|
|
||||||
|
|
||||||
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.name = name
|
||||||
self.iso = iso
|
self.iso = iso
|
||||||
self.fps = fps
|
self.fps = fps
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
self.fix_shutter = fix_shutter
|
self.exposure_mode = exposure_mode
|
||||||
self.fix_wb = fix_wb
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<CameraSettings {}>'.format(self.name)
|
return '<CameraSettings {}>'.format(self.name)
|
Binary file not shown.
|
@ -1,6 +1,6 @@
|
||||||
from flask import render_template, Response, flash, jsonify, request, stream_with_context, send_file
|
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.forms import CameraSettingsForm
|
||||||
|
|
||||||
from phytopi import app
|
from phytopi import app
|
||||||
|
@ -10,46 +10,59 @@ from phytopi.models import Dataset
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import zipstream
|
import zipstream
|
||||||
|
|
||||||
camera = Camera(app=app)
|
camera = CameraWorker(app=app)
|
||||||
# camera = app.camera
|
camera.start()
|
||||||
save_frames = False
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@app.route('/index')
|
@app.route('/index')
|
||||||
def 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)
|
return render_template('index.html', content=content)
|
||||||
|
|
||||||
@app.route('/toggle_timelapse', methods=['GET', 'POST'])
|
@app.route('/toggle_timelapse', methods=['GET', 'POST'])
|
||||||
def start_stop_timelapse():
|
def start_stop_timelapse():
|
||||||
global save_frames
|
# global save_frames
|
||||||
global current_dataset
|
global current_dataset
|
||||||
global camera
|
global camera
|
||||||
if save_frames:
|
# if save_frames:
|
||||||
save_frames = False
|
# save_frames = False
|
||||||
timelapse_interval = None
|
# timelapse_interval = None
|
||||||
btn_text = "Start"
|
# btn_text = "Start"
|
||||||
btn_class = 'btn-primary'
|
# btn_class = 'btn-primary'
|
||||||
camera.current_dataset = None
|
# camera.current_dataset = None
|
||||||
camera.last_saved = None
|
# camera.last_saved = None
|
||||||
print(" > switched off timelapse mode")
|
# print(" > switched off timelapse mode")
|
||||||
else:
|
# else:
|
||||||
save_frames = True
|
# save_frames = True
|
||||||
timelapse_interval = 1200
|
# 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_text = "Stop"
|
||||||
btn_class = 'btn-danger'
|
btn_class = 'btn-danger'
|
||||||
dataset = Dataset()
|
camera.start_timelapse()
|
||||||
db.session.add(dataset)
|
elif camera.status == CameraStatus.TIMELAPSE:
|
||||||
db.session.commit()
|
btn_text = "Start"
|
||||||
camera.current_dataset = dataset.id
|
btn_class = 'btn-primary'
|
||||||
print(" > switched on timelapse mode")
|
camera.stop_timelapse()
|
||||||
|
|
||||||
camera.set_timelapse_interval(timelapse_interval)
|
|
||||||
return jsonify(btn_text=btn_text, btn_class=btn_class)
|
return jsonify(btn_text=btn_text, btn_class=btn_class)
|
||||||
|
|
||||||
|
|
||||||
def gen(camera):
|
def gen(camera):
|
||||||
# def gen():
|
|
||||||
"""Video streaming generator function."""
|
"""Video streaming generator function."""
|
||||||
while True:
|
while True:
|
||||||
frame = camera.get_frame(thumbnail=True)
|
frame = camera.get_frame(thumbnail=True)
|
||||||
|
|
|
@ -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
|
|
Loading…
Add table
Reference in a new issue