Skip to content
HostisHumaniPublic

About

A modular ESC/POS printing platform with a modern web interface.

Topics

Resources

Stars

14 stars

Watchers

0 watching

Forks

Latest commit

 

History

53 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ReceiptPi Logo

License Status Python Platform Last Commit

Features • Quick Start • Roadmap • Issues

ReceiptPi

Deutsche Version / German version

Flask server for an ESC/POS thermal receipt printer (tested with an Epson TM-T88V) on a Raspberry Pi Zero (2) W. Mobile web UI (German/English) for printing lists (shopping, to-do, task/kanban cards), status messages, weather reports, images, system reports, and offline pen-and-paper games (Sudoku, a dice score sheet, Tic-Tac-Toe boards) - most print types optionally prefixed with a small logo - plus a generic automation webhook (compatible with Home Assistant, Node-RED, n8n, or any tool that can do an HTTP POST) and other automation triggers (GitHub stars, Fritz!Box guest network, storm warnings).

ReceiptPi Web Interface

Quick start

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

cp config.example.py config.py
# Fill config.py with real values (Home Assistant token, Netatmo entities, Fritz!Box credentials, ...)

python3 app.py

The server then runs on port 5000, web UI at http://<pi-hostname>:5000. python3 app.py uses Flask's development server - for production the service runs via Gunicorn with gunicorn.conf.py and exactly 1 worker, which is required because the print queue is process-local.

Most settings (quiet-hour rules, weather locations, GitHub-watched repos, SSH targets for the system report, per-module logos, UI language) are configured entirely through the web UI's Settings page - no need to touch config.py for those. Only bootstrap-level values (secret key, API token, printer USB IDs, real credentials such as the Home Assistant token or Fritz!Box password) stay in config.py, since ReceiptPi currently has no login/auth for the web UI itself.

Structure

receiptpi/
├── app.py                 Create the Flask app, register blueprints
├── gunicorn.conf.py       on_starting hook for the boot greeting
├── print_queue.py         central print queue + print-rule checks
├── printer.py             printer hardware access (USB)
├── security.py            CSRF protection, API token protection, JSON parsing
├── secrets_crypto.py      Fernet encryption for system-report SSH passwords and the recipe manager API token (key in STATE_DIR, not in git)
├── settings_store.py      central settings (JSON in STATE_DIR, not in the project folder)
├── history_store.py       SQLite print history (STATE_DIR), auto-pruned after 180 days
├── lists_store.py          in-progress list/task-card drafts (STATE_DIR), cleared once printed
├── pending_store.py        blocked/failed print jobs (STATE_DIR, 0600), see "Pending jobs" below
├── logos.py                per-print-type logo resolution, upload validation, seeding
├── i18n.py                 minimal translation lookup (JSON files, no Flask-Babel)
├── module_catalog.py       registry of the toggleable home-page modules (key, icon, URL) + their effective on/off state
├── text_style.py           Easy-Read text-size scaling, shared by print jobs and the web UI
├── themes.py                registry of the selectable web UI color themes
├── version.py               reads VERSION, minimal comparison for ReceiptPi's own version scheme
├── VERSION                  single source of truth for the running version (e.g. "0.1.9alpha")
├── config.example.py       template for config.py (fill in locally)
├── modules/
│   ├── lists/                 shopping lists, to-do lists, task/kanban cards
│   ├── message/               status messages (free title + text)
│   ├── images/                print images
│   ├── wifi/                  guest wifi slip (text + QR code)
│   ├── weather/                weather report (DWD + Netatmo)
│   ├── system/                 system report (Proxmox/PBS via SSH, plus custom SSH commands for other hosts)
│   ├── games/                  offline games (Sudoku, dice score sheet, Tic-Tac-Toe)
│   ├── recipes/                recipe search/print + shopping list import (Mealie)
│   ├── automation/            generic automation webhook
│   ├── history/                print history dashboard
│   ├── pending/                 review/reprint/discard blocked or failed print jobs
│   └── settings/                settings pages (web UI) + settings API
├── watchers/
│   ├── github_star_watch.py     cron: prints on a new GitHub star
│   ├── fritzbox_wifi_watch.py    cron: prints a wifi slip once the guest network is enabled
│   ├── storm_warning_watch.py    cron: prints on a new active storm warning
│   ├── update_check_watch.py     cron (every 12h): caches whether a newer GitHub release exists, for the footer
│   └── crontab.example           ready-to-adjust cron template for all four watchers above
├── assets/example-logos/    bundled starter logo set (outline icons)
├── static/
│   ├── style.css             design system (spacing/colors/themes, card/tile/icon styles)
│   ├── draft-autosave.js      generic auto-save for the lists/task-card forms
│   ├── mealie-import.js       Mealie shopping list import on the shopping list page
│   ├── manifest.json          web app manifest (home-screen name/icons/colors)
│   ├── sw.js                  service worker (served at /sw.js, HTTPS only)
│   └── icons/                 local Lucide SVG icon set (see LICENSE in that folder)
└── templates/                shared layout and module pages
    ├── base.html
    ├── home.html
    ├── shopping.html
    ├── lists_*.html            lists overview, to-do page, task/kanban card page
    ├── message.html
    ├── images.html
    ├── wifi.html
    ├── weather.html
    ├── system.html
    ├── history.html
    ├── games_*.html            games overview + one page per game
    ├── recipes*.html           recipe search/random picker + recipe preview
    └── settings_*.html        settings overview + one sub-page per area

