A vibe coded python app for (80mm) esc/pos thermo printers
  • Python 47%
  • CSS 28.4%
  • HTML 24.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-13 14:18:07 +02:00
static Add favicon 2026-08-13 14:11:00 +02:00
templates Fix typo 2026-08-13 14:12:10 +02:00
.gitignore minor changes 2026-08-13 11:20:33 +02:00
app.py inital commit 2026-08-13 11:17:54 +02:00
printer.py inital commit 2026-08-13 11:17:54 +02:00
README.md minor changes 2026-08-13 14:18:07 +02:00
requirements.txt inital commit 2026-08-13 11:17:54 +02:00

Thermal Printer Web App

A small Flask app for printing grocery lists and short notes to a USB ESC/POS thermal printer (built for an FEC-80T on a Raspberry Pi, but works with any 80mm ESC/POS printer).

Features

  • Grocery list — type items, print with checkboxes
  • Short messages — leave notes for roommates with optional "To / From"
  • Custom logo — upload an image, auto-resized and dithered for clean thermal output
  • Test page — verify formatting and line width
  • Mobile-friendly UI

Setup

# From your existing venv (~/escpos-env)
source ~/escpos-env/bin/activate

# Copy this folder somewhere, then:
cd thermal_printer_app
pip install -r requirements.txt

Run (development)

The Flask dev server is fine for tinkering:

python app.py

Run (production / Gunicorn)

gunicorn -w 1 --threads 4 -b 0.0.0.0:5000 app:app

Important: always use -w 1 (one worker process). The USB printer is a serialized hardware resource — multiple worker processes would race for it.

--threads 4 lets the single worker handle multiple HTTP requests concurrently (HTML + CSS + favicon load in parallel). Without it, page loads feel sluggish. Concurrent print jobs from different threads are serialized by an internal lock in printer.py.

Then open http://<your-pi-ip>:PORT from your phone or laptop.

Configuration

Edit the top of printer.py:

PRINTER_CONFIG = {
    "idVendor": 0x0525,
    "idProduct": 0xA700,
    "in_ep": 0x81,
    "out_ep": 0x02,
    "profile": "default",
}

LINE_WIDTH = 48        # chars per line at Font A — try 42 if 48 wraps
MAX_LOGO_WIDTH = 400   # logo pixel width before printing

If your printer's separators wrap to a second line, drop LINE_WIDTH to 42. Crosscheck with printer manual.

Running on boot (optional)

Create /etc/systemd/system/thermalprint.service:

[Unit]
Description=Thermo Printer Web App
After=network.target

[Service]
Type=simple
User=$USER
WorkingDirectory=/home/$USER/thermal_printer_app
ExecStart=/home/$USER/escpos-env/bin/gunicorn -w 1 --threads 4 -b 0.0.0.0:5000 --access-logfile - app:app
Restart=on-failure

[Install]
WantedBy=multi-user.target

Then:

sudo systemctl daemon-reload
sudo systemctl enable --now thermalprint

# Watch logs
journalctl -u thermalprint -f

File layout

thermal_printer_app/
├── app.py              # Flask routes
├── printer.py          # ESC/POS print functions
├── requirements.txt
├── config.json         # Created at runtime (logo settings)
├── static/
│   ├── style.css
│   └── logos/          # Uploaded logos go here
└── templates/
    ├── base.html
    ├── index.html
    ├── grocery.html
    ├── message.html
    └── settings.html

Notes

  • The app binds to 0.0.0.0:PORT so anything on your LAN can reach it. Don't expose it directly to the public internet — there is no authentication.
  • A .flask_secret file is auto-generated on first run (used to sign session cookies for flash messages). Keep it private; don't commit it.
  • For remote access, use Tailscale, WireGuard, or an SSH tunnel rather than port-forwarding.