code for new guide

This commit is contained in:
Jeff Epler 2021-09-28 11:35:03 -05:00
parent dcd5f0d4de
commit b5e610e9b2
7 changed files with 354 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -0,0 +1,142 @@
import seekablebitmap
import gc
import os
import struct
import time
import board
import digitalio
import keypad
import ulab.numpy as np
import adafruit_ble
from adafruit_ble import BLERadio
from adafruit_ble.advertising import Advertisement
from thermalprinter import CatPrinter
from seekablebitmap import imageopen
ble = BLERadio() # pylint: disable=no-member
buttons = keypad.Keys([board.BUTTON_A, board.BUTTON_B], value_when_pressed=False)
def pnmopen(filename):
"""
Scan for netpbm format info, skip over comments, and read header data.
Return the format, header, and the opened file positioned at the start of
the bitmap data.
"""
# pylint: disable=too-many-branches
image_file = open(filename, "rb")
magic_number = image_file.read(2)
image_file.seek(2)
pnm_header = []
next_value = bytearray()
while True:
# We have all we need at length 3 for formats P2, P3, P5, P6
if len(pnm_header) == 3:
return image_file, magic_number, pnm_header
if len(pnm_header) == 2 and magic_number in [b"P1", b"P4"]:
return image_file, magic_number, pnm_header
next_byte = image_file.read(1)
if next_byte == b"":
raise RuntimeError("Unsupported image format {}".format(magic_number))
if next_byte == b"#": # comment found, seek until a newline or EOF is found
while image_file.read(1) not in [b"", b"\n"]: # EOF or NL
pass
elif not next_byte.isdigit(): # boundary found in header data
if next_value:
# pull values until space is found
pnm_header.append(int("".join(["%c" % char for char in next_value])))
next_value = bytearray() # reset the byte array
else:
next_value += next_byte # push the digit into the byte array
def wait_for_press(kbd):
"""
Wait for a keypress and return the event
"""
while True:
event = kbd.events.get()
if event and event.pressed:
return event
def show(s):
"""
Display a message on the screen
"""
board.DISPLAY.auto_refresh = False
print("\n" * 24)
print(s)
board.DISPLAY.auto_refresh = True
def show_error(s):
"""
Display a message on the screen and wait for a button press
"""
show(s + "\nPress a button to continue")
wait_for_press(buttons)
def find_cat_printer(radio):
"""
Connect to the cat printer device using BLE
"""
while True:
show("Scanning for GB02 device...")
for adv in radio.start_scan(Advertisement):
complete_name = getattr(adv, 'complete_name')
if complete_name is not None:
print(f"Saw {complete_name}")
if complete_name == 'GB02':
radio.stop_scan()
return radio.connect(adv, timeout=10)[CatPrinter]
image_files = [i for i in os.listdir('/') if i.lower().endswith(".pbm") or i.lower().endswith(".bmp")]
image_files.sort(key=lambda filename: filename.lower())
def select_image():
i = 0
while True:
show(f"Select image file\nA: next image\nB: print this image\n\n{image_files[i]}")
event = wait_for_press(buttons)
if event.key_number == 0: # button "A"
i = (i + 1) % len(image_files)
if event.key_number == 1: # button "B"
return image_files[i]
printer = find_cat_printer(ble)
while True:
try:
filename = select_image()
show(f"Loading {filename}")
image = imageopen(filename)
if image.width != 384:
raise ValueError("Invalid image. Must be 384 pixels wide")
if image.bits_per_pixel != 1:
raise ValueError("Invalid image. Must be 1 bit per pixel (black & white)")
invert_image = image.palette and image.palette[0] == 0
show(f"Printing {filename}")
for i in range(image.height):
row_data = image.get_row(i)
if invert_image:
row_data = ~np.frombuffer(row_data, dtype=np.uint8)
printer.print_bitmap_row(row_data)
# Print blank lines until the paper can be torn off
for i in range(80):
printer.print_bitmap_row(b'\0' * 48)
except Exception as e:
show_error(str(e))
image_files.remove(filename)
continue

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -0,0 +1,90 @@
import struct
import ulab
class SeekableBitmap:
def __init__(self, image_file, width, height, bits_per_pixel, *, bytes_per_row=None, data_start=None, stride=None, palette=None):
self.image_file = image_file
self.width = width
self.height = height
self.bits_per_pixel = bits_per_pixel
self.bytes_per_row = bytes_per_row if bytes_per_row else (bits_per_pixel * width + 7) // 8
self.stride = stride if stride else self.bytes_per_row
self.palette = palette
self.data_start = data_start if data_start else image_file.tell()
def get_row(self, row):
self.image_file.seek(self.data_start + row * self.stride)
return self.image_file.read(self.bytes_per_row)
def _pnmopen(filename):
"""
Scan for netpbm format info, skip over comments, and read header data.
Return the format, header, and the opened file positioned at the start of
the bitmap data.
"""
# pylint: disable=too-many-branches
image_file = open(filename, "rb")
magic_number = image_file.read(2)
image_file.seek(2)
pnm_header = []
next_value = bytearray()
while True:
# We have all we need at length 3 for formats P2, P3, P5, P6
if len(pnm_header) == 3:
return image_file, magic_number, pnm_header
if len(pnm_header) == 2 and magic_number in [b"P1", b"P4"]:
return image_file, magic_number, pnm_header
next_byte = image_file.read(1)
if next_byte == b"":
raise RuntimeError("Unsupported image format {}".format(magic_number))
if next_byte == b"#": # comment found, seek until a newline or EOF is found
while image_file.read(1) not in [b"", b"\n"]: # EOF or NL
pass
elif not next_byte.isdigit(): # boundary found in header data
if next_value:
# pull values until space is found
pnm_header.append(int("".join(["%c" % char for char in next_value])))
next_value = bytearray() # reset the byte array
else:
next_value += next_byte # push the digit into the byte array
def pnmopen(filename):
image_file, magic_number, pnm_header = _pnmopen(filename)
if magic_number == b'P4':
return SeekableBitmap(image_file, pnm_header[0], pnm_header[1], 1, palette=b'\xff\xff\xff\x00\x00\x00\x00\x00')
if magic_number == b'P5':
return SeekableBitmap(image_file, pnm_header[0], pnm_header[1], pnm_header[2].bit_length())
if magic_number == b'P6':
return SeekableBitmap(image_file, pnm_header[0], pnm_header[1], 3*pnm_header[2].bit_length())
raise ValueError(f"Unknown or unsupported magic number {magic_number}")
def bmpopen(filename):
image_file = open(filename, "rb")
header = image_file.read(34)
data_start, header_size, width, height, planes, bits_per_pixel, method = struct.unpack("<10x4l2hl", header)
bits_per_pixel = bits_per_pixel if bits_per_pixel != 0 else 1
mask_size = (12 if header_size < 56 else 16) if method == 3 else 0
palette_start = header_size + 14
image_file.seek(palette_start)
palette = image_file.read(4 << bits_per_pixel)
stride=(bits_per_pixel*width+31)//32*4
if height < 0:
height = -height
else:
data_start = data_start + stride * (height - 1)
stride = -stride
return SeekableBitmap(image_file, width, height, bits_per_pixel, data_start=data_start, stride=stride, palette=palette)
def imageopen(filename):
if filename.lower().endswith(".bmp"):
return bmpopen(filename)
return pnmopen(filename)