Each feature is implemented as its own Flask blueprint. Unhandled exceptions are isolated to the current request; modules share the same process, print queue and settings store.

Web UI

The web UI is mobile-first (a single narrow column, no separate desktop layout) and built as a small card/tile-based design system rather than plain forms:

  • Home/dashboard (/) - a status strip (printer reachability, polled live via /health every 60 seconds; how many of the toggleable modules are currently active; total print count from the history store), followed by a tile grid linking to every currently enabled module.
  • Navigation - a topbar wordmark, a small printer-status dot, and a hamburger menu (History, Settings).
  • Themes - 6 selectable color themes (Warm [default], Forrest, Dark Lime, Frost, Butter Bean, White Purple), picked under /settings/design and applied via a data-theme attribute plus CSS custom properties. Warm is the only theme that also follows the system/browser light-dark preference automatically.
  • Icons - a local set of Lucide SVG icons under static/icons/ (no external icon font or CDN), rendered via CSS mask-image so every icon automatically follows the active theme's accent color instead of being hardcoded to one color. See Third-Party Licenses.
  • Easy-Read - one text-size switch scales both the web UI and the receipt output, see Easy-Read below.
  • Footer - shows the running version and a link to the GitHub repo on every page; if watchers/update_check_watch.py (cron, every 12h) has found a newer GitHub release (including alpha/prereleases), the footer links directly to it. Information only, no auto-update.

Each page shows a small icon matching its content in the topbar (a printer icon on the home page, a message icon on the message page, and so on). There is no browser-tab favicon yet.

Home screen / installable web app - static/manifest.json plus a theme-color and an apple-touch-icon in templates/base.html let the web UI be added to a phone's home screen with its own name and icon (iOS: Safari share menu -> "Add to Home Screen"; Android: browser menu). When the UI is served over HTTPS (e.g. behind a reverse proxy), a service worker (static/sw.js, served at /sw.js) is registered as well, so Chrome on Android offers "Install app". Over plain HTTP (http://<pi>:5000) no service worker is registered - the home-screen shortcut still works.

