From 6f4caf7f799d2d355bf0b0727e9663ce87f466af Mon Sep 17 00:00:00 2001 From: David Rodenkirchen Date: Wed, 2 Apr 2025 08:55:43 +0200 Subject: [PATCH] add receipt printer code --- .gitignore | 154 +++++++++++++++++++++++++++++++++++++++++ README.md | 18 +++++ init_printer.py | 57 +++++++++++++++ logo.png | Bin 0 -> 15429 bytes main.py | 78 +++++++++++++++++++++ post_request_tester.py | 21 ++++++ 6 files changed, 328 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 init_printer.py create mode 100644 logo.png create mode 100644 main.py create mode 100644 post_request_tester.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d17b523 --- /dev/null +++ b/.gitignore @@ -0,0 +1,154 @@ +# CONFIG +*.toml + +# Chatch-all default Python gitignore + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# plaintext files +*.txt + +# csv-files +*.csv + +# certificates +*.crt + +# icons +*.ico + +# C extensions +*.so + +# PyCharm +.idea/ + +# Excel tables +*.ods +*.xlsx + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# JSON containing project data +projects.json + +# Authentication directoy +auth/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..1350a1e --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Receipt Printer + +This software is designed to run on a debian based system and acts as a middle-man between the EZ LAN Manager database and the USB interface of the receipt printer. + +# Notes + +- Does only work on Linux +- Needs root privileges or udev config to work +- Designed to run alone on a Raspberry Pi +- Not meant to be exposed to the internet, local deployment only +- For EZ LAN Manager configuration, check README in that repo + +# Deploy + +1. Configure password and port in `main.py` +2. Make sure USB printer is connected to device +3. Install requirements with sudo +4. Run `main.py` with sudo diff --git a/init_printer.py b/init_printer.py new file mode 100644 index 0000000..9858e86 --- /dev/null +++ b/init_printer.py @@ -0,0 +1,57 @@ +import logging + +import usb.core +import usb.util +from usb.core import Endpoint + +logger = logging.getLogger(__name__) + +def init_printer() -> tuple[Endpoint, Endpoint]: + """ + ! DEBUG ONLY ! + Initializes printer and returns the input and output endpoints, in that order. + """ + dev = usb.core.find(idVendor=0x28e9, idProduct=0x0289) + + if dev is None: + raise ValueError("Printer could not be found") + else: + logger.debug("Printer found!") + + # Check for config + cfg = dev.get_active_configuration() + logger.debug(f"Found configuration: {cfg.bConfigurationValue}") + + intf = cfg[(0, 0)] # Select first interface and first setting + + logger.debug(f"Interface #: {intf.bInterfaceNumber}, Endpoints: {intf.bNumEndpoints}") + + # Find Bulk OUT- und IN-Endpoints + ep_out = usb.util.find_descriptor( + intf, + custom_match=lambda endpoint: usb.util.endpoint_direction(endpoint.bEndpointAddress) == usb.util.ENDPOINT_OUT + ) + + ep_in = usb.util.find_descriptor( + intf, + custom_match=lambda endpoint: usb.util.endpoint_direction(endpoint.bEndpointAddress) == usb.util.ENDPOINT_IN + ) + + # Validate endpoints + if ep_out: + logger.debug(f"OUT-Endpoint found: {ep_out.bEndpointAddress}") + else: + raise ValueError("OUT-Endpoint not found") + + if ep_in: + logger.debug(f"IN-Endpoint found: {ep_in.bEndpointAddress}") + else: + raise ValueError("IN-Endpoint not found") + + if dev.is_kernel_driver_active(intf.bInterfaceNumber): + try: + dev.detach_kernel_driver(intf.bInterfaceNumber) + except usb.core.USBError as e: + raise ValueError(f"Could not detach kernel driver from interface({intf.bInterfaceNumber}): {e}") + + return ep_in, ep_out diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2d77216c2792e97e8143814e301215c0333eaffd GIT binary patch literal 15429 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 vxP13_4`R1;OSg1Ow{%OlbW68 str: + return (f"EZ GG e.V. - EZ LAN 1.0\n\n" + f"Bestellungs-ID: {order_id}\n" + f"Bestellt: {order_date.strftime('%d.%m. - %H:%M')}\n" + f"Gast: {customer_name}\n" + f"Sitzplatz: {seat_id}\n\n") + +def format_order(order: list[OrderItem]) -> list[str]: + formatted_order = [] + for item in order: + formatted_text = f"{item.amount}x {item.menu_item_name}" + formatted_text = formatted_text.replace("ä", "ae") + formatted_text = formatted_text.replace("ö", "oe") + formatted_text = formatted_text.replace("ü", "ue") + formatted_text = formatted_text.replace("ß", "ss") + if len(formatted_text) < MAX_LINE_LEN: + while len(formatted_text) < MAX_LINE_LEN: + formatted_text = formatted_text.replace(" ", " ", 1) + else: + formatted_text = formatted_text[:MAX_LINE_LEN] + + formatted_order.append(formatted_text + "\n") + return formatted_order + +def print_order(order: Order, printer_: Usb) -> None: + header = build_header(order.order_id, order.order_date, order.customer_name, order.seat_id) + items = format_order(order.items) + printer_.image("logo.png") + printer_.text(header) + for item in items: + printer_.text(item) + printer_.cut() + +api = FastAPI() + + +@api.post("/print_order") +async def print_order_api_endpoint(order: Order, x_password: str = Header(None)): + if x_password != SECRET_PASSWORD: + raise HTTPException(status_code=401, detail="Unauthorized") + + for item in order.items: + if item.amount < 1: + return 422 + + print_order(order, PRINTER) + + return "done" + +if __name__ == "__main__": + print("Starting receipt printing server...") + uvicorn.run(api, host="0.0.0.0", port=5000, log_level="warning") diff --git a/post_request_tester.py b/post_request_tester.py new file mode 100644 index 0000000..2020cbc --- /dev/null +++ b/post_request_tester.py @@ -0,0 +1,21 @@ +import requests + +if __name__ == "__main__": + # Helper for testing the API + response = requests.post( + "http://127.0.0.1:5000/print_order", + json={ + "order_id": "12345", + "order_date": "2025-04-02T06:34:08.012Z", + "customer_name": "Typhus", + "seat_id": "A13", + "items": [ + { + "menu_item_name": "Bier", + "amount": 2 + } + ] + }, + headers={"x-password": "Alkohol1"} + ) + print(response)