Skip to content

Commit

Permalink
V2.0 (#147)
Browse files Browse the repository at this point in the history
* change how device are initialized

* test pass and add py-custom-printer to dependencies

* fix settings

* is_pressed is a property

* py-custom-printer@b1754c01921a7ac25553a8794498201ec8e44c4d

* fix typo

* GPIO -> GPIOZERON in RemoteReleaseConnector factory

* @timeit on printer.print_image

* update py-custom-printer version

* focus lens on startup

* install numpy with apt-get

* update wifi-connect version

* add ImageMagick dependency

* add all wifi-connect files

* implementation of RTC_DS1302 real time clock

* fix settings and @transaction_decorate

* change default pin configuration

* do not create RTC for time being

* try fixing RTC

* use RPi.GPIO for real time clock management

* remove datadog agent

* call camera.configure(), camera.clear_space() and camera.focus() explicitly in photobooth.initialize_devices()

* enter liveview during camera.focus_narer() and camera.focus_further()

* change default focus steps

* remove datadog agent

* allow shutting down the device by activating a specific pin

* add wireless-tools to the list of software installed

* comment lockfile for time being

* sharpen image before printing

* try writing the file in binary mode

* enhance picture only

* Not filter by default

* remove imagemagick

* do not trigger capture if paper is not present

* make sure to clear messages before asking the printer for status

* python-epson-printer@1.10.0

* python-epson-printer@v1.11.0 py-custom-printer@v0.1.1. Check photobooth paper level

* raise OutPfPaper error in CustomPrinter

* fix trigger api endpoint

* py-custom-printer@0.1.2

* check paper level only for VKP80III

* fix printer

* add a white margin on VKP80III

* fix ticket not printing correctly

* rotate image before printing

* remove logging of workers

* change how image enhancement work

* fix margin color

* try/except for printer.configure()

* set resin wifi retry flag to True

* resin-wifi-connect@2.0.5

* log wifi-connect connectivity state

* always retry after failed connection

* start local server only if specified in configuration

* fix Fix NM_CONNECTIVITY constants

* customize wifi-connect state flow diagram

* try using python-webkit2png

* install specific version of pip

* fix bug render_screenshot take one argument

* build from figure base images

* fix some errors

* add xvfb to init.d

* start Xfvb in start.sh

* create qt application in main thread

* remove typo in try: except

* call init_qtgui after it is defined

* add an argument to get_screenshot in print_booting_template

* use python-webkit2png as a separate process

* try launching get_screenshot as multiprocessing.Process

* some bug fixes

* figure/%%RESIN_MACHINE_NAME%%:1.1.0

* py-custom-printer@v0.1.3

* fix some imports

* py_custom_printer@v0.1.4

* V2.0 refactor (#146)

* Refactor + tests

* return the file ticket that was actually printed

* fix booting ticket

* change Makefile, remove unused tests, add a fabfile for deployment

* remove Cython dependency

* fix some errors in api.py

* start server no matter what but catch socket error

* add a /focus endpoint to the api

* fix /test_template and /print

* EOS_1200D_CONFIG -> CAMERA_CONFIG

* make door lock pin configurable

* compatibility with master

* fix test
  • Loading branch information
benoitguigal authored May 31, 2017
1 parent a3fc640 commit 98a5f02
Show file tree
Hide file tree
Showing 58 changed files with 3,759 additions and 2,946 deletions.
120 changes: 19 additions & 101 deletions Dockerfile.template
Original file line number Diff line number Diff line change
@@ -1,67 +1,7 @@
# Base image
FROM resin/%%RESIN_MACHINE_NAME%%-node:0.10

MAINTAINER Benoit Guigal <benoit@postcardgroup.com>

# Make sure installation is not asking for prompt
ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update && apt-get install -y \
python \
python-dev \
python-setuptools \
python-pip \
pkg-config \
gcc \
g++ \
make \
build-essential \
tcl8.5 \
unzip \
tar \
wget \
bzip2 \
libjpeg-dev \
zlib1g-dev \
libpng12-dev \
usbutils \
libfreetype6 \
libfontconfig \
git \
bind9 \
bridge-utils \
connman \
iptables \
libdbus-1-dev \
libexpat-dev \
net-tools \
wireless-tools \
sysstat \
procmail \
vim \
&& rm -rf /var/lib/apt/lists/*


RUN wget --no-check-certificate https://github.com/gonzalo/gphoto2-updater/archive/2.5.10.zip && \
unzip 2.5.10.zip && \
cd gphoto2-updater-2.5.10 && \
chmod +x gphoto2-updater.sh && \
./gphoto2-updater.sh


RUN wget --no-check-certificate https://github.com/Postcard/python-epson-printer/archive/v1.9.0.zip && \
unzip v1.9.0.zip && \
rm v1.9.0.zip && \
cd python-epson-printer-1.9.0 && \
python setup.py install


RUN wget --no-check-certificate https://github.com/aeberhardo/phantomjs-linux-armv6l/archive/master.zip && \
unzip master.zip && \
rm master.zip && \
cd phantomjs-linux-armv6l-master && \
bunzip2 *.bz2 && \
tar xf *.tar
FROM figure/%%RESIN_MACHINE_NAME%%:1.1.0

MAINTAINER Benoit Guigal <benoit@figure.co>

RUN git clone https://github.com/Postcard/png2pos.git && \
cd png2pos && \
Expand All @@ -70,49 +10,27 @@ RUN git clone https://github.com/Postcard/png2pos.git && \
git submodule update && \
make install

RUN pip install gphoto2==1.4.1 \
gpiozero==1.2.0 \
Pillow==3.1.0 \
pifacecommon==4.1.2 \
pifacedigitalio==3.0.5 \
pytz==2015.2 \
supervisor==3.1.3 \
hashids==1.1.0 \
ticketrenderer==0.2.0 \
figure-sdk==0.2.0 \
peewee==2.8.1 \
Flask==0.11.1 \
psutil==4.3.0 \
netifaces==0.10.4 \
piexif==1.0.5

ENV LANG C.UTF-8
ENV C_FORCE_ROOT true
# Install dependencies for python-webkit2png
RUN apt-get update && apt-get install python-qt4 \
libqt4-webkit \
xvfb \
xauth

# Add xvfb to init.d
ADD xvfb /etc/init.d/xvfb
RUN chmod +x /etc/init.d/xvfb
ENV DISPLAY :1

# Install Python dependencies
RUN mkdir requirements
ADD requirements ./requirements
RUN cd requirements && pip install -r prod.txt

ENV FIGURE_DIR /figure/figureraspbian
ENV IMAGE_DIR /data/images
ENV PHANTOMJS_PATH /phantomjs-linux-armv6l-master/phantomjs-1.9.0-linux-armv6l/bin/phantomjs
ENV DATA_ROOT /data
ENV STATIC_ROOT /data/static
ENV MEDIA_ROOT /data/media
ENV ZEO_SOCKET /data/zeo.sock

COPY ./wifi-connect/assets/bind /etc/bind
RUN mkdir -p /usr/src/app/
WORKDIR /usr/src/app
COPY ./wifi-connect/package.json ./
RUN JOBS=MAX npm install --unsafe-perm --production && npm cache clean

COPY ./wifi-connect/bower.json ./wifi-connect/.bowerrc ./
RUN ./node_modules/.bin/bower --allow-root install \
&& ./node_modules/.bin/bower --allow-root cache clean

COPY ./wifi-connect/. ./
RUN ./node_modules/.bin/coffee -c ./src

RUN touch /var/log/named.log
RUN chown bind /var/log/named.log

VOLUME /var/lib/connman

RUN mkdir -p /usr/share/fonts/opentype
COPY fonts/*.otf /usr/share/fonts/opentype/
Expand Down
4 changes: 1 addition & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
test:
# This runs all of the tests. To run an individual test, run py.test with
# the -k flag, like "py.test -k test_processus"
py.test test_figure.py
python -m unittest discover
28 changes: 28 additions & 0 deletions fabfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

from fabric.api import task, env
from fabric.operations import local

env.force = False

@task
def integration():
env.remotes = ['resinintegration_raspi2', 'resinintegration_raspi3']

@task
def production():
env.remotes = ['resinproduction_raspi2', 'resinproduction_raspi3']

@task
def force():
env.force = True

@task
def deploy(branch="master"):
for remote in env.remotes:
cmd = ['git', 'push']
if env.force:
cmd.append('--force')
cmd.append(remote)
cmd.append('%s:master' % branch)
cmd = ' '.join(cmd)
local(cmd)
37 changes: 36 additions & 1 deletion figureraspbian/__main__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
# -*- coding: utf8 -*-

import signal
import os
import logging

import requests

from app import App
from db import db
from devices.button import Button
import settings
from models import get_all_models, Photobooth


logging.basicConfig(format=settings.LOG_FORMAT, datefmt='%Y.%m.%d %H:%M:%S', level='INFO')

from figureraspbian.app import App

class GracefulKiller:

Expand All @@ -16,13 +28,36 @@ def __init__(self, app):
def exit_gracefully(self, signum, frame):
self.kill_now = True
self.app.stop()
db.close_db()


class ShutdownHook:

def __init__(self):
self.shutdown_button = Button.factory(settings.SHUTDOWN_PIN, 0.05, 10)
self.shutdown_button.when_pressed = self.shutdown

def shutdown(self):
# Call resin.io supervisor shutdown endpoint https://docs.resin.io/runtime/supervisor-api/#post-v1-shutdown
resin_supervisor_address = settings.RESIN_SUPERVISOR_ADDRESS
resin_supervisor_api_key = settings.RESIN_SUPERVISOR_API_KEY
shutdown_url = "%s/v1/shutdown?apikey=%s" % (resin_supervisor_address, resin_supervisor_api_key)
requests.post(shutdown_url, data={'force': True})


def create_tables():
db.connect_db()
# creates tables if not exist
db.database.create_tables(get_all_models(), True)

if __name__ == '__main__':

create_tables()
Photobooth.get_or_create(uuid=settings.RESIN_UUID)
app = App()
killer = GracefulKiller(app)
app.start()
shutdown_hook = ShutdownHook()
signal.pause()


Expand Down
77 changes: 41 additions & 36 deletions figureraspbian/api.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# -*- coding: utf8 -*-
from os.path import basename, dirname, join
from functools import wraps
import cStringIO

from flask import Flask, send_from_directory, request, abort, jsonify
from flask import Flask, send_from_directory, request, jsonify, send_file
import psutil
from PIL import Image

from figureraspbian import photobooth
from figureraspbian import db, settings, utils
from figureraspbian.exceptions import DevicesBusy, OutOfPaperError

from threads import rlock
from photobooth import get_photobooth
from models import Photobooth, Portrait
import settings
from exceptions import DevicesBusy, PhotoboothNotReady, OutOfPaperError

app = Flask(__name__)

Expand All @@ -25,14 +28,32 @@ def decorated_function(*args, **kwargs):
return decorated_function


@app.route('/focus', methods=['POST'])
@login_required
def focus():
try:
steps = request.values.get('focus_steps')
photobooth = get_photobooth()
if steps:
photobooth.focus_camera(int(steps))
else:
photobooth.focus_camera()
return jsonify(message='Camera focused')
except DevicesBusy:
return jsonify(error='the photobooth is busy'), 423


@app.route('/trigger', methods=['POST'])
@login_required
def trigger():
try:
ticket_path = photobooth._trigger()
return send_from_directory(dirname(ticket_path), basename(ticket_path))
photobooth = get_photobooth()
ticket = photobooth.trigger()
return send_file(cStringIO.StringIO(ticket))
except DevicesBusy:
return jsonify(error='the photobooth is busy'), 423
except PhotoboothNotReady:
return jsonify(erro='the photobooth is not ready or not initialized properly'), 423


ALLOWED_EXTENSIONS = ['jpg', 'JPEG', 'JPG', 'png', 'PNG', 'gif']
Expand All @@ -53,31 +74,22 @@ def test_template():
w, h = picture.size
if w != h:
return jsonify(error='The picture must have a square shape'), 400
exif_bytes = picture.info['exif'] if 'exif' in picture.info else None
photobooth.render_print_and_upload(picture, exif_bytes)
photobooth = get_photobooth()
photobooth.render_print_and_upload(picture_file.getvalue())
return jsonify(message='Ticket successfully printed')


@app.route('/print', methods=['POST'])
@login_required
def print_image():
""" Print the image uploaded by the user """

image_file = request.files['image']
if image_file and allowed_file(image_file.filename):
im = Image.open(image_file)
w, h = im.size
if w != settings.PRINTER_MAX_WIDTH:
ratio = float(settings.PRINTER_MAX_WIDTH) / w
im = im.resize((settings.PRINTER_MAX_WIDTH, int(h * ratio)))
if im.mode != '1':
im = im.convert('1')
im_path = join(settings.MEDIA_ROOT, 'test.png')
im.save(im_path, im.format, quality=100)
pos_data = utils.png2pos(im_path)

try:
photobooth.printer.print_ticket(pos_data)
photobooth = get_photobooth()
photobooth.print_image(image_file.getvalue())
except DevicesBusy:
return jsonify(error='the photobooth is busy'), 423
except OutOfPaperError:
return jsonify(error='Out of paper'), 500
return jsonify(message='Ticket printed succesfully')
Expand All @@ -86,30 +98,23 @@ def print_image():
@app.route('/door_open', methods=['POST'])
@login_required
def door_open():
photobooth.door_open()
photobooth = get_photobooth()
photobooth.unlock_door()
return jsonify(message='Door opened')


@app.route('/logs')
@login_required
def logs():
resp = send_from_directory('/data/log', 'figure.log')
resp.headers['Content-Disposition'] = 'attachment; filename="figure.log"'
return resp


@app.route('/info')
@login_required
def info():
photobooth = db.get_photobooth()
photobooth = Photobooth.get()
place = photobooth.place.name if photobooth.place else ''
identifier = photobooth.serial_number or settings.RESIN_UUID
number_of_portraits_to_be_uploaded = db.get_portrait_to_be_uploaded() or 0
portraits_not_uploaded_count = Portrait.not_uploaded_count()
res = {
'identifier': identifier,
'place': place,
'counter': photobooth.counter,
'number_of_portraits_to_be_uploaded': number_of_portraits_to_be_uploaded
'number_of_portraits_to_be_uploaded': portraits_not_uploaded_count
}
return jsonify(**res)

Expand All @@ -133,7 +138,7 @@ def system():
@app.route('/acquire_lock', methods=['POST'])
@login_required
def acquire_lock():
acquired = photobooth.lock.acquire(False)
acquired = rlock.acquire(False)
if acquired:
return jsonify(message='Lock acquired')
else:
Expand All @@ -144,7 +149,7 @@ def acquire_lock():
@login_required
def release_lock():
try:
photobooth.lock.release()
rlock.release()
except Exception:
pass
finally:
Expand Down
Loading

0 comments on commit 98a5f02

Please sign in to comment.