The service worker only caches same-origin /static/ files that carry a ?v=<content hash> (style sheet, fonts, icons, manifest) - these URLs change whenever their content does, so nothing stale can be served. Pages, /health, /print/*, /ui/*, /settings/* and every other request always go to the network. The cache is named after the VERSION file; a new release replaces the worker and deletes the old cache. It is not an offline mode - the UI still needs the Pi to be reachable. The icons under static/icons/ (icon-192.png, icon-512.png, icon-maskable-512.png, apple-touch-icon.png) are an interim solution derived from the existing logo - a proper icon set will follow.

Settings pages

/settings is an overview page (tile grid, grouped into General / Printing / Integrations / System) linking to a dedicated sub-page per area, instead of one long form:

  • /settings/language - UI language (German/English)
  • /settings/design - web UI color theme
  • /settings/modules - enable/disable individual home-page modules, see Module toggles
  • /settings/print-rules - quiet-hour rules (multiple independent rules, each with its own weekdays and time window) + rate limiting + duplicate suppression + the Easy-Read text-size switch
  • /settings/logos - global logo toggle, default logo, and a per-print-type toggle/upload/preview (falls back to the default logo if no custom one is set)
  • /settings/weather - weather report provider (DWD or Open-Meteo), weather locations, and an independent storm-warning provider (DWD, MeteoAlarm, or NWS) with its own enable toggle and an optional "ignore quiet hours" override
  • /settings/github-watch - watched GitHub repositories
  • /settings/system-report - a free-form list of SSH targets for the system report, see System report below
  • /settings/recipes - recipe manager (Off / Mealie), Mealie base URL, encrypted API token, connection test, see Recipes below

Endpoints

All /print/* and /settings/* endpoints require the X-Api-Token: <value> header once API_TOKEN is set in config.py (empty = no protection). Every print job also passes through the central print rules: quiet hours, rate limiting and duplicate suppression. A blocked job responds with 429.

Printing

  • POST /print/message - { "title": "...", "text": "..." }
  • POST /print/list - { "title": "...", "items": ["..."] } - shopping list, unchanged since before the lists module existed, kept fully backward compatible
  • POST /print/todo - { "title": "...", "items": ["..."] } - to-do list, same shape as /print/list
  • POST /print/task - { "title": "...", "description": "optional", "items": ["optional checklist"], "priority": "low"|"medium"|"high", "due_date": "YYYY-MM-DD" } - printable task/kanban card; only title is required
  • POST /print/image - multipart upload (max. 6000px per side, 12MB)
  • POST /print/wifi - { "ssid": "...", "password": "...", "auth_type": "WPA" }
  • POST /print/weather - optional { "location": "Berlin" }, otherwise the default location
  • POST /print/system - no body needed
  • POST /print/automation - { "title": "optional", "text": "..." } - generic automation webhook (compatible with Home Assistant, Node-RED, n8n, or any tool that can do an HTTP POST)
  • GET /health - check printer reachability (no token needed, but runs through the same queue; excluded from print history)

Settings API

  • GET /settings/api - current print rules + weather locations (JSON)
  • POST /settings/print_rules - change rate limit/duplicate window
  • GET|POST /settings/quiet_hours/rules, POST /settings/quiet_hours/rules/<id>/toggle, DELETE /settings/quiet_hours/rules/<id>
  • GET|POST /settings/weather/locations, DELETE /settings/weather/locations/<name>
  • GET /settings/system_report - current list of system-report hosts (JSON, passwords redacted to a has_password flag)
  • POST /settings/system_report - { "name": "...", "role": "proxmox"|"pbs"|"custom", "host": "...", "user": "...", "command": "required for role=custom", "password": "optional" } - adds one host
  • DELETE /settings/system_report/<id> - removes one host
  • GET|POST /settings/github_watch/repos, DELETE /settings/github_watch/repos/<owner>/<repo>
  • GET|POST /settings/logos/config - global/per-module logo toggles
  • POST /settings/logos/upload/<slot>, DELETE /settings/logos/upload/<slot> - logo image upload/removal (base64), slot is default or a module key

Recipes (web UI routes)

The recipes module has no /print/* JSON API and no settings API - its routes serve the web UI and need no X-Api-Token. All of them answer 404 while the module is inactive (see Module toggles).

  • GET /recipes?q=<term> - search page with results
  • GET /recipes/view/<slug> - recipe preview
  • GET /recipes/random?ingredients=<a,b>&match=all - random recipe; ingredients optional (max. 5), match=all requires all of them instead of any
  • POST /ui/recipes/print - form post (CSRF-protected), field slug
  • GET /recipes/import/lists - Mealie shopping lists as JSON (used by the import on /shopping)
  • GET /recipes/import/lists/<id> - unchecked items of one list as JSON, formatted as shopping list lines

After installation

pip freeze > requirements.lock.txt

Freezes the actually installed versions - requirements.txt itself deliberately has no version pins (no ==x.y.z), requirements.lock.txt is only a reference in case an update ever breaks something.

Enabling the watchers (cron)

The four scripts under watchers/ (GitHub star notifications, guest Wi-Fi slip, storm warnings, update check) only run when something schedules them - they are never started by app.py/Gunicorn itself. See watchers/crontab.example for a ready-to-adjust template covering all four with the interval each script's own docstring recommends; install it with crontab watchers/crontab.example after editing the PYTHON/PROJECT_DIR placeholders, or copy individual lines into an existing crontab (crontab -e).

Features

  • Lists: shopping lists, to-do lists, and printable task/kanban cards, see Lists below
  • Free-form messages
  • Image uploads and API image printing
  • Guest Wi-Fi credentials and QR codes
  • Weather reports (DWD or Open-Meteo, selectable, + optional Netatmo), with an optional storm-warning watcher (DWD, MeteoAlarm, or NWS, prints only when a warning is actually active)
  • System reports: Proxmox/PBS via SSH, plus a custom-command role for any other host (e.g. a NAS), with optional encrypted password auth, see System report below
  • Offline games (Sudoku, a dice score sheet, Tic-Tac-Toe), see Games below
  • Recipes from Mealie: search, preview and print recipes, random recipe by ingredient, and import a Mealie shopping list into the shopping list, see Recipes below
  • Print history dashboard (stats + paginated log, SQLite-backed)
  • Pending jobs: review/reprint/discard print jobs blocked by quiet hours/rate limit or failed with a real printer error, see "Pending jobs" below
  • Optional per-print-type logos with a global default fallback
  • Individually toggleable home-page modules, see Module toggles below
  • Web-based settings, split into per-area sub-pages and grouped by topic
  • 6 selectable web UI color themes
  • Easy-Read: one switch for larger text on both the receipt and the web UI, see Easy-Read below
  • German/English UI, including receipt content itself (not just the UI chrome)
  • USB-connected ESC/POS printer (python-escpos + pyusb, Epson TM-T88V profile)
  • Version footer with GitHub release update notice (checked every 12h, no auto-update)

Lists

Grew out of the original shopping-list module; all three list types share the same print queue, history logging, rate limiting and quiet hours as every other module.

  • Shopping list (/shopping, also reachable at /lists/shopping) - a plain checklist. The /print/list API endpoint is unchanged since before the lists module existed, kept fully backward compatible for existing integrations. This is the only list type with a logo slot (reuses the existing shopping logo slot) - to-do lists and task cards never show a logo.
  • To-Do list (/lists/todo) - the same checklist mechanism as the shopping list, printed and labelled separately. POST /print/todo JSON API.
  • Task/kanban card (/lists/task) - a title plus optional description, optional checklist, optional priority (low/medium/high) and optional due date; only fields actually set are printed, no empty placeholders on the receipt. POST /print/task JSON API - suitable for a plain curl call, a Home Assistant rest_command, or any other automation that can do an HTTP POST, with the same X-Api-Token auth, print queue, quiet hours, rate limit and history logging as every other /print/* endpoint.
  • Drafts - all three forms auto-save to the server (lists_store.py, ~1s after typing stops) so an in-progress list survives navigating away or closing the tab; a draft is only cleared once actually printed. A "Discard" button clears it back to a blank form.

System report

/settings/system-report manages a free-form list of SSH targets instead of a fixed set - each entry has a name, a role, a host and a user, and connects either via SSH key (default) or an optional password.

  • Proxmox and PBS are structured roles with their own fixed set of SSH commands (CPU/RAM/LXC/VM list for Proxmox, recent backup tasks for PBS).
  • Custom command runs an arbitrary user-supplied SSH command verbatim on the target host - the general-purpose role for anything that isn't Proxmox/PBS, e.g. a NAS of any kind. No NAS-specific software is assumed anywhere in the codebase.
  • An update check (apt list --upgradable) runs across every configured host regardless of role.
  • Password auth: an optional password field is the alternative to SSH-key auth, for a host with its own restricted, password-based user. Connects via paramiko instead of the system ssh CLI (avoids passing the password through a process argument, unlike sshpass). The password is encrypted at rest with Fernet (cryptography package) before being written to settings.json; the encryption key lives in its own file under STATE_DIR (secret.key, mode 0600), generated on first use, never in git and never in settings.json itself.

Games

Three offline games, printed through the same central print queue as every other module. Unlike most modules they are UI-only - there is no /print/games/* JSON API and they are not wired into the automation webhook - and they have no logo slot.

  • Sudoku (/games/sudoku) - a locally generated 9x9 puzzle with three difficulty levels (easy/medium/hard), with an option to also print the solution as a second receipt.
  • Dice score sheet (/games/dice) - a generic five-dice score pad (upper section ones-sixes with subtotal/bonus, lower section three-/four-of-a-kind, full house, small/large straight, five-alike, chance, plus upper/lower/grand totals). One receipt per player, 1-12 players per print.
  • Tic-Tac-Toe (/games/tictactoe) - printable blank 3x3 boards to fill in by hand, 3/6/9 rounds per print.

Recipes

Connects to a self-hosted Mealie instance (tested with v3.27.0). Set it up under /settings/recipes: base URL + an API token (created in Mealie under Profile -> API Tokens). The token is stored Fernet-encrypted (same mechanism as the system report SSH passwords) and never shown again; leaving the token field empty when saving keeps the stored one (only while the base URL stays the same - a changed URL needs the token entered again, so the stored token is never sent to a new address), and "Remove token" deletes it. "Test connection" checks both the URL and the token (via /api/users/self - Mealie's /api/app/about answers without authentication, so it alone can't validate a token) without saving anything.

The module is only active when its toggle under /settings/modules is on and a recipe manager is selected - with the recipe manager set to "Off" it behaves like a disabled module (no tile, 404 on all its routes, no import on /shopping), see Module toggles.

  • Search + print (/recipes) - searches recipe titles/descriptions (Mealie's search does not cover ingredients), shows a preview, prints title, total time, ingredients and instructions.
  • Random recipe - optionally filtered by one or more ingredients (any/all). Ingredient names must match a Mealie food exactly (case-insensitive, singular or plural) - otherwise close matches are suggested.
  • Shopping list import (on /shopping) - appends the unchecked items of a Mealie shopping list to the current list. Append-only, and read-only towards Mealie (nothing is checked off there).

UI-only (no /print/* JSON API, see Endpoints), no logo slot. Fraction characters in Mealie's ingredient text (e.g. ¹/₂) are printed as plain ASCII (1/2).

Easy-Read (large text)

A single switch under /settings/print-rules ("text size": Normal / Large) scales both the receipt output and the web UI from the same setting:

  • Receipt: headings print at double width/double height, body text at double width (which halves the usable line width, so body text wraps more often); the footer timestamp and section separators always stay at normal size.
  • Web UI: the base font size grows from 16px to 19px, and the home status strip and module tile grid drop from a multi-column to a single-column layout so nothing gets squeezed into a cramped column.

It is a plain text-size switch, not a full accessibility overhaul - there is no separate screen-reader markup or contrast mode tied to it.

Pending jobs

A print job blocked by quiet hours/rate limit, or one that fails with a real printer error (device unreachable, USB error, ...), doesn't just vanish - its content is saved so it can be reviewed and manually reprinted or discarded later, reachable via the burger menu. Applies across every source (web UI, JSON API, automation webhook, watchers) equally, since it hooks into enqueue_print() centrally rather than each module individually. Duplicate-suppression blocks are deliberately excluded - that reflects an already-accepted earlier submission of the same content, not lost content.

Check the box next to the entries you want, then choose "Print selected" or "Discard selected"; "Print all"/"Discard all" skip the selection entirely. A reprint bypasses quiet hours (an explicit, reviewed action) and duplicate suppression, but the rate limit still applies. An entry is only removed once its reprint is CONFIRMED successful - a failed retry leaves it in place, and never creates a second pending entry for the same content. /settings/print-rules has an optional auto-cleanup setting (0 = never, otherwise a number of days).

Not every job type is included - weather/system reports re-fetch live data on reprint anyway (no staleness concern), but a raw image upload is stored as its already-downscaled/dithered print-ready version (a few KB, not the original photo) rather than the full upload.

Module toggles

/settings/modules lets each of the 8 catalog modules (lists, message, weather, images, wifi, system, games, recipes) be switched on or off individually:

  • Disabling a module removes its tile from the home page and blocks all of its routes - web UI pages, /ui/* form posts, and /print/* API endpoints alike - with a 404, not just hiding the tile.
  • A module can have an additional precondition on top of its toggle: recipes is only active while a recipe manager is selected under /settings/recipes. With the recipe manager set to "Off" it is treated exactly like a disabled module; its checkbox here stays as saved and shows a note explaining why the module is hidden.
  • The home page's status strip shows how many of these 8 modules are currently active (toggle and precondition).
  • History, the Settings pages themselves, and the generic automation webhook are outside this toggle system and stay reachable regardless.
  • Two of the three watchers respect this: fritzbox_wifi_watch.py and storm_warning_watch.py check the wifi/weather module state and skip cleanly if disabled. github_star_watch.py does not check it and will simply fail its print call if the message module is disabled.

Roadmap

Next up: NFC-tag-triggered printing (just a URL in the tag, no app needed), Tandoor as a second recipe manager next to Mealie, and a proper icon set replacing the interim home-screen icons. The roadmap may change as ReceiptPi is tested on more hardware.

Contributing

Issues and pull requests are welcome. New modules should use Flask Blueprints, submit print jobs through the central queue and avoid direct writes to the printer or settings files.

Trademark Notice

Raspberry Pi is a trademark of the Raspberry Pi Foundation. This project is not affiliated with, endorsed by, or sponsored by the Raspberry Pi Foundation.

Epson and TM-T88V are trademarks of Seiko Epson Corporation. This project is not affiliated with, endorsed by, or sponsored by Epson.

ReceiptPi is an independent, community-built project designed to run on Raspberry Pi hardware and to be compatible with Epson ESC/POS thermal printers.

Third-Party Licenses

The web UI's icons are from Lucide (static/icons/), used under the ISC License - see static/icons/LICENSE for the full text.

The web UI's typefaces are IBM Plex Sans and IBM Plex Mono (static/fonts/), self-hosted rather than loaded from Google Fonts, used under the SIL Open Font License 1.1 - see static/fonts/LICENSE.txt for the full text.

License

MIT - see LICENSE.

About

A modular ESC/POS printing platform with a modern web interface.

Topics

Resources

Stars

14 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages