Merge github.com:DavisGoglin/python-escpos into davisgoglin-merge

This commit is contained in:
Davis Goglin 2015-12-30 13:47:16 -08:00
commit a1180e428e
4 changed files with 293 additions and 80 deletions

200
escpos/cli.py Executable file
View File

@ -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<type>\S+)\s(?P<data>[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 :

View File

@ -1,86 +1,98 @@
""" ESC/POS Commands (Constants) """
# Control characters
# as labelled in http://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf
NUL = '\x00'
EOT = '\x04'
ENQ = '\x05'
DLE = '\x10'
DC4 = '\x14'
CAN = '\x18'
ESC = '\x1b'
FS = '\x1c'
GS = '\x1d'
# Feed control sequences
CTL_LF = '\x0a' # Print and line feed
CTL_FF = '\x0c' # Form feed
CTL_CR = '\x0d' # Carriage return
CTL_HT = '\x09' # Horizontal tab
CTL_SET_HT = '\x1b\x44' # Set horizontal tab positions
CTL_VT = '\x1b\x64\x04' # Vertical tab
CTL_SET_HT = ESC + '\x44' # Set horizontal tab positions
CTL_VT = ESC + '\x64\x04' # Vertical tab
# Printer hardware
HW_INIT = '\x1b\x40' # Clear data in buffer and reset modes
HW_SELECT = '\x1b\x3d\x01' # Printer select
HW_RESET = '\x1b\x3f\x0a\x00' # Reset printer hardware
HW_INIT = ESC + '\x40' # Clear data in buffer and reset modes
HW_SELECT = ESC + '\x3d\x01' # Printer select
HW_RESET = ESC + '\x3f\x0a\x00' # Reset printer hardware
# Cash Drawer
CD_KICK_2 = '\x1b\x70\x00' # Sends a pulse to pin 2 []
CD_KICK_5 = '\x1b\x70\x01' # Sends a pulse to pin 5 []
CD_KICK_2 = ESC + '\x70\x00' # Sends a pulse to pin 2 []
CD_KICK_5 = ESC + '\x70\x01' # Sends a pulse to pin 5 []
# Paper
PAPER_FULL_CUT = '\x1d\x56\x00' # Full cut paper
PAPER_PART_CUT = '\x1d\x56\x01' # Partial cut paper
PAPER_FULL_CUT = GS + '\x56\x00' # Full cut paper
PAPER_PART_CUT = GS + '\x56\x01' # Partial cut paper
# Text format
TXT_NORMAL = '\x1b\x21\x00' # Normal text
TXT_2HEIGHT = '\x1b\x21\x10' # Double height text
TXT_2WIDTH = '\x1b\x21\x20' # Double width text
TXT_4SQUARE = '\x1b\x21\x30' # Quad area text
TXT_UNDERL_OFF = '\x1b\x2d\x00' # Underline font OFF
TXT_UNDERL_ON = '\x1b\x2d\x01' # Underline font 1-dot ON
TXT_UNDERL2_ON = '\x1b\x2d\x02' # Underline font 2-dot ON
TXT_BOLD_OFF = '\x1b\x45\x00' # Bold font OFF
TXT_BOLD_ON = '\x1b\x45\x01' # Bold font ON
TXT_FONT_A = '\x1b\x4d\x00' # Font type A
TXT_FONT_B = '\x1b\x4d\x01' # Font type B
TXT_ALIGN_LT = '\x1b\x61\x00' # Left justification
TXT_ALIGN_CT = '\x1b\x61\x01' # Centering
TXT_ALIGN_RT = '\x1b\x61\x02' # Right justification
TXT_NORMAL = ESC + '\x21\x00' # Normal text
TXT_2HEIGHT = ESC + '\x21\x10' # Double height text
TXT_2WIDTH = ESC + '\x21\x20' # Double width text
TXT_4SQUARE = ESC + '\x21\x30' # Quad area text
TXT_UNDERL_OFF = ESC + '\x2d\x00' # Underline font OFF
TXT_UNDERL_ON = ESC + '\x2d\x01' # Underline font 1-dot ON
TXT_UNDERL2_ON = ESC + '\x2d\x02' # Underline font 2-dot ON
TXT_BOLD_OFF = ESC + '\x45\x00' # Bold font OFF
TXT_BOLD_ON = ESC + '\x45\x01' # Bold font ON
TXT_FONT_A = ESC + '\x4d\x00' # Font type A
TXT_FONT_B = ESC + '\x4d\x01' # Font type B
TXT_ALIGN_LT = ESC + '\x61\x00' # Left justification
TXT_ALIGN_CT = ESC + '\x61\x01' # Centering
TXT_ALIGN_RT = ESC + '\x61\x02' # Right justification
# Char code table
CHARCODE_PC437 = '\x1b\x74\x00' # USA: Standard Europe
CHARCODE_JIS = '\x1b\x74\x01' # Japanese Katakana
CHARCODE_PC850 = '\x1b\x74\x02' # Multilingual
CHARCODE_PC860 = '\x1b\x74\x03' # Portuguese
CHARCODE_PC863 = '\x1b\x74\x04' # Canadian-French
CHARCODE_PC865 = '\x1b\x74\x05' # Nordic
CHARCODE_WEU = '\x1b\x74\x06' # Simplified Kanji, Hirakana
CHARCODE_GREEK = '\x1b\x74\x07' # Simplified Kanji
CHARCODE_HEBREW = '\x1b\x74\x08' # Simplified Kanji
CHARCODE_PC1252 = '\x1b\x74\x11' # Western European Windows Code Set
CHARCODE_PC866 = '\x1b\x74\x12' # Cirillic #2
CHARCODE_PC852 = '\x1b\x74\x13' # Latin 2
CHARCODE_PC858 = '\x1b\x74\x14' # Euro
CHARCODE_THAI42 = '\x1b\x74\x15' # Thai character code 42
CHARCODE_THAI11 = '\x1b\x74\x16' # Thai character code 11
CHARCODE_THAI13 = '\x1b\x74\x17' # Thai character code 13
CHARCODE_THAI14 = '\x1b\x74\x18' # Thai character code 14
CHARCODE_THAI16 = '\x1b\x74\x19' # Thai character code 16
CHARCODE_THAI17 = '\x1b\x74\x1a' # Thai character code 17
CHARCODE_THAI18 = '\x1b\x74\x1b' # Thai character code 18
CHARCODE_PC437 = ESC + '\x74\x00' # USA: Standard Europe
CHARCODE_JIS = ESC + '\x74\x01' # Japanese Katakana
CHARCODE_PC850 = ESC + '\x74\x02' # Multilingual
CHARCODE_PC860 = ESC + '\x74\x03' # Portuguese
CHARCODE_PC863 = ESC + '\x74\x04' # Canadian-French
CHARCODE_PC865 = ESC + '\x74\x05' # Nordic
CHARCODE_WEU = ESC + '\x74\x06' # Simplified Kanji, Hirakana
CHARCODE_GREEK = ESC + '\x74\x07' # Simplified Kanji
CHARCODE_HEBREW = ESC + '\x74\x08' # Simplified Kanji
CHARCODE_PC1252 = ESC + '\x74\x11' # Western European Windows Code Set
CHARCODE_PC866 = ESC + '\x74\x12' # Cirillic #2
CHARCODE_PC852 = ESC + '\x74\x13' # Latin 2
CHARCODE_PC858 = ESC + '\x74\x14' # Euro
CHARCODE_THAI42 = ESC + '\x74\x15' # Thai character code 42
CHARCODE_THAI11 = ESC + '\x74\x16' # Thai character code 11
CHARCODE_THAI13 = ESC + '\x74\x17' # Thai character code 13
CHARCODE_THAI14 = ESC + '\x74\x18' # Thai character code 14
CHARCODE_THAI16 = ESC + '\x74\x19' # Thai character code 16
CHARCODE_THAI17 = ESC + '\x74\x1a' # Thai character code 17
CHARCODE_THAI18 = ESC + '\x74\x1b' # Thai character code 18
# Barcode format
BARCODE_TXT_OFF = '\x1d\x48\x00' # HRI barcode chars OFF
BARCODE_TXT_ABV = '\x1d\x48\x01' # HRI barcode chars above
BARCODE_TXT_BLW = '\x1d\x48\x02' # HRI barcode chars below
BARCODE_TXT_BTH = '\x1d\x48\x03' # HRI barcode chars both above and below
BARCODE_FONT_A = '\x1d\x66\x00' # Font type A for HRI barcode chars
BARCODE_FONT_B = '\x1d\x66\x01' # Font type B for HRI barcode chars
BARCODE_HEIGHT = '\x1d\x68\x64' # Barcode Height [1-255]
BARCODE_WIDTH = '\x1d\x77\x03' # Barcode Width [2-6]
BARCODE_UPC_A = '\x1d\x6b\x00' # Barcode type UPC-A
BARCODE_UPC_E = '\x1d\x6b\x01' # Barcode type UPC-E
BARCODE_EAN13 = '\x1d\x6b\x02' # Barcode type EAN13
BARCODE_EAN8 = '\x1d\x6b\x03' # Barcode type EAN8
BARCODE_CODE39 = '\x1d\x6b\x04' # Barcode type CODE39
BARCODE_ITF = '\x1d\x6b\x05' # Barcode type ITF
BARCODE_NW7 = '\x1d\x6b\x06' # Barcode type NW7
BARCODE_TXT_OFF = GS + '\x48\x00' # HRI barcode chars OFF
BARCODE_TXT_ABV = GS + '\x48\x01' # HRI barcode chars above
BARCODE_TXT_BLW = GS + '\x48\x02' # HRI barcode chars below
BARCODE_TXT_BTH = GS + '\x48\x03' # HRI barcode chars both above and below
BARCODE_FONT_A = GS + '\x66\x00' # Font type A for HRI barcode chars
BARCODE_FONT_B = GS + '\x66\x01' # Font type B for HRI barcode chars
BARCODE_HEIGHT = GS + '\x68\x64' # Barcode Height [1-255]
BARCODE_WIDTH = GS + '\x77\x03' # Barcode Width [2-6]
BARCODE_UPC_A = GS + '\x6b\x00' # Barcode type UPC-A
BARCODE_UPC_E = GS + '\x6b\x01' # Barcode type UPC-E
BARCODE_EAN13 = GS + '\x6b\x02' # Barcode type EAN13
BARCODE_EAN8 = GS + '\x6b\x03' # Barcode type EAN8
BARCODE_CODE39 = GS + '\x6b\x04' # Barcode type CODE39
BARCODE_ITF = GS + '\x6b\x05' # Barcode type ITF
BARCODE_NW7 = GS + '\x6b\x06' # Barcode type NW7
# Image format
S_RASTER_N = '\x1d\x76\x30\x00' # Set raster image normal size
S_RASTER_2W = '\x1d\x76\x30\x01' # Set raster image double width
S_RASTER_2H = '\x1d\x76\x30\x02' # Set raster image double height
S_RASTER_Q = '\x1d\x76\x30\x03' # Set raster image quadruple
S_RASTER_N = GS + '\x76\x30\x00' # Set raster image normal size
S_RASTER_2W = GS + '\x76\x30\x01' # Set raster image double width
S_RASTER_2H = GS + '\x76\x30\x02' # Set raster image double height
S_RASTER_Q = GS + '\x76\x30\x03' # Set raster image quadruple
# Printing Density
PD_N50 = '\x1d\x7c\x00' # Printing Density -50%
PD_N37 = '\x1d\x7c\x01' # Printing Density -37.5%
PD_N25 = '\x1d\x7c\x02' # Printing Density -25%
PD_N12 = '\x1d\x7c\x03' # Printing Density -12.5%
PD_0 = '\x1d\x7c\x04' # Printing Density 0%
PD_P50 = '\x1d\x7c\x08' # Printing Density +50%
PD_P37 = '\x1d\x7c\x07' # Printing Density +37.5%
PD_P25 = '\x1d\x7c\x06' # Printing Density +25%
PD_P12 = '\x1d\x7c\x05' # Printing Density +12.5%
PD_N50 = GS + '\x7c\x00' # Printing Density -50%
PD_N37 = GS + '\x7c\x01' # Printing Density -37.5%
PD_N25 = GS + '\x7c\x02' # Printing Density -25%
PD_N12 = GS + '\x7c\x03' # Printing Density -12.5%
PD_0 = GS + '\x7c\x04' # Printing Density 0%
PD_P50 = GS + '\x7c\x08' # Printing Density +50%
PD_P37 = GS + '\x7c\x07' # Printing Density +37.5%
PD_P25 = GS + '\x7c\x06' # Printing Density +25%
PD_P12 = GS + '\x7c\x05' # Printing Density +12.5%

View File

@ -134,6 +134,7 @@ class Escpos(object):
self._print_image(pix_line, img_size)
<<<<<<< HEAD
def image(self, path_img):
""" Open image file
@ -185,12 +186,12 @@ class Escpos(object):
self._raw(binascii.unhexlify(bytes(buf, "ascii")))
self._raw('\n')
def qr(self, text):
def qr(self, text, error_correct=qrcode.constants.ERROR_CORRECT_M):
""" Print QR Code for the provided string
:param text: text to generate a QR-Code from
"""
qr_code = qrcode.QRCode(version=4, box_size=4, border=1)
qr_code = qrcode.QRCode(version=4, box_size=4, border=1, error_correction=qrcode.constants.ERROR_CORRECT_H)
qr_code.add_data(text)
qr_code.make(fit=True)
qr_img = qr_code.make_image()
@ -278,7 +279,7 @@ class Escpos(object):
# Font
if font.upper() == "B":
self._raw(BARCODE_FONT_B)
else: # DEFAULT FONT: A
else: # DEFAULT FONT: A
self._raw(BARCODE_FONT_A)
# Position
if pos.upper() == "OFF":
@ -302,7 +303,7 @@ class Escpos(object):
self._raw(BARCODE_CODE39)
elif bc.upper() == "ITF":
self._raw(BARCODE_ITF)
elif bc.upper() == "NW7":
elif bc.upper() in ("NW7", "CODABAR"):
self._raw(BARCODE_NW7)
else:
raise BarcodeTypeError()
@ -414,7 +415,7 @@ class Escpos(object):
self._raw("\n\n\n\n\n\n")
if mode.upper() == "PART":
self._raw(PAPER_PART_CUT)
else: # DEFAULT MODE: FULL CUT
else: # DEFAULT MODE: FULL CUT
self._raw(PAPER_FULL_CUT)
def cashdraw(self, pin):
@ -442,7 +443,7 @@ class Escpos(object):
self._raw(HW_SELECT)
elif hw.upper() == "RESET":
self._raw(HW_RESET)
else: # DEFAULT: DOES NOTHING
else: # DEFAULT: DOES NOTHING
pass
def control(self, ctl, pos=4):

View File

@ -31,8 +31,8 @@ class Usb(Escpos):
self.idVendor = idVendor
self.idProduct = idProduct
self.interface = interface
self.in_ep = in_ep
self.out_ep = out_ep
self.in_ep = in_ep
self.out_ep = out_ep
self.open()
def open(self):
@ -146,7 +146,7 @@ class Network(Escpos):
def _raw(self, msg):
""" Print any command sent in raw format """
self.device.send(msg)
self.device.sendall(msg)
def __del__(self):
""" Close TCP connection """