Week 9: How to hack a receipt printer - Wed, Nov 6, 2024
This week our assignment was to add an output device to a microcontroller board you’ve designed and program it to do something. I took this as an opportunity to make a hand held 2048 Game with an ATtiny3228 with neopixels as outputs while also working on the output devices for my Game of 15. I even got around to hacking a thermal printer I found in the Stata loading docs, which should also count as an output device.
2048 Game
After the success of my hand held game in week 7, I knew I could do better. There were some individually accessible neopixels in the maker space, and my first thought was to recreate the game 2048 with a 4x4 grid. I started by programming the layout of the board. You can check out all my boards on my github here. This board was compiled from neo_pixel_game.js.
I then milled out the board.
Next I populated and began programming it just as the last board. You can check out my code here.
Finally, I was left with a small, hand held game. I am running the board off two AA batteries. Given the power consumption I measured from a power supply, the AA batteries should be able to power the device continuously for over 50 hours.
Output for Game of 15
This week I continued to work on the cad for my Game of 15 puzzle. I envisioned a desktop device with an appealing presence that invites users to play with it. I also wanted it to be one solid unit that could survie being dropped. This is what I came up with.
Receipt Printer
As I have mentioned before in week 1, I love thermal printers, and a couple of weeks back I was lucky enough to score a thermal printer in the Stata loading docs!
After buying a power supply for it, fiddling with drivers, and consulting my good friend Chat GPT, I finally got it to connect to my computer over a python script.
I then made a quick and dirty popup window to control the output of the printer for Neil to play with during show and tell.
import tkinter as tk
from escpos.printer import Usb
from mpmath import mp
import requests
from PIL import Image
# Replace with your printer's USB vendor ID and product ID
VENDOR_ID = 0x04B8 # Example: Epson vendor ID
PRODUCT_ID = 0x0202 # Example: M244A product ID
printer = Usb(VENDOR_ID, PRODUCT_ID)
def press_me():
image_path = "neil.png"
image = Image.open(image_path)
image = image.resize((384, int(image.height * (384 / image.width))), Image.LANCZOS)
# Print the centered image
for _ in range(1):
printer.image(image)
printer.cut()
def print_pi():
mp.dps = 10_000 # 100,000 digits plus the leading '3'
pi_digits = str(mp.pi)[:] # Get digits after '3.'
# Format pi digits for printing
def format_pi(digits, line_length=60, group_size=5):
formatted_lines = []
for i in range(0, len(digits), line_length):
# Group into chunks of `group_size` for readability (e.g., "12345 67890")
line = " ".join(digits[i + j:i + j + group_size] for j in range(0, line_length, group_size))
formatted_lines.append(line)
return formatted_lines
# Get formatted lines (10 digits per line, with spaces every 5 digits)
formatted_pi = format_pi(pi_digits, line_length=35, group_size=5)
# Print in chunks to avoid buffer overload
try:
chunk_size = 500 # Adjust based on printer's capacity
for i in range(0, len(formatted_pi), chunk_size):
chunk = formatted_pi[i:i + chunk_size]
printer.text("\n".join(chunk) + "\n")
printer.cut()
print("Printing complete!")
except Exception as e:
print(f"Failed to print pi digits: {e}")
def print_poem():
def get_random_poem():
poem = {
"title": None,
"author": None,
"body": None
}
url = "https://poetrydb.org/random"
response = requests.get(url)
response.encoding = 'utf-8' # Ensure UTF-8 encoding
poem_data = response.json()[0]
poem["title"] = poem_data["title"]
poem["author"] = poem_data["author"]
body = ""
for line in poem_data["lines"]:
body += line + "\n"
poem["body"] = body
return poem
poem = get_random_poem()
if len(poem['body']) > 700:
poem['body'] = poem['body'][0:700]
printer.set(font="a", double_width=True, double_height=True)
printer.text(poem['title'])
printer.text("\n")
printer.set(font="a", double_width=False, double_height=False)
printer.text(poem['author'])
printer.text("\n")
printer.text(poem['body'])
printer.cut()
# Create the popup window
root = tk.Tk()
root.title("Thermal Printer Test")
# Set window size
root.geometry("300x100")
# Add a button to print
print_button_0 = tk.Button(root, text="Press Me : )", command=press_me)
print_button_0.pack(pady=40)
print_button_1 = tk.Button(root, text="Slice of Pi", command=print_pi)
print_button_1.pack(pady=40)
print_button_2 = tk.Button(root, text="Poem", command=print_poem)
print_button_2.pack(pady=40)
# Run the Tkinter event loop
root.mainloop()