Skip to content

MachMitGoslar/thermal-printer

Repository files navigation

Thermal Printer Project

Markdown-to-ESC/POS printer stack for a MASUNG M_ONE_TYPE thermal printer, with a barcode scanner trigger and a remote admin panel.


Files

File Purpose
print_md.py Convert Markdown to ESC/POS and send via CUPS
scan_trigger.py Watch barcode scanner, fire print job on coord.info scan
admin.py Flask admin panel (service control, printer status, logs)
printer-scanner.service systemd unit for scan_trigger.py
printer-admin.service systemd unit for admin.py
geotour.md Logbook template (uses {{username}} and {{datetime}})
nice_try.md Printed when a coord.info scan yields no username

Setup

1. Dependencies

pip install mistune pillow pycups evdev flask qrcode beautifulsoup4 requests

2. CUPS printer queues

Two raw queues are expected — POS-RAW (receipts) and Lable (labels). Both point to the same MASUNG device. Check with:

lpstat -v

3. Input group (barcode scanner)

The scanner is a USB HID keyboard device. The running user needs read access:

sudo usermod -aG input $USER
# log out and back in for the group to take effect

4. systemd services

sudo cp printer-scanner.service printer-admin.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now printer-scanner printer-admin

5. Sudoers rule (admin panel service control)

The admin panel calls systemctl to restart the scanner service. Add a passwordless rule:

sudo visudo -f /etc/sudoers.d/printer-admin

Paste this line:

kalle ALL=(ALL) NOPASSWD: /bin/systemctl restart printer-scanner, /bin/systemctl stop printer-scanner, /bin/systemctl start printer-scanner

6. Remote access via Tailscale

The Pi has no static IP. Tailscale creates a private mesh network so you can reach it from anywhere without port forwarding.

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
# authenticate once via the URL it prints

After that, SSH and the admin panel are accessible from any device on your Tailscale network using the Pi's Tailscale hostname or IP.


Admin Panel

Access at http://<tailscale-ip>:5000 from any device on your Tailscale network.

Shows:

  • Scanner service status with Restart / Stop / Start buttons
  • Printer status for each CUPS queue — highlights stopped state and PAPER EMPTY
  • Recent print jobs (last 5 completed)
  • Scanner log (last 40 lines from journald, auto-refreshes every 30 s)

To view logs from the command line:

journalctl -u printer-scanner -f        # live
journalctl -u printer-admin -f

print_md.py

Usage

python3 print_md.py [file] [options]
Option Default Description
file stdin Markdown file path, or - for stdin
-p, --printer POS-RAW CUPS printer queue name
-w, --width 60 Paper width in mm: 60, 80, 82
--var KEY=VALUE Template variable (repeatable)
--preview off Hex-dump ESC/POS bytes, do not print
python3 print_md.py note.md
echo "# Hello" | python3 print_md.py -
python3 print_md.py -p Lable -w 80 note.md
python3 print_md.py --var username=Alice note.md
python3 print_md.py --preview note.md

scan_trigger.py

Listens on the USB barcode scanner. When a https://coord.info/XXXX QR code is scanned, it fetches the geocaching profile page, extracts the username from the <h1>, and fires a print job.

python3 scan_trigger.py [-f FILE] [-p PRINTER] [-w WIDTH]
Scan result Action
Not a coord.info URL Ignored
Network error Skipped
HTTP 404 or no h1 found Prints nice_try.md
Username found Prints template with {{username}} filled in

Supported Markdown

Headings

# H1 — centered, double width + height, bold
## H2 — double height, bold
### H3 — bold + underline

#### and deeper are treated the same as ###.


Text formatting

**bold text**
*italic text*       ← printed as bold (no italic in ESC/POS)
`inline code`       ← smaller monospace font

Horizontal rule

---

Printed as a full-width dashed separator line. Always put a blank line before --- — without one, the preceding paragraph is parsed as an H2 heading.


Lists

- unordered item
- another item
  - nested item

1. ordered item
2. second item

Nested lists are indented by two spaces per level.


Code blocks

```
some code here
```

Or indent with 4 spaces. Printed in smaller Font B with 2-space indent.


Block quotes

> quoted text

Printed in smaller Font B.


Images

![alt text](photo.png)
![alt text](/absolute/path/image.jpg)
  • Supported formats: PNG, JPEG, BMP, GIF, WEBP (anything Pillow can open)
  • Scaled to full paper width, aspect ratio preserved
  • Floyd-Steinberg dithering to 1-bit
  • Relative paths resolve from the Markdown file's directory

Template variables

{{key}} placeholders are substituted before parsing. Built-in variables:

Placeholder Value
{{datetime}} Current date and time: 06.05.2026 20:00

Custom variables are passed with --var:

**Handle:** {{username}}
**Printed:** {{datetime}}
python3 print_md.py logbook.md --var username=hywel93

Links

[link text](https://example.com)

Only the link text is printed; the URL is discarded.


Not supported

  • Tables
  • Footnotes
  • Inline HTML (silently skipped)
  • Remote image URLs (local files only)

About

Markdown-to-ESC/POS thermal printer stack with barcode scanner trigger and remote admin panel

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages