rewrite cli

This commit is contained in:
davisgoglin 2016-03-12 16:50:57 -08:00 committed by Davis Goglin
parent 81b3c1a63a
commit 99291abd10
1 changed files with 335 additions and 184 deletions

View File

@ -1,213 +1,364 @@
#!/usr/bin/env python2 #!/usr/bin/env python
# -*- 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 from __future__ import absolute_import
__author__ = "Stephan Sokolow (deitarion/SSokolow)" import argparse
__license__ = "MIT" import sys
import serial
import re
from escpos import printer from escpos import printer
from escpos import constants
epson = printer.Usb(0x0416, 0x5011) parser = argparse.ArgumentParser(
# TODO: Un-hardcode this description='CLI for python-escpos',
epilog='To see help for escpos commands, run with a destination defined.',
)
dest_subparsers = parser.add_subparsers(
title='Destination',
)
parser_dest_file = dest_subparsers.add_parser('file', help='Print to a file')
parser_dest_file.set_defaults(func=printer.File)
parser_dest_file.add_argument(
'--devfile',
help='Destination file',
required=True
)
parser_dest_network = dest_subparsers.add_parser('network', help='Print to a network device')
parser_dest_network.set_defaults(func=printer.Network)
parser_dest_network.add_argument(
'--host',
help='Destination host',
required=True
)
parser_dest_network.add_argument(
'--port',
help='Destination port',
type=int
)
parser_dest_network.add_argument(
'--timeout',
help='Timeout in seconds',
type=int
)
parser_dest_usb = dest_subparsers.add_parser('usb', help='Print to a usb device')
parser_dest_usb.set_defaults(func=printer.Usb)
parser_dest_usb.add_argument(
'--idVendor',
help='USB Vendor ID',
required=True
)
parser_dest_usb.add_argument(
'--idProduct',
help='USB Device ID',
required=True
)
parser_dest_usb.add_argument(
'--interface',
help='USB device interface',
type=int
)
parser_dest_usb.add_argument(
'--in_ep',
help='Input end point',
type=int
)
parser_dest_usb.add_argument(
'--out_ep',
help='Output end point',
type=int
)
parser_dest_serial = dest_subparsers.add_parser(
'serial',
help='Print to a serial device'
)
parser_dest_serial.set_defaults(func=printer.Serial)
parser_dest_serial.add_argument(
'--devfile',
help='Device file'
)
parser_dest_serial.add_argument(
'--baudrate',
help='Baudrate for serial transmission',
type=int
)
parser_dest_serial.add_argument(
'--bytesize',
help='Serial byte size',
type=int
)
parser_dest_serial.add_argument(
'--timeout',
help='Read/Write timeout in seconds',
type=int
)
parser_dest_serial.add_argument(
'--parity',
help='Parity checking',
choices=[serial.PARITY_NONE, serial.PARITY_EVEN, serial.PARITY_ODD, serial.PARITY_MARK, serial.PARITY_SPACE],
)
parser_dest_serial.add_argument(
'--stopbits',
help='Number of stopbits',
choices=[serial.STOPBITS_ONE, serial.STOPBITS_ONE_POINT_FIVE, serial.STOPBITS_TWO],
)
parser_dest_serial.add_argument(
'--xonxoff',
help='Software flow control',
type=bool
)
parser_dest_serial.add_argument(
'--dsrdtr',
help='Hardware flow control (False to enable RTS,CTS)',
type=bool
)
def _print_text_file(path): cmd_parser = argparse.ArgumentParser(
"""Print the given text file""" description='Parser for escpos commands',
epson.set(align='left') usage='{previous command parts} {espos command} ...',
with open(path, 'rU') as fobj: )
for line in fobj:
epson.text(line)
command_subparsers = cmd_parser.add_subparsers(
title='ESCPOS Command',
)
def _print_image_file(path): # From here on func needs to be a string, since we don't have a printer to work on yet
"""Print the given image file.""" parser_command_qr = command_subparsers.add_parser('qr', help='Print a QR code')
epson.fullimage(path, histeq=False, width=384) parser_command_qr.set_defaults(func='qr')
parser_command_qr.add_argument(
'--text',
help='Text to print as a qr code',
required=True
)
parser_command_barcode = command_subparsers.add_parser('barcode', help='Print a barcode')
parser_command_barcode.set_defaults(func='barcode')
parser_command_barcode.add_argument(
'--code',
help='Barcode data to print',
required=True,
)
parser_command_barcode.add_argument(
'--bc',
help='Barcode format',
required=True,
)
parser_command_barcode.add_argument(
'--height',
help='Barcode height in px',
type=int
)
parser_command_barcode.add_argument(
'--width',
help='Barcode width',
type=int
)
parser_command_barcode.add_argument(
'--pos',
help='Label position',
choices=['BELOW', 'ABOVE', 'BOTH', 'OFF']
)
parser_command_barcode.add_argument(
'--font',
help='Label font',
choices=['A', 'B']
)
parser_command_barcode.add_argument(
'--align_ct',
help='Align barcode center',
type=bool,
)
parser_command_barcode.add_argument(
'--function_type',
help='ESCPOS function type',
choices=['A', 'B']
)
def print_files(args): parser_command_text = command_subparsers.add_parser('text', help='Print plain text')
"""The 'print' subcommand""" parser_command_text.set_defaults(func='text')
for path in args.paths: parser_command_text.add_argument(
if args.images: '--txt',
_print_image_file(path) help='Text to print',
else: required=True
_print_text_file(path) )
epson.cut()
# {{{ 'echo' Subcommand parser_command_block_text = command_subparsers.add_parser('block_text', help='Print wrapped text')
parser_command_block_text.set_defaults(func='block_text')
parser_command_block_text.add_argument(
'--txt',
help='block_text to print',
required=True
)
parser_command_block_text.add_argument(
'--columns',
help='Number of columns',
type=int,
)
KNOWN_BARCODE_TYPES = ['UPC-A', 'UPC-E', 'EAN13', 'ITF'] parser_command_cut = command_subparsers.add_parser('cut', help='Cut the paper')
re_barcode_escape = re.compile(r'^%(?P<type>\S+)\s(?P<data>[0-9X]+)$') parser_command_cut.set_defaults(func='cut')
parser_command_cut.add_argument(
'--mode',
help='Type of cut',
choices=['FULL', 'PART']
)
parser_command_cashdraw = command_subparsers.add_parser('cashdraw', help='Kick the cash drawer')
parser_command_cashdraw.set_defaults(func='cashdraw')
parser_command_cashdraw.add_argument(
'--pin',
help='Which PIN to kick',
choices=[2, 5]
)
def echo(args): # pylint: disable=unused-argument parser_command_image = command_subparsers.add_parser('image', help='Print an image')
"""TTY-like line-by-line keyboard-to-printer echo loop.""" parser_command_image.set_defaults(func='image')
try: parser_command_image.add_argument(
while True: '--path_img',
line = raw_input() help='Path to image',
match = re_barcode_escape.match(line) required=True
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('{0}\n'.format(line))
except KeyboardInterrupt:
epson.cut()
# }}} parser_command_fullimage = command_subparsers.add_parser('fullimage', help='Print an fullimage')
# {{{ 'test' Subcommand parser_command_fullimage.set_defaults(func='fullimage')
parser_command_fullimage.add_argument(
'--img',
help='Path to img',
required=True
)
parser_command_fullimage.add_argument(
'--max_height',
help='Max height of image in px',
type=int
)
parser_command_fullimage.add_argument(
'--width',
help='Max width of image in px',
type=int
)
parser_command_fullimage.add_argument(
'--histeq',
help='Equalize the histrogram',
type=bool
)
parser_command_fullimage.add_argument(
'--bandsize',
help='Size of bands to divide into when printing',
type=int
)
from PIL import Image, ImageDraw # Not supported
# parser_command_direct_image = command_subparsers.add_parser('direct_direct_image', help='Print an direct_image')
# parser_command_direct_image.set_defaults(func='direct_image')
parser_command_charcode = command_subparsers.add_parser('charcode', help='Set character code table')
parser_command_charcode.set_defaults(func='charcode')
parser_command_charcode.add_argument(
'--code',
help='Character code',
required=True
)
def _stall_test(width, height): parser_command_set = command_subparsers.add_parser('set', help='Set text properties')
"""Generate a pattern to detect print glitches due to vertical stalling.""" parser_command_set.set_defaults(func='set')
img = Image.new('1', (width, height)) parser_command_set.add_argument(
for pos in [(x, y) for y in range(0, height) for x in range(0, width)]: '--align',
img.putpixel(pos, not sum(pos) % 10) help='Horizontal alignment',
return img choices=['left', 'center', 'right']
)
parser_command_set.add_argument(
'--font',
help='Font choice',
choices=['left', 'center', 'right']
)
parser_command_set.add_argument(
'--text_type',
help='Text properties',
choices=['B', 'U', 'U2', 'BU', 'BU2', 'NORMAL']
)
parser_command_set.add_argument(
'--width',
help='Width multiplier',
type=int
)
parser_command_set.add_argument(
'--height',
help='Height multiplier',
type=int
)
parser_command_set.add_argument(
'--density',
help='Print density',
type=int
)
parser_command_set.add_argument(
'--invert',
help='White on black printing',
type=bool
)
parser_command_set.add_argument(
'--smooth',
help='Text smoothing. Effective on >= 4x4 text',
type=bool
)
parser_command_set.add_argument(
'--flip',
help='Text smoothing. Effective on >= 4x4 text',
type=bool
)
parser_command_hw = command_subparsers.add_parser('hw', help='Hardware operations')
parser_command_hw.set_defaults(func='hw')
parser_command_hw.add_argument(
'--hw',
help='Operation',
choices=['INIT', 'SELECT', 'RESET'],
required=True
)
def _test_basic(): parser_command_control = command_subparsers.add_parser('control', help='Control sequences')
"""The original test code from python-escpos's Usage wiki page""" parser_command_control.set_defaults(func='control')
epson.set(align='left') parser_command_control.add_argument(
# Print text '--ctl',
epson.text("TODO:\n") # pylint: disable=fixme help='Control sequence',
epson.text("[ ] Task 1\n") choices=['LF', 'FF', 'CR', 'HT', 'VT'],
epson.text("[ ] Task 2\n") required=True
# Print image )
# TODO: Bundle an image so this can be used parser_command_control.add_argument(
# epson.image("weather.png") '--pos',
# Print QR Code (must have a white border to be scanned) help='Horizontal tab position (1-4)',
epson.set(align='center') type=int
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()
parser_command_panel_buttons = command_subparsers.add_parser('panel_buttons', help='Disables panel buttons')
parser_command_panel_buttons.set_defaults(func='panel_buttons')
parser_command_panel_buttons.add_argument(
'--enable',
help='Feed button enabled',
type=bool,
required=True
)
def _test_barcodes(): # Get arguments along with function to pass them to
"""Print test barcodes for all ESCPOS-specified formats.""" args, rest = parser.parse_known_args()
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{0}\n'.format(name))
epson.barcode(data, name, 64, 2, '', '')
# filter out function name and non passed arguments
func_args = dict((k, v) for k, v in vars(args).iteritems() if v and k != 'func')
def _test_patterns(width=384, height=255): # define a printer
"""Print a set of test patterns for raster image output.""" p = args.func(**func_args)
# 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 if not rest:
# affected by data rate cmd_parser.print_help()
epson.image(_stall_test(width, height)) sys.exit(1)
epson.image(_stall_test(width / 2, height))
cmd_args = cmd_parser.parse_args(rest)
def test(args): # filter out function name and non passed arguments
"""The 'test' subcommand""" func_args = dict((k, v) for k, v in vars(cmd_args).iteritems() if v and k != 'func')
if args.barcodes:
_test_barcodes()
elif args.patterns:
_test_patterns()
else:
_test_basic()
# print command with args
# }}} getattr(p, cmd_args.func)(**func_args)
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 :