Commit 03cc83c4 authored by Jakob Moser's avatar Jakob Moser
Browse files

Merge branch 'master' into 'feat/play-sounds'

# Conflicts:
#   poolpay/__main__.py
#   poolpay/director/Director.py
parents 45460a56 3fc63f01
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -2,6 +2,12 @@

Maintainer: Jakob Moser <moser@cl.uni-heidelberg.de>

## Shop

First, you need to buy all the necessary components. An incomplete shopping list (it does not contain the Raspberry Pi 2B I used, and the necessary components to get it running in basic form) is provided below.

* [Shopping List (via reichelt.de)](https://www.reichelt.de/my/2266576)

## Setup

### Base Image
+1 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ Description=PoolPay
ExecStart=/coli/poolpay/venv/bin/python3 -m poolpay
WorkingDirectory=/coli/poolpay
User=poolpay
Restart=always

[Install]
WantedBy=multi-user.target
+55 −18
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ from threading import Thread
from types import FrameType

from poolpay import db, paths
from poolpay.card.CardReaderException import CardReaderException
from poolpay.card.Pirc522CardReader import Pirc522CardReader
from poolpay.director.Director import Director
from poolpay.display.PygameDisplay import PygameDisplay
@@ -16,7 +17,7 @@ from poolpay.wire.Message import Message
from poolpay.wire.Server import Server

logging.basicConfig(
    level=logging.INFO, format="[%(levelname)s] <%(module)s> %(message)s"
    level=logging.DEBUG, format="[%(levelname)s] <%(module)s.%(funcName)s> %(message)s"
)

logging.info("Starting PoolPay")
@@ -28,30 +29,56 @@ sound_library = SoundLibrary()
director = Director(play, display, sound_library)


def launch_application(password: str) -> None:
def _initialize_card_reader_and_run() -> None:
    logging.info("Initializing Pirc522CardReader")
    with Pirc522CardReader() as card_reader:
        logging.info("Registering card removal handler")
        card_reader.on_card_removed(lambda reader: director.card_removed())

        logging.info("Registering card placement handler and starting to wait")
        card_reader.on_card_placed(
            lambda reader: director.card_placed(reader.read_id())
        )


def _initialize_card_reader_and_run_with_automatic_restart() -> None:
    while True:
        try:
            _initialize_card_reader_and_run()
        except CardReaderException as e:
            logging.error("Card reader errored, reinitialization needed")
            logging.exception(e)
            logging.debug("Proceeding to reinitialize card reader")


def launch_application(password: str | None) -> None:
    if director.started:
        logging.warning(
            "Application is already launched, don't launch it a second time"
        )
        return

    logging.info("Opening vault, creating CardReader")
    with vault.open(password), Pirc522CardReader() as card_reader, touch_screen:
    open_vault = vault
    if password:
        logging.info("Opening vault")
        open_vault = vault.open(password)
    else:
        logging.info("No password to open vault was given, so assuming it was already open")

    with open_vault, touch_screen:
        try:
            logging.info("Opening database")
            db.open(paths.db_file)
            logging.info("Starting database")
            logging.info("Starting director")
            director.start()
            logging.info("Registering card removal handler")
            card_reader.on_card_removed(lambda reader: director.card_removed())
            logging.info("Registering touch screen input handler")
            touch_screen.on_screen_touched(director.screen_touched)
            logging.info("Opening sound library")
            sound_library.open()
            logging.info("Registering card placement handler and starting to wait")
            card_reader.on_card_placed(
                lambda reader: director.card_placed(reader.read_id())
            )
            _initialize_card_reader_and_run_with_automatic_restart()
        except Exception as e:
            logging.exception(e)
            raise e
        finally:
            logging.info("Closing database")
            db.close()
@@ -61,18 +88,21 @@ def launch_application(password: str) -> None:
            server.stop()


def launch_application_as_daemon(password: str | None) -> None:
    logging.info("Launching main application as daemon thread")
    Thread(
        target=launch_application, args=(password,), daemon=True
    ).start()


logging.info("Creating wire.Server")
server = Server(paths.socket_path)


def on_message_received(message: Message) -> None:
    if message.get("action") == "unlock" and message.get("password"):
        logging.info(
            "Unlock message received via wire, starting thread to launch main application"
        )
        Thread(
            target=launch_application, args=(message["password"],), daemon=True
        ).start()
        logging.info("Unlock message received via wire")
        launch_application_as_daemon(message["password"])


server.on_receive(on_message_received)
@@ -89,5 +119,12 @@ logging.info("Registering SIGINT and SIGTERM handlers")
signal.signal(signal.SIGINT, handle_signal)  # for keyboard interrupt
signal.signal(signal.SIGTERM, handle_signal)  # for systemd interrupt

if vault.is_open:
    # The vault has already been opened, so we don't need to wait for a password to start
    launch_application_as_daemon(None)
    # We will still launch the server, in case I ever get around to implementing more commands
else:
    logging.info("Waiting for vault unlock password via wire server")

# As documented above, we launch the server in any case
server.start()
+2 −0
Original line number Diff line number Diff line
class CardReaderException(Exception):
    pass
+14 −0
Original line number Diff line number Diff line
from collections.abc import Callable
import logging
from time import sleep

import RPi.GPIO as GPIO
from pirc522 import RFID
from pirc522.rfid import logger as pirc522_logger  # We need this for some ugly monkey-patching, see below

from poolpay.card.CardReader import CardReader
from poolpay.card.raise_on_error_e1 import raise_on_error_e1


class Pirc522CardReader(CardReader):
@@ -20,6 +23,13 @@ class Pirc522CardReader(CardReader):
        self._reader = RFID(bus=1, device=0, pin_irq=None, pin_rst=33)
        self._handle_removal: Callable[[CardReader], None] | None = None

        # Ugly monkey-patching: There is a crash scenario in which the card reader no longer returns anything,
        # even when a card is presented (the rest of the application keeps running, although that is not particularly
        # relevant). The only indication of this happening is an "Error E1" logged as warning to the logger in the
        # pirc522 module. We replace the logger.warning function with one that is almost identical, but throws
        # an exception if the message is "Error E1", so that we can handle the crash properly.
        pirc522_logger.warning = raise_on_error_e1(pirc522_logger.warning)

    def read_id(self) -> int | None:
        self._reader.anticoll()
        uid = self._reader.read_id()
@@ -41,9 +51,11 @@ class Pirc522CardReader(CardReader):

            if uid is None:
                if card_recently_placed and self._handle_removal is not None:
                    logging.debug("Cannot read uid, but was previously able to => card removed")
                    self._handle_removal(self)
                card_recently_placed = False
            elif not card_recently_placed:
                logging.debug("Can read id, but was not previously able to => card newly placed")
                card_recently_placed = True
                handle_card(self)

@@ -53,4 +65,6 @@ class Pirc522CardReader(CardReader):
        self._handle_removal = handle_removal

    def close(self) -> None:
        logging.debug("Closing reader")
        self._reader.cleanup()
        logging.debug("Closed reader")
Loading