import os.path import time import traceback from typing import List from PIL import Image from escpos.escpos import Escpos from escpos.printer import Usb from Event import Event, load_event from Photo import Photo from config import * from enum import Enum from config import read, file_increase, iso, now from timer import Timer from libcamera import Transform import RPi.GPIO as GPIO GPIO_LED_BORDER = 7 GPIO_SPOT_RIGHT = 35 GPIO_SPOT_LEFT = 37 GPIO_BUTTON_LED = 36 GPIO_BUTTON = 38 # GPIO helpers spot_mode = 0 def led_border(state): global GPIO_LED_BORDER GPIO.output(GPIO_LED_BORDER, state) def set_spot_mode(new_mode): global spot_mode spot_mode = new_mode % 4 GPIO.output(GPIO_SPOT_LEFT, not (spot_mode & 1)) GPIO.output(GPIO_SPOT_RIGHT, not (spot_mode & 2)) def next_spot_mode(): global spot_mode set_spot_mode(spot_mode + 1) # user interface def button_press(gpio_id): next_spot_mode() # INIT GPIO GPIO.setmode(GPIO.BOARD) GPIO.setwarnings(False) GPIO.setup(GPIO_SPOT_RIGHT, GPIO.OUT, initial=GPIO.HIGH) GPIO.setup(GPIO_SPOT_LEFT, GPIO.OUT, initial=GPIO.HIGH) GPIO.setup(GPIO_LED_BORDER, GPIO.OUT, initial=GPIO.LOW) GPIO.setup(GPIO_BUTTON_LED, GPIO.OUT, initial=GPIO.LOW) GPIO.setup(GPIO_BUTTON, GPIO.IN) GPIO.add_event_detect(GPIO_BUTTON, GPIO.FALLING, button_press, bouncetime=300) led_border(False) time.sleep(0.02) led_border(True) time.sleep(0.02) led_border(False) time.sleep(0.02) led_border(True) time.sleep(0.02) led_border(False) time.sleep(0.02) led_border(True) time.sleep(0.02) led_border(False) class State(Enum): IDLE = 0 COUNTDOWN = 1 SHOOTING = 2 PREPARE = 3 CHOOSE = 4 QR = 5 res = (1024,768) class Fotobox: def __init__(self, camera): camera.vflip = False camera.hflip = True camera.resolution = (2592, 1944) camera.rotation = 270 camera.preview_configuration.main.size = res camera.preview_configuration.main.format = 'BGR888' camera.configure("preview") self._capture_config = camera.create_still_configuration() camera.still_configuration.main.size = (2592, 1944) camera.start() self._camera = camera self._running: bool = True self._state: State = State.IDLE self._photos: List[Photo] = [] self._choice: Photo | None = None self._countdown_timer: Timer = Timer("countdown", 0, 1, COUNTDOWN_COUNT + 1, self._countdown_callback) self._shooting_timer: Timer = Timer("shooting", 0, SHOOTING_INTERVAL, SHOOTING_COUNT, self._shooting_callback) self._qr_timer: Timer = Timer("qr", 0, QR_TIMEOUT, 1, self._qr_callback) self._fotobox_uuid = read("./data/fotobox.uuid") print("Starting fotobox: F-%s" % self._fotobox_uuid) self._runtime_datetime = now() self._runtime_number = file_increase("./data/runtime.number") print("Starting runtime: R%04d-%s" % (self._runtime_number, iso(self._runtime_datetime))) self._event: Event | None = load_event() self._idle_url_without_protocol = "%s/e/%s" % (DOMAIN, self._event.code) if self._event is not None else DOMAIN self._idle_url_with_protocol = "http%s://%s/" % ('s' if HTTPS else '', self._idle_url_without_protocol) self._idle_qr = qr_create(self._idle_url_with_protocol) self._idle_qr_rect = self._idle_qr.get_rect(center=SCREEN_RECT.center) self._idle_url = FONT_URL.render(self._idle_url_without_protocol, True, (255, 255, 255)) self._idle_url_rect = self._idle_url.get_rect(centerx=SCREEN_RECT.centerx, bottom=HEIGHT - BORDER) self._shooting_number = 0 self._shooting_datetime = None self._printer: Escpos | None = None def _countdown_callback(self): if self._countdown_timer.complete(): self._set_state(State.SHOOTING) else: print("COUNTDOWN: %d" % self._countdown_timer.rest()) def _shooting_callback(self): print("SHOT: %d/%d" % (self._shooting_timer.current(), SHOOTING_COUNT)) screen.fill((255, 255, 255)) pygame.display.flip() if not DEBUG: time.sleep(0.5) frame = self._camera.switch_mode_and_capture_array(self._capture_config, "main") image = Image.fromarray(frame) self._photos.append(Photo(self._shooting_timer.current(), image)) screen.fill((0, 0, 0)) pygame.display.flip() if self._shooting_timer.complete(): self._set_state(State.PREPARE) elif not DEBUG: start = time.time() while time.time() - start < 1: self._draw_live_frame() pygame.display.flip() def _qr_callback(self): print("QR TIMEOUT") self._set_state(State.IDLE) def run(self): if DEBUG: self._set_state(State.SHOOTING) while self._running: self._events() self._timer() self._draw() def _events(self): for event in pygame.event.get(): if event.type == pygame.QUIT: self._running = False elif event.type == pygame.MOUSEBUTTONDOWN: if self._state == State.IDLE: self._set_state(State.COUNTDOWN) elif self._state == State.CHOOSE: if CANCEL_RECT.collidepoint(event.pos): print("CANCEL") self._set_state(State.IDLE) elif SAVE_RECT.collidepoint(event.pos) and self._choice is not None: print("SAVE") self._set_state(State.QR) else: for photo in self._photos: if photo.thumb_rect.collidepoint(event.pos): self._choice = photo print("CHOSE: %d" % self._choice.number) elif self._state == State.QR: if DONE_RECT.collidepoint(event.pos): print("DONE") self._set_state(State.IDLE) elif self._printer is not None and not self._printed and PRINT_RECT.collidepoint(event.pos): print("PRINT") try: self._printer.set(align='center', font='a', width=1, height=1) self._printer.text("Fotobox\n") url = "http%s://%s" % ('s' if HTTPS else '', DOMAIN) if self._event is not None: self._printer.text(self._event.title + "\n") self._printer.text(self._event.date.strftime("%d.%m.%Y") + "\n") if self._event.gallery: self._printer.text(self._event.url_without_protocol + "\n") self._printer.text("Passwort: " + self._event.password + "\n") self._printer.text("Dein Foto findest du hier:\n") self._printer.qr(self._choice.urlWithProtocol, center=True, size=5) self._printer.text(url + "\n") self._printer.text("/p/" + self._choice.code + "\n") self._printer.cut() self._printed = True except Exception as e: print(e) def _timer(self): dt: float = clock.tick(20) / 1000 self._shooting_timer.tick(dt) self._countdown_timer.tick(dt) self._qr_timer.tick(dt) def _draw(self): screen.fill((0, 0, 0)) if self._state == State.IDLE: screen.blit(FOTOBOX_TITLE_SURFACE, FOTOBOX_TITLE_RECT) screen.blit(self._idle_qr, self._idle_qr_rect) screen.blit(self._idle_url, self._idle_url_rect) elif self._state == State.COUNTDOWN: self._draw_live_frame() if self._countdown_timer.rest() == 3: screen.blit(FOTOBOX_COUNTDOWN3_SURFACE, FOTOBOX_COUNTDOWN3_RECT) elif self._countdown_timer.rest() == 2: screen.blit(FOTOBOX_COUNTDOWN2_SURFACE, FOTOBOX_COUNTDOWN2_RECT) elif self._countdown_timer.rest() == 1: screen.blit(FOTOBOX_COUNTDOWN1_SURFACE, FOTOBOX_COUNTDOWN1_RECT) elif self._state == State.CHOOSE: screen.blit(CHOOSE_SURFACE, CHOOSE_RECT) screen.blit(CANCEL_SURFACE, CANCEL_RECT) if self._choice is not None: screen.blit(SAVE_SURFACE, SAVE_RECT) for photo in self._photos: screen.blit(photo.thumb_surface, photo.thumb_rect) if self._choice == photo: rect = pygame.Rect( photo.thumb_rect.x - CHOICE_BORDER, photo.thumb_rect.y - CHOICE_BORDER, photo.thumb_rect.width + 2 * CHOICE_BORDER, photo.thumb_rect.height + 2 * CHOICE_BORDER ) pygame.draw.rect(screen, (0, 255, 0), rect, width=CHOICE_BORDER) elif self._state == State.QR: screen.blit(self._qr, self._qr_rect) screen.blit(self._choice.chosen_surface, self._choice.chosen_rect) if self._printer is not None and not self._printed: screen.blit(PRINT_SURFACE, PRINT_RECT) screen.blit(DONE_SURFACE, DONE_RECT) pygame.display.flip() def _draw_live_frame(self): array = self._camera.capture_array() img = pygame.image.frombuffer(array.data, res, 'RGB') screen.blit(img, (0, 0)) def _set_state(self, new_state: State): print(new_state) self._state = new_state if new_state == State.COUNTDOWN: led_border(True) self._countdown_timer.restart() elif new_state == State.SHOOTING: self._photos = [] self._shooting_number += 1 self._shooting_datetime = now() print("Starting shooting: S%04d-%s" % (self._shooting_number, iso(self._shooting_datetime))) self._shooting_timer.restart() elif new_state == State.PREPARE: led_border(False) screen.fill((0, 0, 0)) screen.blit(LOADING_SURFACE, LOADING_RECT) pygame.display.flip() for photo in self._photos: photo.prepare(self._event) self._set_state(State.CHOOSE) elif new_state == State.CHOOSE: if DEBUG: self._choice = self._photos[0] self._set_state(State.QR) else: self._choice = None elif new_state == State.QR: screen.fill((0, 0, 0)) screen.blit(LOADING_SURFACE, LOADING_RECT) pygame.display.flip() self._printed = False try: self._printer = Usb(0x0483, 0x5840, 0, interface=0, out_ep=0x04, in_ep=0x82) self._printer.profile.media['width']['pixels'] = 512 print("Printer AVAILABLE") except Exception as e: print("No printer available") traceback.print_exc() self._printer = None self._qr = qr_create(self._choice.urlWithProtocol) self._qr_rect = self._qr.get_rect(left=2 * BORDER, centery=SCREEN_RECT.centery) self.save(self._choice.photo_image, "", False) self.save(self._choice.framed_image, "framed", True) self._qr_timer.restart() def save(self, img: Image, suffix: str, do_upload: bool): filename = "F-%s---R%04d-%s---S%04d-%s---P%04d-%s---%s%s.jpg" % ( self._fotobox_uuid, self._runtime_number, iso(self._runtime_datetime), self._shooting_number, iso(self._shooting_datetime), self._choice.number, iso(self._choice.datetime), self._choice.code, "---" + suffix if suffix != "" else "" ) path = Path("./data/photos/original/F-%s/R%04d-%s/%s" % ( self._fotobox_uuid, self._runtime_number, iso(self._runtime_datetime), filename, )) path.parent.mkdir(parents=True, exist_ok=True) img.convert('RGB').save(path, format='JPEG', quality=95) if do_upload: upload = Path("./data/photos/upload/%s.jpg" % (self._choice.code,)) upload.parent.mkdir(parents=True, exist_ok=True) os.chmod(upload.parent, 0o777) os.link(path, upload) os.chmod(upload, 0o777) print("Photo saved: %s" % path.absolute())