View file

@ -0,0 +1,122 @@
# Protocol information from Thermal_Printer Arduino library
# https://github.com/bitbank2/Thermal_Printer/
import collections
import time
from adafruit_ble.uuid import StandardUUID
from adafruit_ble.services import Service
from adafruit_ble.characteristics.stream import StreamIn
PrinterConfig = collections.namedtuple(
"PrinterConfig", ("width", "service", "characteristic")
)
# Switch the printing mode to bitmap
printimage = b"Qx\xbe\x00\x01\x00\x00\x00\xff"
# Switch the printing mode to text
printtext = b"Qx\xbe\x00\x01\x00\x01\x07\xff"
# Command to feed paper
paperfeed = b"Qx\xa1\x00\x02\x00\x1eZ\xff\xff"
# this table helps compute the checksum of transmitted data
# it is crc-8-ccitt
checksumtable = b"\x00\x07\x0e\t\x1c\x1b\x12\x158?61$#*-pw~ylkbeHOFATSZ]\xe0\xe7\xee\xe9\xfc\xfb\xf2\xf5\xd8\xdf\xd6\xd1\xc4\xc3\xca\xcd\x90\x97\x9e\x99\x8c\x8b\x82\x85\xa8\xaf\xa6\xa1\xb4\xb3\xba\xbd\xc7\xc0\xc9\xce\xdb\xdc\xd5\xd2\xff\xf8\xf1\xf6\xe3\xe4\xed\xea\xb7\xb0\xb9\xbe\xab\xac\xa5\xa2\x8f\x88\x81\x86\x93\x94\x9d\x9a' ).;<52\x1f\x18\x11\x16\x03\x04\r\nWPY^KLEBohafst}z\x89\x8e\x87\x80\x95\x92\x9b\x9c\xb1\xb6\xbf\xb8\xad\xaa\xa3\xa4\xf9\xfe\xf7\xf0\xe5\xe2\xeb\xec\xc1\xc6\xcf\xc8\xdd\xda\xd3\xd4ing`ur{|QV_XMJCD\x19\x1e\x17\x10\x05\x02\x0b\x0c!&/(=:34NI@GRU\\[vqx\x7fjmdc>907\"%,+\x06\x01\x08\x0f\x1a\x1d\x14\x13\xae\xa9\xa0\xa7\xb2\xb5\xbc\xbb\x96\x91\x98\x9f\x8a\x8d\x84\x83\xde\xd9\xd0\xd7\xc2\xc5\xcc\xcb\xe6\xe1\xe8\xef\xfa\xfd\xf4\xf3"
# mirrortable[i] is the bit reversed version of the byte i
mirrortable = b"\x00\x80@\xc0 \xa0`\xe0\x10\x90P\xd00\xb0p\xf0\x08\x88H\xc8(\xa8h\xe8\x18\x98X\xd88\xb8x\xf8\x04\x84D\xc4$\xa4d\xe4\x14\x94T\xd44\xb4t\xf4\x0c\x8cL\xcc,\xacl\xec\x1c\x9c\\\xdc<\xbc|\xfc\x02\x82B\xc2\"\xa2b\xe2\x12\x92R\xd22\xb2r\xf2\n\x8aJ\xca*\xaaj\xea\x1a\x9aZ\xda:\xbaz\xfa\x06\x86F\xc6&\xa6f\xe6\x16\x96V\xd66\xb6v\xf6\x0e\x8eN\xce.\xaen\xee\x1e\x9e^\xde>\xbe~\xfe\x01\x81A\xc1!\xa1a\xe1\x11\x91Q\xd11\xb1q\xf1\t\x89I\xc9)\xa9i\xe9\x19\x99Y\xd99\xb9y\xf9\x05\x85E\xc5%\xa5e\xe5\x15\x95U\xd55\xb5u\xf5\r\x8dM\xcd-\xadm\xed\x1d\x9d]\xdd=\xbd}\xfd\x03\x83C\xc3#\xa3c\xe3\x13\x93S\xd33\xb3s\xf3\x0b\x8bK\xcb+\xabk\xeb\x1b\x9b[\xdb;\xbb{\xfb\x07\x87G\xc7'\xa7g\xe7\x17\x97W\xd77\xb7w\xf7\x0f\x8fO\xcf/\xafo\xef\x1f\x9f_\xdf?\xbf\x7f\xff"
def checksum(data, start, count):
cs = 0
for i in range(start, start + count):
cs = checksumtable[cs ^ data[i]]
return cs
PRINTER_CAT = PrinterConfig(width=384, service=0xA3E0, characteristic=0xAE01)
MODE_TEXT = "MODE_TEXT"
MODE_BITMAP = "MODE_BITMAP"
printers = {"GB02": PRINTER_CAT, "GB01": PRINTER_CAT}
class CatPrinter(Service):
uuid = StandardUUID(0xAE30)
_tx = StreamIn(uuid=StandardUUID(0xAE01),
timeout=1.0,
buffer_size=256,
)
def _write_data(self, buf):
result = self._tx.write(buf)
@property
def bitmap_width(self):
return 384
def __init__(self, service=None):
super().__init__(service=service)
self._mode = None
@property
def mode(self):
return self._mode
@mode.setter
def mode(self, value):
if value == self.mode:
return
if value == MODE_TEXT:
self._write_data(printtext)
elif value == MODE_BITMAP:
self._write_data(printimage)
else:
raise ValueError("Invalid mode %r" % value)
self._mode = value
def feed_lines(self, lines):
buf = bytearray(paperfeed)
buf[6] = lines & 0xff
buf[7] = lines >> 8
buf[8] = checksum(buf, 6, 2)
self._write_data(buf)
def _print_common(self, text, reverse_bits=True):
offset = 0
data = memoryview(text)
while data:
sz = min(112, len(data))
sub_data = data[:sz]
data = data[sz:]
buf = bytearray(sz + 8)
buf[0] = 0x51
buf[1] = 0x78
buf[2] = 0xA2
buf[3] = 0x0
buf[4] = sz
buf[5] = 0
if reverse_bits:
buf[6 : 6 + sz] = bytes(mirrortable[c] for c in sub_data)
else:
buf[6 : 6 + sz] = sub_data
buf[6 + sz] = checksum(buf, 6, len(sub_data))
buf[6 + sz + 1] = 0xFF
self._write_data(buf)
def print_text(self, text):
self.mode = MODE_TEXT
self._print_common(text.encode("utf-8"))
def print_line(self, text):
self.print_text(text)
self._print_common(b"\n")
def print_bitmap_row(self, data, reverse_bits=True):
self.mode = MODE_BITMAP
self._print_common(data, reverse_bits)