Compare commits
No commits in common. "raspimjpeg" and "master" have entirely different histories.
raspimjpeg
...
master
12 changed files with 50 additions and 149 deletions
|
@ -3,6 +3,7 @@ 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)
|
||||||
|
|
||||||
|
@ -10,11 +11,6 @@ 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
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
from flask import Blueprint
|
|
||||||
|
|
||||||
bp = Blueprint('camera', __name__)
|
|
||||||
|
|
||||||
from phytopi.camera import camera
|
|
|
@ -1,90 +0,0 @@
|
||||||
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()
|
|
|
@ -1,5 +0,0 @@
|
||||||
from flask import Blueprint
|
|
||||||
|
|
||||||
bp = Blueprint('errors', __name__)
|
|
||||||
|
|
||||||
from phytopi.errors import handlers
|
|
|
@ -1,8 +1,6 @@
|
||||||
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)
|
||||||
|
@ -31,16 +29,17 @@ 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)
|
||||||
exposure_mode = db.Column(db.String(64), default='auto')
|
fix_shutter = db.Column(db.Boolean, default=False)
|
||||||
|
fix_wb = db.Column(db.Boolean, default=False)
|
||||||
|
|
||||||
def __init__(self, name, iso=100, fps=5, width=3280, height=2464, exposure_mode='auto'):
|
def __init__(self, name, iso=100, fps=5, width=3280, height=2464, fix_shutter=False, fix_wb=False):
|
||||||
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.exposure_mode = exposure_mode
|
self.fix_shutter = fix_shutter
|
||||||
|
self.fix_wb = fix_wb
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<CameraSettings {}>'.format(self.name)
|
return '<CameraSettings {}>'.format(self.name)
|
BIN
phytopi/phytopi.db
Normal file
BIN
phytopi/phytopi.db
Normal file
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 import CameraWorker, CameraStatus
|
from phytopi.camera.camera_pi import Camera
|
||||||
from phytopi.forms import CameraSettingsForm
|
from phytopi.forms import CameraSettingsForm
|
||||||
|
|
||||||
from phytopi import app
|
from phytopi import app
|
||||||
|
@ -10,59 +10,46 @@ from phytopi.models import Dataset
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import zipstream
|
import zipstream
|
||||||
|
|
||||||
camera = CameraWorker(app=app)
|
camera = Camera(app=app)
|
||||||
camera.start()
|
# camera = app.camera
|
||||||
|
save_frames = False
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@app.route('/index')
|
@app.route('/index')
|
||||||
def index():
|
def index():
|
||||||
global camera
|
content = {'timelapse': save_frames}
|
||||||
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_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'
|
|
||||||
camera.start_timelapse()
|
|
||||||
elif camera.status == CameraStatus.TIMELAPSE:
|
|
||||||
btn_text = "Start"
|
btn_text = "Start"
|
||||||
btn_class = 'btn-primary'
|
btn_class = 'btn-primary'
|
||||||
camera.stop_timelapse()
|
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)
|
||||||
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)
|
||||||
|
|
19
requirements_pi.txt
Normal file
19
requirements_pi.txt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
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