From a921061b40afc79242550430e2285d9f01df8091 Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Fri, 15 Aug 2014 20:20:02 -0400 Subject: [PATCH] Start on a command-line interface to the library (still hard-codes USB ID) (...and still needs better documentation) --- escpos/cli.py | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100755 escpos/cli.py diff --git a/escpos/cli.py b/escpos/cli.py new file mode 100755 index 0000000..6f1df41 --- /dev/null +++ b/escpos/cli.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +"""A simple command-line interface for common python-escpos functionality + +Usage: python -m escpos.cli --help + +Dependencies: +- DavisGoglin/python-escpos or better +- A file named weather.png (for the 'test' subcommand) + +Reasons for using the DavisGoglin/python-escpos fork: +- image() accepts a PIL.Image object rather than requiring me to choose + between writing a temporary file to disk or calling a "private" method. +- fullimage() allows me to print images of arbitrary length using slicing. + +How to print unsupported barcodes: + barcode -b 'BARCODE' -e 'code39' -E | convert -density 200% eps:- code.png + python test_escpos.py --images code.png + +Copyright (C) 2014 Stephan Sokolow (deitarion/SSokolow) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" + +from __future__ import absolute_import + +__author__ = "Stephan Sokolow (deitarion/SSokolow)" +__license__ = "MIT" + +import re + +from escpos import printer +epson = printer.Usb(0x0416, 0x5011) +# TODO: Un-hardcode this + +def _print_text_file(path): + """Print the given text file""" + epson.set(align='left') + with open(path, 'rU') as fobj: + for line in fobj: + epson.text(line) + +def _print_image_file(path): + """Print the given image file.""" + epson.fullimage(path, histeq=False, width=384) + +def print_files(args): + """The 'print' subcommand""" + for path in args.paths: + if args.images: + _print_image_file(path) + else: + _print_text_file(path) + epson.cut() + +# {{{ 'echo' Subcommand + +KNOWN_BARCODE_TYPES = ['UPC-A', 'UPC-E', 'EAN13', 'ITF'] +re_barcode_escape = re.compile(r'^%(?P\S+)\s(?P[0-9X]+)$') + +def echo(args): # pylint: disable=unused-argument + """TTY-like line-by-line keyboard-to-printer echo loop.""" + try: + while True: + line = raw_input() + match = re_barcode_escape.match(line) + if match and match.group('type') in KNOWN_BARCODE_TYPES: + bctype, data = match.groups() + epson.barcode(data, bctype, 48, 2, '', '') + epson.set(align='left') + else: + epson.text('%s\n' % line) + except KeyboardInterrupt: + epson.cut() + +# }}} +# {{{ 'test' Subcommand + +from PIL import Image, ImageDraw + +def _stall_test(width, height): + """Generate a pattern to detect print glitches due to vertical stalling.""" + img = Image.new('1', (width, height)) + for pos in [(x, y) for y in range(0, height) for x in range(0, width)]: + img.putpixel(pos, not sum(pos) % 10) + return img + +def _test_basic(): + """The original test code from python-escpos's Usage wiki page""" + epson.set(align='left') + # Print text + epson.text("TODO:\n") # pylint: disable=fixme + epson.text("[ ] Task 1\n") + epson.text("[ ] Task 2\n") + # Print image + # TODO: Bundle an image so this can be used + # epson.image("weather.png") + # Print QR Code (must have a white border to be scanned) + epson.set(align='center') + epson.text("Scan to recall TODO list") # pylint: disable=fixme + epson.qr("http://www.example.com/") + # Print barcode + epson.barcode('1234567890128', 'EAN13', 32, 2, '', '') + # Cut paper + epson.cut() + +def _test_barcodes(): + """Print test barcodes for all ESCPOS-specified formats.""" + for name, data in ( + # pylint: disable=bad-continuation + ('UPC-A', '123456789012\x00'), + ('UPC-E', '02345036\x00'), + ('EAN13', '1234567890128\x00'), + ('EAN8', '12345670\x00'), + ('CODE39', 'BARCODE12345678\x00'), + ('ITF', '123456\x00'), + ('CODABAR', 'A40156B'), + # TODO: CODE93 and CODE128 + ): + # TODO: Fix the library to restore old alignment somehow + epson.set(align='center') + epson.text('\n%s\n' % name) + epson.barcode(data, name, 64, 2, '', '') + +def _test_patterns(width=384, height=255): + """Print a set of test patterns for raster image output.""" + # Test our guess of the paper width + img = Image.new('1', (width, height), color=1) + draw = ImageDraw.Draw(img) + draw.polygon(((0, 0), img.size, (0, img.size[1])), fill=0) + epson.image(img) + del draw, img + + # Test the consistency of printing large data and whether stall rate is + # affected by data rate + epson.image(_stall_test(width, height)) + epson.image(_stall_test(width / 2, height)) + +def test(args): + """The 'test' subcommand""" + if args.barcodes: + _test_barcodes() + elif args.patterns: + _test_patterns() + else: + _test_basic() + +# }}} + +def main(): + """Wrapped in a function for import and entry point compatibility""" + # pylint: disable=bad-continuation + + import argparse + parser = argparse.ArgumentParser( + description="Command-line interface to python-escpos") + subparsers = parser.add_subparsers(title='subcommands') + + echo_parser = subparsers.add_parser('echo', help='Echo the keyboard to ' + 'the printer line-by-line (Exit with Ctrl+C)') + echo_parser.set_defaults(func=echo) + + print_parser = subparsers.add_parser('print', help='Print the given files') + print_parser.add_argument('--images', action='store_true', + help="Provided files are images rather than text files.") + print_parser.add_argument('paths', metavar='path', nargs='+') + print_parser.set_defaults(func=print_files) + + test_parser = subparsers.add_parser('test', help='Print test patterns') + test_modes = test_parser.add_mutually_exclusive_group() + test_modes.add_argument('--barcodes', action='store_true', + help="Test supported barcode types (Warning: Some printers must be " + "reset after attempting an unsupported barcode type.)") + test_modes.add_argument('--patterns', action='store_true', + help="Print test patterns") + test_parser.set_defaults(func=test) + + args = parser.parse_args() + args.func(args) + +if __name__ == '__main__': + main() + +# vim: set sw=4 sts=4 :