Skip to content

Instantly share code, notes, and snippets.

@sdonnan
Created August 26, 2020 14:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sdonnan/b5ced558b3dc42c18fd11736ccfd1ee8 to your computer and use it in GitHub Desktop.
Save sdonnan/b5ced558b3dc42c18fd11736ccfd1ee8 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# Markdown to Sparkfun Thermal Printer (Model: CSN-A2) script
# Copyright 2020 Stu Donnan
# License: Public Domain
import sys
import argparse
import binascii
import bs4
import markdown
import serial
import struct
import textwrap
import time
# Constants for thermal printer communication
ESC = b"\x1B"
GS = b"\x1D"
WIDTH = 32
LINE = 30
FMT_EMPH = 1 << 3
FMT_DBLH = 1 << 4
FMT_DBLW = 1 << 5
FMT_DELL = 1 << 6
FMT_UNDL = 1 << 7
class Printer:
'''Class for thermal printer interface'''
def __init__(self, port, baud=19200):
self.port = serial.Serial(port, baudrate=baud)
self.port.flush()
self.command(ESC, '@')
def command(self, cmd, char, *args):
raw = cmd
raw += struct.pack('B', ord(char))
for arg in args:
try:
raw += arg.encode('ASCII')
except:
raw += struct.pack('B', arg)
# print(binascii.hexlify(raw))
self.port.write(raw)
def text(self, s):
self.port.write(str(s).encode('cp437'))
def println(self, s):
for line in s.split('\n'):
self.text(line + '\n')
def print_and_feed(self, s, n):
self.text(s)
self.command(ESC, 'J', n)
def set_fmt(self, fmt):
self.command(ESC, '!', fmt)
def set_reverse(self, reverse=True):
self.command(GS, 'B', 1 if reverse else 0)
def fmt_p(s):
'''Format an HTML paragraph'''
return (0, textwrap.fill(s, WIDTH))
def fmt_h1(s):
'''Format an HTML heading 1'''
return (FMT_DBLH | FMT_DBLW, textwrap.fill(s, WIDTH/2))
def fmt_hx(s):
'''Format an HTML heading > 1'''
return (FMT_EMPH | FMT_UNDL, textwrap.fill(s, WIDTH))
def fmt_ul(i):
'''Format an HTML unordered list'''
items = []
w = textwrap.TextWrapper(
drop_whitespace=True, initial_indent='', subsequent_indent=' ', width=WIDTH)
for child in i:
if child.name == 'li' and child.text:
items.append(w.fill('· ' + child.text.strip()))
return (0, '\n'.join(items))
def fmt_ol(i):
'''Format an HTML ordered list'''
items = []
count = 1
w = textwrap.TextWrapper(
drop_whitespace=True, initial_indent='', subsequent_indent=' ', width=WIDTH)
for child in i:
if child.name == 'li' and child.text:
items.append(w.fill('{}. '.format(count) + child.text.strip()))
count += 1
return (0, '\n'.join(items))
def fmt_block(s):
'''Format an HTML quote block'''
w = textwrap.TextWrapper(
drop_whitespace=True, initial_indent='> ', subsequent_indent='| ', width=WIDTH)
return (0, w.fill(s))
def fmt(item):
'''Format an HTML item'''
if not item.name:
return (0, None)
if item.name == 'h1':
return fmt_h1(item.text)
elif item.name.startswith('h'):
return fmt_hx(item.text)
elif item.name == 'p':
return fmt_p(item.text)
elif item.name == 'ul':
return fmt_ul(item)
elif item.name == 'ol':
return fmt_ol(item)
elif item.name == 'blockquote':
return fmt_block(item.text.strip())
else:
if item.text:
return fmt_p(item.text)
return (0, None)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Print markdown to thermal printer')
parser.add_argument('--verbose', '-v', action='store_true',
help='Generate more output')
parser.add_argument('--trial', '-t', action='store_true',
help='Dont actually print, use with -v for preview')
parser.add_argument('--device', '-d', default='/dev/ttyUSB0',
help='Serial device for printer')
parser.add_argument('--baudrate', '-b', type=int,
default=19200, help='Serial device baudrate')
parser.add_argument('infile', nargs='?',
type=argparse.FileType('r'), default=sys.stdin)
args = parser.parse_args()
md = args.infile.read()
html = markdown.markdown(md)
soup = bs4.BeautifulSoup(html, 'lxml')
if args.verbose:
print('-- HTML --')
print(html)
print('')
print('-- Formatted --')
if not args.trial:
p = Printer(args.device, args.baudrate)
for item in soup.body.children:
f, s = fmt(item)
if s:
if args.verbose:
print(s)
print()
if not args.trial:
p.set_fmt(f)
p.print_and_feed(s, 2 * LINE)
p.set_fmt(0)
if not args.trial:
p.print_and_feed('', 2 * LINE)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment