A collection of small Python scripts that render and print 384px-wide receipts on a Phomemo / DP-S01–class BLE thermal printer. Each script fetches or generates something, lays it out as a 1-bit "card," and sends it to the printer over Bluetooth (or USB).
Everything shares one tiny toolkit (print_common.py) and a common set of CLI
flags, so they all behave the same way.
print_common.py— the shared toolkit. ACardbuilder lays out content top-down (title,heading,line,para,kv,divider,image,footer) onto a 384px canvas, thenfinish()crops, frames, pads (top/bottom feed so the printer doesn't clip edges), and prints it. Also providesget_json/get_bytes(HTTP) andfit_dither(decode + scale + Floyd–Steinberg dither any image to 1-bit).s1_print.py— print dispatcher; picks the transport.direct_s1_ble_print.py/direct_s1_usb_print.py— the BLE / USB transports.print_s1_ble.py,s1_ble_console.py,ble_probe.pyare pairing and diagnostic helpers.toy_thermal_printer.py,usb_camera_verify.py,make_gradient_test.py,sitecustomize.py— utilities and test rigs.s01_printer_config.json— printer identity / connection config.
Almost every *_print.py accepts:
| Flag | Meaning |
|---|---|
--no-print |
render the PNG only; don't send to the printer |
--out PATH |
where to write the PNG |
--darkness 1-5 |
print darkness (default 3) |
--bottom-feed N |
blank feed at the bottom (default 24) |
Generators take a --seed (printed on the card) so any result can be
reproduced. Run any script with -h for its full options.
python pokemon_print.py --gen1 # random Gen-1 Pokémon card
python moon_print.py --no-print # just render moon.png
python forecast_print.py --city Tokyo # 7-day forecast with chart| Script | Prints |
|---|---|
calendar_month.py |
Month calendar grid; circles today |
daylight_print.py |
Sunrise/sunset/solar-noon with a sun-arc diagram |
moon_print.py |
Tonight's moon phase, drawn with the correct lit fraction |
onthisday_print.py |
"On this day" historical events (Wikipedia) + photo |
weather_print.py |
Local current weather |
forecast_print.py |
7-day forecast for any city, with a temp chart |
airquality_print.py |
Air quality index + pollutants with a gauge |
iss_print.py |
Current position of the ISS |
earthquake_print.py |
Recent quakes (USGS) on a world dot-map |
launches_print.py |
Upcoming rocket launches with countdowns |
epic_print.py |
Full-disc Earth photo from NASA EPIC / DSCOVR |
apod_print.py |
NASA Astronomy Picture of the Day |
world_clock_print.py |
Analog local clock + time in major cities |
| Script | Prints |
|---|---|
crypto_print.py |
Crypto price + 7-day chart (CoinGecko) |
forex_print.py |
Currency exchange rates + 30-day trend (ECB) |
| Script | Prints |
|---|---|
word_of_day_print.py |
Word of the day with pronunciation & definitions |
element_print.py |
Periodic-table element tile + properties |
quote_print.py |
Inspirational quote, typeset |
wiki_print.py |
Random Wikipedia article summary |
ascii_print.py |
ASCII table (printable chars, dec/hex) |
roman_print.py |
Roman numeral converter + reference |
multiplication_print.py |
Times-table grid |
| Script | Prints |
|---|---|
meme_print.py |
Random meme (Reddit via meme-api) |
dark_joke_print.py |
Dark-humour joke (JokeAPI) |
joke_print.py |
Random two-line joke |
insult_print.py |
Random insult (Evil Insult Generator) |
facts_print.py |
"Did you know?" mix of fact/joke APIs |
bored_print.py |
A random activity to beat boredom |
cocktail_print.py |
Cocktail recipe + photo (TheCocktailDB) |
meal_print.py |
Meal recipe + photo (TheMealDB) |
pokemon_print.py |
Pokémon trading card (PokeAPI) |
pokemon_item_print.py |
Live random Pokemon item from PokeAPI |
anime_character_print.py |
Anime character card (MyAnimeList) |
starwars_print.py |
Star Wars character dossier (SWAPI) |
album_print.py |
Album card + cover art (iTunes) |
met_print.py |
Met Museum artwork (open collection) |
country_print.py |
Country facts + flag |
cat_print.py |
Random cat photo |
xkcd_print.py |
Random xkcd comic |
fake_human_print.py |
"Dossier" for a GAN face that doesn't exist |
randomuser_print.py |
Random fake person profile |
hacker_news_print.py |
Top Hacker News stories |
color_print.py |
Colour of the day: name, codes, dithered swatch |
internet_story_print.py |
Internet story from a live story API or Gutendex |
news_feed_print.py |
Continuous live RSS headline tape |
x_following_feed_print.py |
Print only new tweets from watched X/Twitter RSS feeds |
| Script | Prints |
|---|---|
numerology_print.py |
Name numerology (Destiny / Soul / Personality) |
biorhythm_print.py |
Physical/emotional/intellectual cycle chart |
| Script | Prints |
|---|---|
tarot_print.py |
Tarot card draw |
iching_print.py |
I Ching hexagram with changing lines |
fortune_print.py |
A fortune |
| Script | Prints |
|---|---|
maze_print.py |
Maze (with optional solution) |
sudoku_print.py |
Sudoku (with optional answer key) |
wordsearch_print.py |
Themed word search (with answer key) |
nonogram_print.py |
Picross puzzle (logic-verified unique solution) |
magicsquare_print.py |
Magic square (rows/cols/diagonals sum equal) |
bingo_print.py |
75-ball bingo cards |
trivia_print.py |
Multiple-choice trivia quiz + answer key |
wheel_print.py |
Decision wheel — picks one of your options |
| Script | Prints |
|---|---|
dungeon_print.py |
Roguelike dungeon map |
dnd_print.py |
Random D&D character (stats, race, class) |
tavern_print.py |
Fantasy tavern (menu, patrons, rumour) |
magicitem_print.py |
Random magic item / loot |
superhero_print.py |
Silly superhero origin card |
dispatch_print.py |
Fictional field dispatch / incident report |
artifact_print.py |
Fictional recovered artifact museum label |
book_pick_print.py |
Real book recommendation from Open Library |
trail_report_print.py |
Generated trail report with elevation profile |
seed_packet_print.py |
Fictional seed packet with growth profile |
paper_digest_print.py |
Curated digest of real research papers |
poem_print.py |
Short poem card from PoetryDB or fallback classics |
arxiv_brief_print.py |
arXiv paper brief with fallback summaries |
abstract_snapshot_print.py |
Paper abstract with text-derived pulse chart |
etymology_snapshot_print.py |
Dictionary entry with origin and definition-length chart |
focus_plan_print.py |
Generated focus plan with schedule timeline |
micro_lesson_print.py |
Short factual lesson with quiz |
decision_memo_print.py |
Generated decision memo with scored options |
git_activity_print.py |
Live Git worktree and commit activity brief |
repo_metrics_print.py |
Live repository file metrics and extension chart |
process_snapshot_print.py |
Live Windows process snapshot with memory chart |
python_env_print.py |
Live Python environment package inventory |
event_log_print.py |
Live Windows event log brief with level chart |
github_events_print.py |
Live GitHub public-events pulse |
dog_card_print.py |
Live random dog photo card from Dog CEO |
duck_card_print.py |
Live random duck photo card from Random-d.uk |
fox_card_print.py |
Live random fox photo card from RandomFox |
card_hand_print.py |
Live playing-card hand from Deck of Cards API |
mtg_card_print.py |
Live random Magic card from Scryfall |
rickmorty_print.py |
Live Rick and Morty character card |
dnd_monster_print.py |
Live D&D monster card from 5e API |
yugioh_card_print.py |
Live random Yu-Gi-Oh card from YGOPRODeck |
random_anime_print.py |
Live random anime card from Jikan |
random_manga_print.py |
Live random manga card from Jikan |
space_crew_print.py |
Live people-in-space crew card |
| Script | Prints |
|---|---|
cellular_print.py |
Wolfram elementary cellular automaton |
life_print.py |
Conway's Game of Life, 2×2 evolution montage |
fractal_print.py |
Mandelbrot / Julia set |
attractor_print.py |
Clifford / de Jong strange attractor |
hilbert_print.py |
Hilbert space-filling curve |
dragon_print.py |
Heighway dragon curve |
koch_print.py |
Koch snowflake |
spirograph_print.py |
Hypotrochoid rosettes |
truchet_print.py |
Truchet arc tiling |
phyllotaxis_print.py |
Sunflower seed spiral (golden angle) |
voronoi_print.py |
Voronoi tessellation |
mandala_print.py |
N-fold symmetric mandala (colouring-book) |
flowfield_print.py |
Particle flow field |
tree_print.py |
Recursive fractal tree |
dna_print.py |
DNA double helix from a random sequence |
constellation_print.py |
Invented constellation + mythology |
invaders_print.py |
Swarm of random symmetric pixel sprites |
aquarium_print.py |
Little fish-tank scene |
pcb_print.py |
Generative circuit-board art |
metro_print.py |
Generative metro map of an invented city |
sort_print.py |
Sorting-algorithm visualization |
| Script | Prints |
|---|---|
chord_print.py |
Guitar chord diagram |
piano_print.py |
Piano scale diagram |
| Script | Prints |
|---|---|
barcode_print.py |
Scannable Code 39 barcode |
morse_print.py |
Message in Morse code + key |
eyechart_print.py |
Snellen eye chart |
boardingpass_print.py |
Fake boarding pass |
habit_tracker_print.py |
Monthly habit tracker grid |
binaryclock_print.py |
Current time as binary-coded decimal |
paper_print.py |
Blank templates (graph / dots / lined / staff / iso) |
photo_print.py |
Print any image file (dithered) |
map_print.py |
Map of a location |
terrain_map_print.py |
Terrain / topographic map |
| Script | Prints |
|---|---|
github_print.py |
GitHub user stats |
repo_print.py |
GitHub repository stats |
claude_session_print.py |
Claude Code session summary |
codex_status_print.py |
Codex status |
neofetch_print.py |
System info, neofetch-style |
terminal_print.py |
Real command output or simulated terminal transcript |
live_stream_print.py |
Continuous live Wikimedia edit stream tape |
python -m venv .venv
.venv\Scripts\activate # Windows
pip install pillow bleak # Pillow for rendering, bleak for BLEConfigure your printer target with S1_BLUETOOTH_TARGET or pass
--bluetooth; S1_BLUETOOTH_ADDRESS can provide an optional fallback BLE
address. The scripts also accept S1_TRANSPORT / PRINT_TRANSPORT to force a
transport.
from print_common import ROOT, Card, fit_dither, get_json, get_bytes
data = get_json("https://api.example.com/thing")
card = Card()
card.title("MY THING")
card.image(fit_dither(get_bytes(data["image"]), card.inner_w, 320))
card.para(data["text"])
card.footer("example.com")
card.finish(ROOT / "thing.png", do_print=True)Card handles wrapping, dithering, the border, and the top/bottom feed so the
printer doesn't clip the edges.
This repo also includes a small Linux HTTP sidecar for GP-58 / ESC-POS-style USB
thermal printers that appear as /dev/usb/lp0.
The API is intentionally narrow: it owns the raw printer device and exposes only health checks plus text/random/image print endpoints. This is useful when an agent or another container should print without getting direct access to the USB device.
docker compose -f docker-compose.example.yml up --buildThe example maps the host USB printer device class into the container:
volumes:
- /dev/usb:/dev/usb
device_cgroup_rules:
- "c 180:* rwm"
group_add:
- "7"On many Linux hosts /dev/usb/lp0 is owned by root:lp with group id 7, so
group_add: ["7"] lets the non-root container user write to the printer.
For standalone local testing, uncomment the loopback-only port mapping in the compose file and call:
curl http://127.0.0.1:8787/health
curl -X POST http://127.0.0.1:8787/print/text \
-H 'Content-Type: application/json' \
-d '{"title":"Hermes","text":"hello from the tiny printer","dry_run":true}'Remove "dry_run": true to actually print.
thermal_printer_client.py is a small stdlib-only client for the HTTP sidecar.
The Docker image installs it as thermal-printer, and Hermes can install or copy
the same file as /opt/data/bin/thermal-printer.
thermal-printer health
thermal-printer text --dry-run --title Hermes "hello from Hermes"
thermal-printer random
thermal-printer image card.png --dry-runBy default the client calls http://127.0.0.1:8787. Set
THERMAL_PRINTER_API_URL=http://thermal-printer:8787 when calling it from
another container on the same Docker network.
The API can refuse real prints unless another machine is reachable by ICMP ping. This is useful for agents that should not print while you are away from your computer. Dry-runs still work.
environment:
THERMAL_PRINTER_REQUIRE_PRESENCE: "1"
THERMAL_PRINTER_PRESENCE_HOST: "<TAILSCALE_DESKTOP_IP>"
THERMAL_PRINTER_PRESENCE_TIMEOUT: "2"Check the configured guard with:
thermal-printer presenceIf the guard is enabled and the target is not reachable, real print requests
return HTTP 409 Conflict and nothing is written to the printer.
GET /healthreturns printer device status and presence guard config without pinging.GET /presencepings the configured presence target and returns guard status.POST /print/textprints text with optionaltitle,footer, andfeed_lines.POST /print/randomprints a short built-in random message, or a suppliedtext.POST /print/imageprints an image using ESC/POS raster mode from either a container-localpathor animage_base64body.
Example internal call from another container on the same Docker network:
curl -X POST http://thermal-printer:8787/print/random \
-H 'Content-Type: application/json' \
-d '{}'Inside the container or on a Linux host with direct access to /dev/usb/lp0:
python gp58_print.py status
python gp58_print.py text "hello" --title Hermes --dry-run
python gp58_print.py random
python gp58_print.py image card.png