HTMAA JD Hagood
  • About Me
  • Final Project
  • All Projects

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.

Profile Picture

I then milled out the board.

Profile Picture

Next I populated and began programming it just as the last board. You can check out my code here. Profile Picture

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. Profile Picture

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.

Image 1 Image 1 Image 1
The control screen will be a 2.8" TFT screen controlled by 5 buttons next to it. I started by 3D printing out just the control screen part which I imagined would be the toughest part to get right.
Image 1 Image 1 Image 1
With this part of the CAD done, I proceeded to print out the base and wall pieces.
Image 1 Image 1
And the assembly was super satisfying! Everything fit together just as I had planned it to. This is a rare occurrence.

Profile Picture

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! Profile Picture

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.

Your browser does not support the video tag.
I then began by experimenting with printing poems, images, and 10,000 digits of pi.

Image 1 Image 1 Image 1

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()

Back to Home


Let’s make something cool | © JD Hagood 2024 | HTMAA 2024 | Built on Hugo

Linkedin GitHub GitLab