From 9159aafd183307d959f9eae1e31ffeee29ab4b69 Mon Sep 17 00:00:00 2001 From: Davis Goglin Date: Fri, 14 Mar 2014 09:05:47 -0700 Subject: [PATCH 01/13] IMPROVE use sendall instead of send in network-printer --- escpos/printer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escpos/printer.py b/escpos/printer.py index 1c3f92f..07e7011 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -167,7 +167,7 @@ class Network(Escpos): :param msg: arbitrary code to be printed """ - self.device.send(msg) + self.device.sendall(msg) def __del__(self): """ Close TCP connection """ From 4e1f9db5c70f3bc5e40b3ec69bd3d0e3226bde61 Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Thu, 22 May 2014 00:40:05 -0400 Subject: [PATCH 02/13] Make the structure of the ESC/POS commands less opaque (The commands are intended to be readable by people who grew up with ANSI escape codes so represent them that way in the code) --- escpos/constants.py | 194 ++++++++++++++++++++++++++------------------ 1 file changed, 117 insertions(+), 77 deletions(-) diff --git a/escpos/constants.py b/escpos/constants.py index 67c6e3b..6025ac4 100644 --- a/escpos/constants.py +++ b/escpos/constants.py @@ -10,87 +10,127 @@ moved to `capabilities` as in `escpos-php by @mike42 ) +_CASH_DRAWER = lambda m, t1='', t2='': ESC + 'p' + m + t1 + t2 +CD_KICK_2 = _CASH_DRAWER('\x00') # Sends a pulse to pin 2 [] +CD_KICK_5 = _CASH_DRAWER('\x01') # Sends a pulse to pin 5 [] + +# Paper Cutter +_CUT_PAPER = lambda m: GS + 'V' + m +PAPER_FULL_CUT = _CUT_PAPER('\x00') # Full cut paper +PAPER_PART_CUT = _CUT_PAPER('\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 +# TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll +# Printers" and tidy up this stuff too. +TXT_NORMAL = ESC + '!\x00' # Normal text +TXT_2HEIGHT = ESC + '!\x10' # Double height text +TXT_2WIDTH = ESC + '!\x20' # Double width text +TXT_4SQUARE = ESC + '!\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 +_SET_BARCODE_TXT_POS = lambda n: GS + 'H' + n +BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS('\x00') # HRI barcode chars OFF +BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS('\x01') # HRI barcode chars above +BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS('\x02') # HRI barcode chars below +BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS('\x03') # HRI both above and below + +_SET_HRI_FONT = lambda n: GS + 'f' + n +BARCODE_FONT_A = _SET_HRI_FONT('\x00') # Font type A for HRI barcode chars +BARCODE_FONT_B = _SET_HRI_FONT('\x01') # Font type B for HRI barcode chars + +BARCODE_HEIGHT = GS + 'h' + '\x64' # Barcode Height [1-255] +BARCODE_WIDTH = GS + 'w' + '\x03' # Barcode Width [2-6] + +#NOTE: This isn't actually an ESC/POS command. It's the common prefix to the +# two "print bar code" commands: +# - "GS k NUL" +# - "GS k " +# The latter command supports more barcode types +_SET_BARCODE_TYPE = lambda m: GS + 'k' + m +BARCODE_UPC_A = _SET_BARCODE_TYPE('\x00') # Barcode type UPC-A +BARCODE_UPC_E = _SET_BARCODE_TYPE('\x01') # Barcode type UPC-E +BARCODE_EAN13 = _SET_BARCODE_TYPE('\x02') # Barcode type EAN13 +BARCODE_EAN8 = _SET_BARCODE_TYPE('\x03') # Barcode type EAN8 +BARCODE_CODE39 = _SET_BARCODE_TYPE('\x04') # Barcode type CODE39 +BARCODE_ITF = _SET_BARCODE_TYPE('\x05') # Barcode type ITF +BARCODE_NW7 = _SET_BARCODE_TYPE('\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 +# NOTE: _PRINT_RASTER_IMG is the obsolete ESC/POS "print raster bit image" +# command. The constants include a fragment of the data's header. +_PRINT_RASTER_IMG = lambda data: GS + 'v0' + data +S_RASTER_N = _PRINT_RASTER_IMG('\x00') # Set raster image normal size +S_RASTER_2W = _PRINT_RASTER_IMG('\x01') # Set raster image double width +S_RASTER_2H = _PRINT_RASTER_IMG('\x02') # Set raster image double height +S_RASTER_Q = _PRINT_RASTER_IMG('\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% From f7a2caee7223cf4891af97e6f5bd15f9e2a35b23 Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Thu, 22 May 2014 00:45:57 -0400 Subject: [PATCH 03/13] Actually use the barcode height and width parameters (And reverse their order in the method signature so corrections to the range checks don't break existing code) check whether this is the solution for #26 --- escpos/constants.py | 4 ++-- escpos/escpos.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/escpos/constants.py b/escpos/constants.py index 6025ac4..5f7c641 100644 --- a/escpos/constants.py +++ b/escpos/constants.py @@ -98,8 +98,8 @@ _SET_HRI_FONT = lambda n: GS + 'f' + n BARCODE_FONT_A = _SET_HRI_FONT('\x00') # Font type A for HRI barcode chars BARCODE_FONT_B = _SET_HRI_FONT('\x01') # Font type B for HRI barcode chars -BARCODE_HEIGHT = GS + 'h' + '\x64' # Barcode Height [1-255] -BARCODE_WIDTH = GS + 'w' + '\x03' # Barcode Width [2-6] +BARCODE_HEIGHT = GS + 'h' # Barcode Height [1-255] +BARCODE_WIDTH = GS + 'w' # Barcode Width [2-6] #NOTE: This isn't actually an ESC/POS command. It's the common prefix to the # two "print bar code" commands: diff --git a/escpos/escpos.py b/escpos/escpos.py index 3a7a83f..b77b5a0 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -268,7 +268,7 @@ class Escpos(object): else: raise CharCodeError() - def barcode(self, code, bc, width, height, pos, font): + def barcode(self, code, bc, height, width, pos, font): """ Print Barcode :param code: alphanumeric data to be printed as bar code @@ -283,10 +283,10 @@ class Escpos(object): * NW7 If none is specified, the method raises :py:exc:`~escpos.exceptions.BarcodeTypeError`. - :param width: barcode width, has to be between 1 and 255 - *default*: 64 :param height: barcode height, has to be between 2 and 6 *default*: 3 + :param width: barcode width, has to be between 1 and 255 + *default*: 64 :param pos: where to place the text relative to the barcode, *default*: below * ABOVE @@ -306,13 +306,13 @@ class Escpos(object): # Align Bar Code() self._raw(TXT_ALIGN_CT) # Height - if height >= 2 or height <= 6: - self._raw(BARCODE_HEIGHT) + if 1 <= height <= 255: + self._raw(BARCODE_HEIGHT + chr(height)) else: raise BarcodeSizeError() # Width - if width >= 1 or width <= 255: - self._raw(BARCODE_WIDTH) + if 2 <= width <= 6: + self._raw(BARCODE_WIDTH + chr(width)) else: raise BarcodeSizeError() # Font From e9888739996775fa9d15ae9e7052ded7e67b938b Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Thu, 22 May 2014 00:47:08 -0400 Subject: [PATCH 04/13] Make BarcodeSizeError and BarcodeTypeError display the erroneous value (MUCH more helpful for debugging) --- escpos/escpos.py | 6 +++--- escpos/exceptions.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index b77b5a0..1751e29 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -309,12 +309,12 @@ class Escpos(object): if 1 <= height <= 255: self._raw(BARCODE_HEIGHT + chr(height)) else: - raise BarcodeSizeError() + raise BarcodeSizeError("height = %s" % height) # Width if 2 <= width <= 6: self._raw(BARCODE_WIDTH + chr(width)) else: - raise BarcodeSizeError() + raise BarcodeSizeError("width = %s" % width) # Font if font.upper() == "B": self._raw(BARCODE_FONT_B) @@ -345,7 +345,7 @@ class Escpos(object): elif bc.upper() == "NW7": self._raw(BARCODE_NW7) else: - raise BarcodeTypeError() + raise BarcodeTypeError(bc) # Print Code if code: self._raw(code) diff --git a/escpos/exceptions.py b/escpos/exceptions.py index bb72e24..c960387 100644 --- a/escpos/exceptions.py +++ b/escpos/exceptions.py @@ -45,7 +45,7 @@ class BarcodeTypeError(Error): self.resultcode = 10 def __str__(self): - return "No Barcode type is defined" + return "No Barcode type is defined (%s)" % self.msg class BarcodeSizeError(Error): @@ -61,7 +61,7 @@ class BarcodeSizeError(Error): self.resultcode = 20 def __str__(self): - return "Barcode size is out of range" + return "Barcode size is out of range (%s)" % self.msg class BarcodeCodeError(Error): From 3e89a10bed9d736eb4fd333b4d0f9921d22154ac Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Thu, 22 May 2014 00:48:24 -0400 Subject: [PATCH 05/13] Accept "CODABAR" as an alias for "NW7" in barcode() --- escpos/escpos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 1751e29..b258bf1 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -342,7 +342,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(bc) From 050419f11767e7034e282f9a57287b14150a1095 Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Thu, 22 May 2014 01:11:27 -0400 Subject: [PATCH 06/13] Apply the cash drawer fix detailed in Issue 10 on the Google Code repo. https://code.google.com/p/python-escpos/issues/detail?id=10 fixes #10 --- escpos/constants.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/escpos/constants.py b/escpos/constants.py index 5f7c641..c7e5309 100644 --- a/escpos/constants.py +++ b/escpos/constants.py @@ -37,10 +37,10 @@ HW_SELECT = ESC + '=\x01' # Printer select HW_RESET = ESC + '\x3f\x0a\x00' # Reset printer hardware # (TODO: Where is this specified?) -# Cash Drawer (ESC p ) -_CASH_DRAWER = lambda m, t1='', t2='': ESC + 'p' + m + t1 + t2 -CD_KICK_2 = _CASH_DRAWER('\x00') # Sends a pulse to pin 2 [] -CD_KICK_5 = _CASH_DRAWER('\x01') # Sends a pulse to pin 5 [] +#{ Cash Drawer (ESC p ) +_CASH_DRAWER = lambda m, t1='', t2='': ESC + 'p' + m + chr(t1) + chr(t2) +CD_KICK_2 = _CASH_DRAWER('\x00', 50, 50) # Sends a pulse to pin 2 [] +CD_KICK_5 = _CASH_DRAWER('\x01', 50, 50) # Sends a pulse to pin 5 [] # Paper Cutter _CUT_PAPER = lambda m: GS + 'V' + m From a921061b40afc79242550430e2285d9f01df8091 Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Fri, 15 Aug 2014 20:20:02 -0400 Subject: [PATCH 07/13] 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 : From 161429886300395160fc926f634344e82e79dc8b Mon Sep 17 00:00:00 2001 From: Nathan Bookham Date: Sat, 16 Aug 2014 14:28:20 +0100 Subject: [PATCH 08/13] Adjusted QR error correction Maxed out the error correction on QR code generation for vastly improved recognition. The trade-off is that the amount of bytes that can be stored are reduced. --- escpos/escpos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index b258bf1..7e11ada 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -205,7 +205,7 @@ class Escpos(object): :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() From 598c893943ff78ddb2e32f3292887b2181f4c591 Mon Sep 17 00:00:00 2001 From: Thomas van den Berg Date: Wed, 27 Feb 2013 15:10:34 +0100 Subject: [PATCH 09/13] add function to print full images including resizing and fix band printing --- escpos/__init__.py | 1 + escpos/escpos.py | 38 ++++++++++++++++++++++++++++++++++++++ escpos/exceptions.py | 15 +++++++++++++++ escpos/printer.py | 2 +- 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/escpos/__init__.py b/escpos/__init__.py index 0459ce8..8f74f5c 100644 --- a/escpos/__init__.py +++ b/escpos/__init__.py @@ -1 +1,2 @@ +import constants, escpos, exceptions, printer __all__ = ["constants", "escpos", "exceptions", "printer"] diff --git a/escpos/escpos.py b/escpos/escpos.py index 7e11ada..2ce4c57 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -18,6 +18,8 @@ import qrcode import time import textwrap import binascii +import os +import operator from .constants import * from .exceptions import * @@ -161,6 +163,42 @@ class Escpos(object): # Convert the RGB image in printable image self._convert_image(im) + def fullimage(self, img, max_height=860, width=512, histeq=True): + """ Resizes and prints an arbitrarily sized image """ + if isinstance(img, (Image, Image.Image)): + im = img.convert("RGB") + else: + im = Image.open(img).convert("RGB") + + if histeq: + # Histogram equaliztion + h = im.histogram() + lut = [] + for b in range(0, len(h), 256): + # step size + step = reduce(operator.add, h[b:b+256]) / 255 + # create equalization lookup table + n = 0 + for i in range(256): + lut.append(n / step) + n = n + h[i+b] + im = im.point(lut) + + ratio = float(width) / im.size[0] + newheight = int(ratio * im.size[1]) + + # Resize the image + im = im.resize((width, newheight), Image.ANTIALIAS) + if im.size[1] > max_height: + im = im.crop((0, 0, im.size[0], max_height)) + + # Divide into bands + bandsize = 255 + current = 0 + while current < im.size[1]: + self.image(im.crop((0, current, width, min(im.size[1], current + bandsize)))) + current += bandsize + def direct_image(self, image): """ Send image to printer diff --git a/escpos/exceptions.py b/escpos/exceptions.py index c960387..0a24ffc 100644 --- a/escpos/exceptions.py +++ b/escpos/exceptions.py @@ -11,6 +11,7 @@ Result/Exit codes: - `60` = Invalid pin to send Cash Drawer pulse :py:exc:`~escpos.exceptions.CashDrawerError` - `70` = Invalid number of tab positions :py:exc:`~escpos.exceptions.TabPosError` - `80` = Invalid char code :py:exc:`~escpos.exceptions.CharCodeError` + - `90` = USB device not found :py:exc:`~escpos.exceptions.USBNotFoundError` :author: `Manuel F Martinez `_ and others :organization: Bashlinux and `python-escpos `_ @@ -151,3 +152,17 @@ class CharCodeError(Error): def __str__(self): return "Valid char code must be set" + +class USBNotFoundError(Error): + """ Device wasn't found (probably not plugged in) + + The USB device seems to be not plugged in. + Ths returncode for this exception is `90`. + """ + def __init__(self, msg=""): + Error.__init__(self, msg) + self.msg = msg + self.resultcode = 90 + + def __str__(self): + return "USB device not found" diff --git a/escpos/printer.py b/escpos/printer.py index 07e7011..1f95665 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -43,7 +43,7 @@ class Usb(Escpos): """ Search device on USB tree and set it as escpos device """ self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct) if self.device is None: - print("Cable isn't plugged in") + raise USBNotFoundError("Device not found or cable not plugged in.") check_driver = None From a6ec6748287c86191bd134f5507da9e0429474e7 Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Thu, 22 May 2014 01:08:58 -0400 Subject: [PATCH 10/13] Allow bypassing the resizing and height-cropping steps in fullimage() --- escpos/escpos.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 2ce4c57..59884ca 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -184,19 +184,22 @@ class Escpos(object): n = n + h[i+b] im = im.point(lut) - ratio = float(width) / im.size[0] - newheight = int(ratio * im.size[1]) + if width: + ratio = float(width) / im.size[0] + newheight = int(ratio * im.size[1]) - # Resize the image - im = im.resize((width, newheight), Image.ANTIALIAS) - if im.size[1] > max_height: + # Resize the image + im = im.resize((width, newheight), Image.ANTIALIAS) + + if max_height and im.size[1] > max_height: im = im.crop((0, 0, im.size[0], max_height)) # Divide into bands bandsize = 255 current = 0 while current < im.size[1]: - self.image(im.crop((0, current, width, min(im.size[1], current + bandsize)))) + self.image(im.crop((0, current, width or im.size[0], + min(im.size[1], current + bandsize)))) current += bandsize def direct_image(self, image): From 0e907644d9609417323b2a2b5c5711baabe303bd Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Fri, 15 Aug 2014 20:17:35 -0400 Subject: [PATCH 11/13] Allow bandsize to be varied in fullimage() --- escpos/escpos.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 59884ca..007c3b1 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -163,7 +163,7 @@ class Escpos(object): # Convert the RGB image in printable image self._convert_image(im) - def fullimage(self, img, max_height=860, width=512, histeq=True): + def fullimage(self, img, max_height=860, width=512, histeq=True, bandsize=255): """ Resizes and prints an arbitrarily sized image """ if isinstance(img, (Image, Image.Image)): im = img.convert("RGB") @@ -195,7 +195,6 @@ class Escpos(object): im = im.crop((0, 0, im.size[0], max_height)) # Divide into bands - bandsize = 255 current = 0 while current < im.size[1]: self.image(im.crop((0, current, width or im.size[0], From f25521f22fd10dcee4dd42d1dd1ee7b45d339723 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Fri, 8 Jan 2016 03:34:14 +0100 Subject: [PATCH 12/13] REFACTOR fix minor PEP8 and similar mistakes --- escpos/__init__.py | 5 ++++- escpos/cli.py | 39 ++++++++++++++++++++++++++------------- escpos/escpos.py | 17 ++++++++--------- escpos/exceptions.py | 1 + escpos/printer.py | 11 +++++------ setup.py | 4 +++- 6 files changed, 47 insertions(+), 30 deletions(-) diff --git a/escpos/__init__.py b/escpos/__init__.py index 8f74f5c..1d72614 100644 --- a/escpos/__init__.py +++ b/escpos/__init__.py @@ -1,2 +1,5 @@ -import constants, escpos, exceptions, printer +import constants +import escpos +import exceptions +import printer __all__ = ["constants", "escpos", "exceptions", "printer"] diff --git a/escpos/cli.py b/escpos/cli.py index 6f1df41..1a52dcb 100755 --- a/escpos/cli.py +++ b/escpos/cli.py @@ -46,9 +46,11 @@ __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') @@ -56,10 +58,12 @@ def _print_text_file(path): 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: @@ -74,6 +78,7 @@ def print_files(args): 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: @@ -94,6 +99,7 @@ def echo(args): # pylint: disable=unused-argument 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)) @@ -101,6 +107,7 @@ def _stall_test(width, height): 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') @@ -120,24 +127,26 @@ def _test_basic(): # 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 + # 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 @@ -152,6 +161,7 @@ def _test_patterns(width=384, height=255): epson.image(_stall_test(width, height)) epson.image(_stall_test(width / 2, height)) + def test(args): """The 'test' subcommand""" if args.barcodes: @@ -161,6 +171,7 @@ def test(args): else: _test_basic() + # }}} def main(): @@ -168,25 +179,26 @@ def main(): # 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)') + '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.") + 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.)") + 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) @@ -194,6 +206,7 @@ def main(): args = parser.parse_args() args.func(args) + if __name__ == '__main__': main() diff --git a/escpos/escpos.py b/escpos/escpos.py index 007c3b1..3a8c39a 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -15,10 +15,8 @@ except ImportError: from PIL import Image import qrcode -import time import textwrap import binascii -import os import operator from .constants import * @@ -26,6 +24,7 @@ from .exceptions import * from abc import ABCMeta, abstractmethod # abstract base class support + class Escpos(object): """ ESC/POS Printer object @@ -64,9 +63,9 @@ class Escpos(object): else: image_border = 32 - (size % 32) if (image_border % 2) == 0: - return (round(image_border / 2), round(image_border / 2)) + return round(image_border / 2), round(image_border / 2) else: - return (round(image_border / 2), round((image_border / 2) + 1)) + return round(image_border / 2), round((image_border / 2) + 1) def _print_image(self, line, size): """ Print formatted image @@ -221,16 +220,16 @@ class Escpos(object): #self._raw(binascii.unhexlify(buf)) for y in range(height): for x in range(width): - value = image.getpixel((x,y)) - value = (value << 8) | value; + value = image.getpixel((x, y)) + value |= (value << 8) if value == 0: temp |= mask - mask = mask >> 1 + mask >>= 1 i += 1 if i == 8: - buf += ("%02X" % temp) + buf += ("%02X" % temp) mask = 0x80 i = 0 temp = 0 @@ -413,7 +412,7 @@ class Escpos(object): :param columns: amount of columns :return: None """ - colCount = self.columns if columns == None else columns + colCount = self.columns if columns is None else columns self.text(textwrap.fill(txt, colCount)) def set(self, align='left', font='a', text_type='normal', width=1, height=1, density=9): diff --git a/escpos/exceptions.py b/escpos/exceptions.py index 0a24ffc..61319df 100644 --- a/escpos/exceptions.py +++ b/escpos/exceptions.py @@ -153,6 +153,7 @@ class CharCodeError(Error): def __str__(self): return "Valid char code must be set" + class USBNotFoundError(Error): """ Device wasn't found (probably not plugged in) diff --git a/escpos/printer.py b/escpos/printer.py index 1f95665..f149c2e 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -13,7 +13,6 @@ import serial import socket from .escpos import * -from .constants import * from .exceptions import * @@ -32,7 +31,7 @@ class Usb(Escpos): :param out_ep: Output end point """ Escpos.__init__(self, *args, **kwargs) - self.idVendor = idVendor + self.idVendor = idVendor self.idProduct = idProduct self.interface = interface self.in_ep = in_ep @@ -100,10 +99,10 @@ class Serial(Escpos): :param dsrdtr: Hardware flow control (False to enable RTS/CTS) """ Escpos.__init__(self, *args, **kwargs) - self.devfile = devfile + self.devfile = devfile self.baudrate = baudrate self.bytesize = bytesize - self.timeout = timeout + self.timeout = timeout self.parity = parity self.stopbits = stopbits self.xonxoff = xonxoff @@ -208,9 +207,9 @@ class File(Escpos): :param msg: arbitrary code to be printed """ if type(msg) is str: - self.device.write(msg.encode()); + self.device.write(msg.encode()) else: - self.device.write(msg); + self.device.write(msg) def __del__(self): """ Close system file """ diff --git a/setup.py b/setup.py index a68987d..bbb4dc5 100755 --- a/setup.py +++ b/setup.py @@ -5,10 +5,12 @@ import sys from setuptools import setup from setuptools.command.test import test as TestCommand + def read(fname): """read file from same path as setup.py""" return open(os.path.join(os.path.dirname(__file__), fname)).read() + class Tox(TestCommand): """proxy class that enables tox to be run with setup.py test""" user_options = [('tox-args=', 'a', "Arguments to pass to tox")] @@ -26,7 +28,7 @@ class Tox(TestCommand): def run_tests(self): """run tox and pass on user-options""" - #import here, cause outside the eggs aren't loaded + # import here, cause outside the eggs aren't loaded import tox import shlex args = self.tox_args From 41c6afd3b8f3154d4408a42179a0c7301c3c4cc7 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Fri, 8 Jan 2016 03:43:33 +0100 Subject: [PATCH 13/13] REFACTOR replace % op with format were it is easy --- escpos/cli.py | 4 ++-- escpos/escpos.py | 4 ++-- escpos/exceptions.py | 4 ++-- escpos/printer.py | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/escpos/cli.py b/escpos/cli.py index 1a52dcb..4b52381 100755 --- a/escpos/cli.py +++ b/escpos/cli.py @@ -90,7 +90,7 @@ def echo(args): # pylint: disable=unused-argument epson.barcode(data, bctype, 48, 2, '', '') epson.set(align='left') else: - epson.text('%s\n' % line) + epson.text('{0}\n'.format(line)) except KeyboardInterrupt: epson.cut() @@ -143,7 +143,7 @@ def _test_barcodes(): ): # TODO: Fix the library to restore old alignment somehow epson.set(align='center') - epson.text('\n%s\n' % name) + epson.text('\n{0}\n'.format(name)) epson.barcode(data, name, 64, 2, '', '') diff --git a/escpos/escpos.py b/escpos/escpos.py index 3a8c39a..dc8912e 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -348,12 +348,12 @@ class Escpos(object): if 1 <= height <= 255: self._raw(BARCODE_HEIGHT + chr(height)) else: - raise BarcodeSizeError("height = %s" % height) + raise BarcodeSizeError("height = {height}".format(height=height)) # Width if 2 <= width <= 6: self._raw(BARCODE_WIDTH + chr(width)) else: - raise BarcodeSizeError("width = %s" % width) + raise BarcodeSizeError("width = {width}".format(width=width)) # Font if font.upper() == "B": self._raw(BARCODE_FONT_B) diff --git a/escpos/exceptions.py b/escpos/exceptions.py index 61319df..e91ed5e 100644 --- a/escpos/exceptions.py +++ b/escpos/exceptions.py @@ -46,7 +46,7 @@ class BarcodeTypeError(Error): self.resultcode = 10 def __str__(self): - return "No Barcode type is defined (%s)" % self.msg + return "No Barcode type is defined ({msg})".format(msg=self.msg) class BarcodeSizeError(Error): @@ -62,7 +62,7 @@ class BarcodeSizeError(Error): self.resultcode = 20 def __str__(self): - return "Barcode size is out of range (%s)" % self.msg + return "Barcode size is out of range ({msg})".format(msg=self.msg) class BarcodeCodeError(Error): diff --git a/escpos/printer.py b/escpos/printer.py index f149c2e..7083957 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -56,13 +56,13 @@ class Usb(Escpos): self.device.detach_kernel_driver(0) except usb.core.USBError as e: if check_driver is not None: - print("Could not detatch kernel driver: %s" % str(e)) + print("Could not detatch kernel driver: {0}".format(str(e))) try: self.device.set_configuration() self.device.reset() except usb.core.USBError as e: - print("Could not set configuration: %s" % str(e)) + print("Could not set configuration: {0}".format(str(e))) def _raw(self, msg): """ Print any command sent in raw format @@ -120,7 +120,7 @@ class Serial(Escpos): if self.device is not None: print("Serial printer enabled") else: - print("Unable to open serial printer on: %s" % self.devfile) + print("Unable to open serial printer on: {0}".format(str(self.devfile))) def _raw(self, msg): """ Print any command sent in raw format @@ -159,7 +159,7 @@ class Network(Escpos): self.device.connect((self.host, self.port)) if self.device is None: - print("Could not open socket for %s" % self.host) + print("Could not open socket for {0}".format(self.host)) def _raw(self, msg): """ Print any command sent in raw format @@ -195,7 +195,7 @@ class File(Escpos): self.device = open(self.devfile, "wb") if self.device is None: - print("Could not open the specified file %s" % self.devfile) + print("Could not open the specified file {0}".format(self.devfile)) def flush(self): """ Flush printing content """