From afc68340829e78bda2a3335d4830babf29c75f3c Mon Sep 17 00:00:00 2001 From: Christoph Heuel Date: Thu, 7 May 2015 20:54:32 +0200 Subject: [PATCH 01/51] After running 2to3 tool --- escpos/escpos.py | 4 ++-- escpos/printer.py | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index b6befdd..3b3a49f 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -14,8 +14,8 @@ except ImportError: import qrcode import time -from constants import * -from exceptions import * +from .constants import * +from .exceptions import * class Escpos: """ ESC/POS Printer object """ diff --git a/escpos/printer.py b/escpos/printer.py index a34edb4..31b00c0 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -11,9 +11,9 @@ import usb.util import serial import socket -from escpos import * -from constants import * -from exceptions import * +from .escpos import * +from .constants import * +from .exceptions import * class Usb(Escpos): """ Define USB printer """ @@ -38,19 +38,19 @@ class Usb(Escpos): """ Search device on USB tree and set is 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" + print("Cable isn't plugged in") if self.device.is_kernel_driver_active(0): try: self.device.detach_kernel_driver(0) except usb.core.USBError as e: - print "Could not detatch kernel driver: %s" % str(e) + print("Could not detatch kernel driver: %s" % 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: %s" % str(e)) def _raw(self, msg): @@ -88,9 +88,9 @@ class Serial(Escpos): self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate, bytesize=self.bytesize, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=self.timeout, dsrdtr=True) if self.device is not None: - print "Serial printer enabled" + print("Serial printer enabled") else: - print "Unable to open serial printer on: %s" % self.devfile + print("Unable to open serial printer on: %s" % self.devfile) def _raw(self, msg): @@ -124,7 +124,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 %s" % self.host) def _raw(self, msg): @@ -154,7 +154,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 %s" % self.devfile) def _raw(self, msg): From b99c076baec6f73631a70910e399401897ee6c0b Mon Sep 17 00:00:00 2001 From: Christoph Heuel Date: Tue, 19 May 2015 02:52:55 +0200 Subject: [PATCH 02/51] Fix for string operation * With Python version 3 data and text are treated different. Convert the * text accordingly. --- escpos/printer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/escpos/printer.py b/escpos/printer.py index 31b00c0..de88814 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -159,7 +159,10 @@ class File(Escpos): def _raw(self, msg): """ Print any command sent in raw format """ - self.device.write(msg); + if type(msg) is str: + self.device.write(msg.encode()); + else: + self.device.write(msg); def __del__(self): From 7da2e32e3c687f54d8b33154e08b4f453c0d2488 Mon Sep 17 00:00:00 2001 From: Christoph Heuel Date: Thu, 4 Jun 2015 13:20:17 +0200 Subject: [PATCH 03/51] Integer is needed, not float * The size of the image can only be integer, not float. Using round to convert it back --- escpos/escpos.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 3b3a49f..876755e 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -29,9 +29,9 @@ class Escpos: else: image_border = 32 - (size % 32) if (image_border % 2) == 0: - return (image_border / 2, image_border / 2) + return (round(image_border / 2), round(image_border / 2)) else: - return (image_border / 2, (image_border / 2) + 1) + return (round(image_border / 2), round((image_border / 2) + 1)) def _print_image(self, line, size): From dd228c9fdae4f4eee972b7d6d91a36b1b4d42258 Mon Sep 17 00:00:00 2001 From: Christoph Heuel Date: Thu, 4 Jun 2015 13:57:59 +0200 Subject: [PATCH 04/51] Add text wrapping * The base class supports to give columns, how much it should wrapped. * This is meant for longer text. * The special instances need to initialize the super class with the * columns --- escpos/escpos.py | 17 ++++++++++++----- escpos/printer.py | 12 ++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 876755e..810167e 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -13,6 +13,7 @@ except ImportError: import qrcode import time +import textwrap from .constants import * from .exceptions import * @@ -21,6 +22,8 @@ class Escpos: """ ESC/POS Printer object """ device = None + def __init__(self, columns=32): + self.columns = columns def _check_image_size(self, size): """ Check and fix the size of the image to 32 bits """ @@ -39,7 +42,7 @@ class Escpos: i = 0 cont = 0 buffer = "" - + self._raw(S_RASTER_N) buffer = "%02X%02X%02X%02X" % (((size[0]/size[1])/8), 0, size[1], 0) self._raw(buffer.decode('hex')) @@ -97,7 +100,7 @@ class Escpos: break elif im_color > (255 * 3 / pattern_len * pattern_len) and im_color <= (255 * 3): pix_line += im_pattern[-1] - break + break pix_line += im_right img_size[0] += im_border[1] @@ -196,9 +199,9 @@ class Escpos: self._raw(BARCODE_TXT_BTH) elif pos.upper() == "ABOVE": self._raw(BARCODE_TXT_ABV) - else: # DEFAULT POSITION: BELOW + else: # DEFAULT POSITION: BELOW self._raw(BARCODE_TXT_BLW) - # Type + # Type if bc.upper() == "UPC-A": self._raw(BARCODE_UPC_A) elif bc.upper() == "UPC-E": @@ -221,7 +224,7 @@ class Escpos: else: raise exception.BarcodeCodeError() - + def text(self, txt): """ Print alpha-numeric text """ if txt: @@ -229,6 +232,10 @@ class Escpos: else: raise TextError() + def block_text(self, txt, columns=None): + '''Text is printed wrapped to specified columns''' + colCount = self.columns if columns == None else columns + self.text(textwrap.fill(txt, colCount)) def set(self, align='left', font='a', type='normal', width=1, height=1, density=9): """ Set text properties """ diff --git a/escpos/printer.py b/escpos/printer.py index de88814..1de54a3 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -18,7 +18,7 @@ from .exceptions import * class Usb(Escpos): """ Define USB printer """ - def __init__(self, idVendor, idProduct, interface=0, in_ep=0x82, out_ep=0x01): + def __init__(self, idVendor, idProduct, interface=0, in_ep=0x82, out_ep=0x01, *args, **kwargs): """ @param idVendor : Vendor ID @param idProduct : Product ID @@ -26,6 +26,7 @@ class Usb(Escpos): @param in_ep : Input end point @param out_ep : Output end point """ + Escpos.__init__(self, *args, **kwargs) self.idVendor = idVendor self.idProduct = idProduct self.interface = interface @@ -69,13 +70,14 @@ class Usb(Escpos): class Serial(Escpos): """ Define Serial printer """ - def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1): + def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1, *args, **kwargs): """ @param devfile : Device file under dev filesystem @param baudrate : Baud rate for serial transmission @param bytesize : Serial buffer size @param timeout : Read/Write timeout """ + Escpos.__init__(self, *args, **kwargs) self.devfile = devfile self.baudrate = baudrate self.bytesize = bytesize @@ -108,11 +110,12 @@ class Serial(Escpos): class Network(Escpos): """ Define Network printer """ - def __init__(self,host,port=9100): + def __init__(self,host,port=9100, *args, **kwargs): """ @param host : Printer's hostname or IP address @param port : Port to write to """ + Escpos.__init__(self, *args, **kwargs) self.host = host self.port = port self.open() @@ -141,10 +144,11 @@ class Network(Escpos): class File(Escpos): """ Define Generic file printer """ - def __init__(self, devfile="/dev/usb/lp0"): + def __init__(self, devfile="/dev/usb/lp0", *args, **kwargs): """ @param devfile : Device file under dev filesystem """ + Escpos.__init__(self, *args, **kwargs) self.devfile = devfile self.open() From 0ef2951c7e187719b99a4ac0912955a6d203d448 Mon Sep 17 00:00:00 2001 From: Christoph Heuel Date: Wed, 10 Jun 2015 03:21:52 +0200 Subject: [PATCH 05/51] Introduce new direct_image * The direct_image method prints directly from the PIL image object * The image should be converted to 1 bit before * The method was derived from png2escpos (https://github.com/twg/png2escpos) --- escpos/escpos.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/escpos/escpos.py b/escpos/escpos.py index 810167e..9d37709 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -14,6 +14,7 @@ except ImportError: import qrcode import time import textwrap +import binascii from .constants import * from .exceptions import * @@ -114,6 +115,38 @@ class Escpos: # Convert the RGB image in printable image self._convert_image(im) + def direct_image(self, image): + """ Send image to printer""" + mask = 0x80 + i = 0 + temp = 0 + + (width, height) = image.size + self._raw(S_RASTER_N) + headerX = int(width / 8) + headerY = height + buf = "%02X" % (headerX & 0xff) + buf += "%02X" % ((headerX >> 8) & 0xff) + buf += "%02X" % (headerY & 0xff) + buf += "%02X" % ((headerY >> 8) & 0xff) + #self._raw(binascii.unhexlify(buf)) + for y in range(height): + for x in range(width): + value = image.getpixel((x,y)) + value = (value << 8) | value; + if value == 0: + temp |= mask + + mask = mask >> 1 + + i += 1 + if i == 8: + buf += ("%02X" % temp) + mask = 0x80 + i = 0 + temp = 0 + self._raw(binascii.unhexlify(buf)) + def qr(self,text): """ Print QR Code for the provided string """ From 6734864a5b25f34678c14ad80122d99ab9ccadc8 Mon Sep 17 00:00:00 2001 From: Christoph Heuel Date: Mon, 15 Jun 2015 23:08:49 +0200 Subject: [PATCH 06/51] Use unhexlify * for decoding, use unhexlify --- escpos/escpos.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 9d37709..30fc126 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -46,7 +46,7 @@ class Escpos: self._raw(S_RASTER_N) buffer = "%02X%02X%02X%02X" % (((size[0]/size[1])/8), 0, size[1], 0) - self._raw(buffer.decode('hex')) + self._raw(binascii.unhexlify(buffer)) buffer = "" while i < len(line): @@ -55,7 +55,7 @@ class Escpos: i += 8 cont += 1 if cont % 4 == 0: - self._raw(buffer.decode("hex")) + self._raw(binascii.unhexlify(buffer)) buffer = "" cont = 0 From 25b650c935ccd0ac723b06d5f21b353bae5ceca7 Mon Sep 17 00:00:00 2001 From: Christoph Heuel Date: Fri, 26 Jun 2015 02:12:30 +0200 Subject: [PATCH 07/51] Hexlify text * The original code did not convert the received text --- escpos/escpos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 30fc126..bea1543 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -145,7 +145,7 @@ class Escpos: mask = 0x80 i = 0 temp = 0 - self._raw(binascii.unhexlify(buf)) + self._raw(binascii.unhexlify(bytes(buf, "ascii"))) def qr(self,text): From 546f47edcc4f6ccf4e4ffc049e5db74ee8462dfd Mon Sep 17 00:00:00 2001 From: Christoph Heuel Date: Thu, 27 Aug 2015 23:20:53 +0200 Subject: [PATCH 08/51] Fix text wrapping error after image * After an image the text wrapping was disturbed. --- escpos/escpos.py | 1 + 1 file changed, 1 insertion(+) diff --git a/escpos/escpos.py b/escpos/escpos.py index bea1543..a2b7fe0 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -146,6 +146,7 @@ class Escpos: i = 0 temp = 0 self._raw(binascii.unhexlify(bytes(buf, "ascii"))) + self._raw('\n') def qr(self,text): From 1a1ed5e7fcc7610601bc77a6faef5c9ac1de21be Mon Sep 17 00:00:00 2001 From: Christoph Heuel Date: Thu, 27 Aug 2015 23:45:15 +0200 Subject: [PATCH 09/51] Fix mixed tabs/space error --- escpos/escpos.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 97326c3..d331b02 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -112,13 +112,13 @@ class Escpos: """ Open image file """ im_open = Image.open(path_img) - # Remove the alpha channel on transparent images - if im_open.mode == 'RGBA': - im_open.load() - im = Image.new("RGB", im_open.size, (255, 255, 255)) - im.paste(im_open, mask=im_open.split()[3]) - else: - im = im_open.convert("RGB") + # Remove the alpha channel on transparent images + if im_open.mode == 'RGBA': + im_open.load() + im = Image.new("RGB", im_open.size, (255, 255, 255)) + im.paste(im_open, mask=im_open.split()[3]) + else: + im = im_open.convert("RGB") # Convert the RGB image in printable image self._convert_image(im) From 371d5e78bda7bb5a8c651d9997dbcc22ac4d0b2e Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Fri, 27 Nov 2015 20:49:28 +0100 Subject: [PATCH 10/51] moved .hgignore to .gitignore --- .hgignore => .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename .hgignore => .gitignore (73%) diff --git a/.hgignore b/.gitignore similarity index 73% rename from .hgignore rename to .gitignore index aa3937a..9d3808a 100644 --- a/.hgignore +++ b/.gitignore @@ -1,10 +1,9 @@ # python temporary files -syntax: glob *.pyc # editor autosaves $~ +.idea/ # temporary data -syntax: regexp temp From 3ea52e52fd8b357a00bce8dbfcbe4061331acb08 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Fri, 27 Nov 2015 21:22:27 +0100 Subject: [PATCH 11/51] REFACTOR chained boolean expression in escpos --- escpos/escpos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 045a4fd..37eed91 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -95,7 +95,7 @@ class Escpos: else: pix_line += im_pattern[x] break - elif im_color > (255 * 3 / pattern_len * pattern_len) and im_color <= (255 * 3): + elif (255 * 3 / pattern_len * pattern_len) < im_color <= (255 * 3): pix_line += im_pattern[-1] break pix_line += im_right From 096445631f42d6806f906fb6f81525c429aa6ea0 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Fri, 27 Nov 2015 21:24:47 +0100 Subject: [PATCH 12/51] REFACTOR use new-style class for Escpos --- escpos/escpos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 37eed91..7a76b98 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -17,7 +17,7 @@ import time from constants import * from exceptions import * -class Escpos: +class Escpos(object): """ ESC/POS Printer object """ device = None From 07d8e073ae1e64f4885eb41abc430438fa006dbe Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Fri, 27 Nov 2015 21:20:12 +0100 Subject: [PATCH 13/51] REFACTOR style and PEP8, fixes #66 --- escpos/__init__.py | 2 +- escpos/constants.py | 150 +++++++++++++++++++++---------------------- escpos/escpos.py | 88 +++++++++++-------------- escpos/exceptions.py | 5 +- escpos/printer.py | 35 +++------- 5 files changed, 128 insertions(+), 152 deletions(-) diff --git a/escpos/__init__.py b/escpos/__init__.py index 22a5af6..0459ce8 100644 --- a/escpos/__init__.py +++ b/escpos/__init__.py @@ -1 +1 @@ -__all__ = ["constants","escpos","exceptions","printer"] +__all__ = ["constants", "escpos", "exceptions", "printer"] diff --git a/escpos/constants.py b/escpos/constants.py index 7511365..0123839 100644 --- a/escpos/constants.py +++ b/escpos/constants.py @@ -1,86 +1,86 @@ """ ESC/POS Commands (Constants) """ # 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_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 # 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 = '\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 # 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 = '\x1b\x70\x00' # Sends a pulse to pin 2 [] +CD_KICK_5 = '\x1b\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 = '\x1d\x56\x00' # Full cut paper +PAPER_PART_CUT = '\x1d\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 = '\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 # 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 = '\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 # 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 = '\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 # 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 = '\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 # 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 = '\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% diff --git a/escpos/escpos.py b/escpos/escpos.py index 7a76b98..4bec5f2 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -12,41 +12,40 @@ except ImportError: from PIL import Image import qrcode -import time from constants import * from exceptions import * + class Escpos(object): """ ESC/POS Printer object """ - device = None + device = None - - def _check_image_size(self, size): + @staticmethod + def _check_image_size(size): """ Check and fix the size of the image to 32 bits """ if size % 32 == 0: - return (0, 0) + return 0, 0 else: image_border = 32 - (size % 32) if (image_border % 2) == 0: - return (image_border / 2, image_border / 2) + return image_border / 2, image_border / 2 else: - return (image_border / 2, (image_border / 2) + 1) - + return image_border / 2, (image_border / 2) + 1 def _print_image(self, line, size): """ Print formatted image """ i = 0 cont = 0 buffer = "" - + self._raw(S_RASTER_N) - buffer = "%02X%02X%02X%02X" % (((size[0]/size[1])/8), 0, size[1]&0xff, size[1]>>8) + buffer = "%02X%02X%02X%02X" % (((size[0]/size[1])/8), 0, size[1] & 0xff, size[1] >> 8) self._raw(buffer.decode('hex')) buffer = "" while i < len(line): - hex_string = int(line[i:i+8],2) + hex_string = int(line[i:i+8], 2) buffer += "%02X" % hex_string i += 8 cont += 1 @@ -55,19 +54,17 @@ class Escpos(object): buffer = "" cont = 0 - def _convert_image(self, im): """ Parse image and prepare it to a printable format """ - pixels = [] + pixels = [] pix_line = "" - im_left = "" + im_left = "" im_right = "" - switch = 0 - img_size = [ 0, 0 ] - + switch = 0 + img_size = [0, 0] if im.size[0] > 512: - print ("WARNING: Image is wider than 512 and could be truncated at print time ") + print ("WARNING: Image is wider than 512 and could be truncated at print time ") if im.size[1] > 0xffff: raise ImageSizeError() @@ -87,7 +84,7 @@ class Escpos(object): im_color = (RGB[0] + RGB[1] + RGB[2]) im_pattern = "1X0" pattern_len = len(im_pattern) - switch = (switch - 1 ) * (-1) + switch = (switch - 1) * (-1) for x in range(pattern_len): if im_color <= (255 * 3 / pattern_len * (x+1)): if im_pattern[x] == "X": @@ -97,30 +94,28 @@ class Escpos(object): break elif (255 * 3 / pattern_len * pattern_len) < im_color <= (255 * 3): pix_line += im_pattern[-1] - break + break pix_line += im_right img_size[0] += im_border[1] self._print_image(pix_line, img_size) - - def image(self,path_img): + def image(self, path_img): """ Open image file """ im_open = Image.open(path_img) - # Remove the alpha channel on transparent images - if im_open.mode == 'RGBA': - im_open.load() - im = Image.new("RGB", im_open.size, (255, 255, 255)) - im.paste(im_open, mask=im_open.split()[3]) - else: - im = im_open.convert("RGB") + # Remove the alpha channel on transparent images + if im_open.mode == 'RGBA': + im_open.load() + im = Image.new("RGB", im_open.size, (255, 255, 255)) + im.paste(im_open, mask=im_open.split()[3]) + else: + im = im_open.convert("RGB") # Convert the RGB image in printable image self._convert_image(im) - - def qr(self,text): + def qr(self, text): """ Print QR Code for the provided string """ qr_code = qrcode.QRCode(version=4, box_size=4, border=1) qr_code.add_data(text) @@ -131,8 +126,7 @@ class Escpos(object): # Convert the RGB image in printable image self._convert_image(im) - - def charcode(self,code): + def charcode(self, code): """ Set Character Code Table """ if code.upper() == "USA": self._raw(CHARCODE_PC437) @@ -184,19 +178,19 @@ class Escpos(object): # Align Bar Code() self._raw(TXT_ALIGN_CT) # Height - if height >=2 or height <=6: + if height >= 2 or height <= 6: self._raw(BARCODE_HEIGHT) else: raise BarcodeSizeError() # Width - if width >= 1 or width <=255: + if width >= 1 or width <= 255: self._raw(BARCODE_WIDTH) else: raise BarcodeSizeError() # 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": @@ -205,9 +199,9 @@ class Escpos(object): self._raw(BARCODE_TXT_BTH) elif pos.upper() == "ABOVE": self._raw(BARCODE_TXT_ABV) - else: # DEFAULT POSITION: BELOW + else: # DEFAULT POSITION: BELOW self._raw(BARCODE_TXT_BLW) - # Type + # Type if bc.upper() == "UPC-A": self._raw(BARCODE_UPC_A) elif bc.upper() == "UPC-E": @@ -228,9 +222,8 @@ class Escpos(object): if code: self._raw(code) else: - raise exception.BarcodeCodeError() + raise BarcodeCodeError() - def text(self, txt): """ Print alpha-numeric text """ if txt: @@ -238,7 +231,6 @@ class Escpos(object): else: raise TextError() - def set(self, align='left', font='a', type='normal', width=1, height=1, density=9): """ Set text properties """ # Width @@ -251,7 +243,7 @@ class Escpos(object): elif width == 2 and height != 2: self._raw(TXT_NORMAL) self._raw(TXT_2WIDTH) - else: # DEFAULT SIZE: NORMAL + else: # DEFAULT SIZE: NORMAL self._raw(TXT_NORMAL) # Type if type.upper() == "B": @@ -303,10 +295,9 @@ class Escpos(object): self._raw(PD_P37) elif density == 8: self._raw(PD_P50) - else:# DEFAULT: DOES NOTHING + else: # DEFAULT: DOES NOTHING pass - def cut(self, mode=''): """ Cut paper """ # Fix the size between last line and cut @@ -314,10 +305,9 @@ 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): """ Send pulse to kick the cash drawer """ if pin == 2: @@ -327,7 +317,6 @@ class Escpos(object): else: raise CashDrawerError() - def hw(self, hw): """ Hardware operations """ if hw.upper() == "INIT": @@ -336,17 +325,16 @@ 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): """ Feed control sequences """ # Set tab positions if pos < 1 or pos > 16: raise TabError() else: - self._raw("".join([CTL_SET_HT,hex(pos)])) + self._raw("".join([CTL_SET_HT, hex(pos)])) # Set position if ctl.upper() == "LF": self._raw(CTL_LF) diff --git a/escpos/exceptions.py b/escpos/exceptions.py index 0d482aa..e1700e3 100644 --- a/escpos/exceptions.py +++ b/escpos/exceptions.py @@ -1,6 +1,5 @@ """ ESC/POS Exceptions classes """ -import os class Error(Exception): """ Base class for ESC/POS errors """ @@ -35,6 +34,7 @@ class BarcodeTypeError(Error): def __str__(self): return "No Barcode type is defined" + class BarcodeSizeError(Error): def __init__(self, msg=""): Error.__init__(self, msg) @@ -44,6 +44,7 @@ class BarcodeSizeError(Error): def __str__(self): return "Barcode size is out of range" + class BarcodeCodeError(Error): def __init__(self, msg=""): Error.__init__(self, msg) @@ -53,6 +54,7 @@ class BarcodeCodeError(Error): def __str__(self): return "Code was not supplied" + class ImageSizeError(Error): def __init__(self, msg=""): Error.__init__(self, msg) @@ -62,6 +64,7 @@ class ImageSizeError(Error): def __str__(self): return "Image height is longer than 255px and can't be printed" + class TextError(Error): def __init__(self, msg=""): Error.__init__(self, msg) diff --git a/escpos/printer.py b/escpos/printer.py index 3a3bc84..b6d9ca2 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -12,9 +12,9 @@ import serial import socket from escpos import * -from constants import * from exceptions import * + class Usb(Escpos): """ Define USB printer """ @@ -26,14 +26,13 @@ class Usb(Escpos): @param in_ep : Input end point @param out_ep : Output end point """ - self.idVendor = idVendor + 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): """ Search device on USB tree and set is as escpos device """ self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct) @@ -52,7 +51,7 @@ 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 detach kernel driver: %s" % str(e) try: self.device.set_configuration() @@ -60,12 +59,10 @@ class Usb(Escpos): except usb.core.USBError as e: print "Could not set configuration: %s" % str(e) - def _raw(self, msg): """ Print any command sent in raw format """ self.device.write(self.out_ep, msg, self.interface) - def __del__(self): """ Release USB interface """ if self.device: @@ -73,13 +70,12 @@ class Usb(Escpos): self.device = None - class Serial(Escpos): """ Define Serial printer """ def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, - xonxoff=False , dsrdtr=True): + xonxoff=False, dsrdtr=True): """ @param devfile : Device file under dev filesystem @param baudrate : Baud rate for serial transmission @@ -91,10 +87,10 @@ class Serial(Escpos): @param xonxoff : Software flow control @param dsrdtr : Hardware flow control (False to enable RTS/CTS) """ - self.devfile = devfile + self.devfile = devfile self.baudrate = baudrate self.bytesize = bytesize - self.timeout = timeout + self.timeout = timeout self.parity = parity self.stopbits = stopbits @@ -103,7 +99,6 @@ class Serial(Escpos): self.open() - def open(self): """ Setup serial port and set is as escpos device """ self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate, @@ -116,23 +111,20 @@ class Serial(Escpos): else: print "Unable to open serial printer on: %s" % self.devfile - def _raw(self, msg): """ Print any command sent in raw format """ self.device.write(msg) - def __del__(self): """ Close Serial interface """ if self.device is not None: self.device.close() - class Network(Escpos): """ Define Network printer """ - def __init__(self,host,port=9100): + def __init__(self, host, port=9100): """ @param host : Printer's hostname or IP address @param port : Port to write to @@ -141,7 +133,6 @@ class Network(Escpos): self.port = port self.open() - def open(self): """ Open TCP socket and set it as escpos device """ self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -150,18 +141,15 @@ class Network(Escpos): if self.device is None: print "Could not open socket for %s" % self.host - def _raw(self, msg): """ Print any command sent in raw format """ self.device.send(msg) - def __del__(self): """ Close TCP connection """ self.device.close() - class File(Escpos): """ Define Generic file printer """ @@ -172,7 +160,6 @@ class File(Escpos): self.devfile = devfile self.open() - def open(self): """ Open system file """ self.device = open(self.devfile, "wb") @@ -180,11 +167,9 @@ class File(Escpos): if self.device is None: print "Could not open the specified file %s" % self.devfile - def _raw(self, msg): """ Print any command sent in raw format """ - self.device.write(msg); - + self.device.write(msg) def __del__(self): """ Close system file """ From ef8035527c22190a5c537b758c5d306e862a18a4 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Fri, 27 Nov 2015 21:38:59 +0100 Subject: [PATCH 14/51] REFACTOR do not shadow built-ins --- escpos/escpos.py | 28 ++++++++++++++-------------- escpos/exceptions.py | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 4bec5f2..3149044 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -37,21 +37,21 @@ class Escpos(object): """ Print formatted image """ i = 0 cont = 0 - buffer = "" + pbuffer = "" self._raw(S_RASTER_N) - buffer = "%02X%02X%02X%02X" % (((size[0]/size[1])/8), 0, size[1] & 0xff, size[1] >> 8) - self._raw(buffer.decode('hex')) - buffer = "" + pbuffer = "%02X%02X%02X%02X" % (((size[0]/size[1])/8), 0, size[1] & 0xff, size[1] >> 8) + self._raw(pbuffer.decode('hex')) + pbuffer = "" while i < len(line): hex_string = int(line[i:i+8], 2) - buffer += "%02X" % hex_string + pbuffer += "%02X" % hex_string i += 8 cont += 1 if cont % 4 == 0: - self._raw(buffer.decode("hex")) - buffer = "" + self._raw(pbuffer.decode("hex")) + pbuffer = "" cont = 0 def _convert_image(self, im): @@ -231,7 +231,7 @@ class Escpos(object): else: raise TextError() - def set(self, align='left', font='a', type='normal', width=1, height=1, density=9): + def set(self, align='left', font='a', text_type='normal', width=1, height=1, density=9): """ Set text properties """ # Width if height == 2 and width == 2: @@ -246,22 +246,22 @@ class Escpos(object): else: # DEFAULT SIZE: NORMAL self._raw(TXT_NORMAL) # Type - if type.upper() == "B": + if text_type.upper() == "B": self._raw(TXT_BOLD_ON) self._raw(TXT_UNDERL_OFF) - elif type.upper() == "U": + elif text_type.upper() == "U": self._raw(TXT_BOLD_OFF) self._raw(TXT_UNDERL_ON) - elif type.upper() == "U2": + elif text_type.upper() == "U2": self._raw(TXT_BOLD_OFF) self._raw(TXT_UNDERL2_ON) - elif type.upper() == "BU": + elif text_type.upper() == "BU": self._raw(TXT_BOLD_ON) self._raw(TXT_UNDERL_ON) - elif type.upper() == "BU2": + elif text_type.upper() == "BU2": self._raw(TXT_BOLD_ON) self._raw(TXT_UNDERL2_ON) - elif type.upper == "NORMAL": + elif text_type.upper == "NORMAL": self._raw(TXT_BOLD_OFF) self._raw(TXT_UNDERL_OFF) # Font diff --git a/escpos/exceptions.py b/escpos/exceptions.py index e1700e3..d1b1842 100644 --- a/escpos/exceptions.py +++ b/escpos/exceptions.py @@ -85,7 +85,7 @@ class CashDrawerError(Error): return "Valid pin must be set to send pulse" -class TabError(Error): +class TabPosError(Error): def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg From 8b5798eedf6c51c400c495f60d81c00d98040088 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Fri, 27 Nov 2015 21:51:58 +0100 Subject: [PATCH 15/51] ADD requirements.txt and requirements to setup.py --- requirements.txt | 1 + setup.py | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ecf975e --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +-e . \ No newline at end of file diff --git a/setup.py b/setup.py index adbb97a..e031c85 100755 --- a/setup.py +++ b/setup.py @@ -23,7 +23,13 @@ setup( 'Operating System :: GNU/Linux', 'Intended Audience :: Developers', 'Programming Language :: Python', - 'Topic :: System :: Pheripherals', + 'Topic :: System :: Peripherals', 'Topic :: Software Development :: Libraries :: Python Modules', ], + install_requires=[ + 'pyusb', + 'Pillow>=2.0', + 'qrcode>=4.0', + 'pyserial', + ], ) From 0dacc35d940e4c8ed09cacb8e1ab25b5a2b9fe08 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Fri, 27 Nov 2015 23:10:20 +0100 Subject: [PATCH 16/51] DOC, IMPROVE improve docstrings and add abstract method _raw to Escpos --- escpos/escpos.py | 106 ++++++++++++++++++++++++++++++++++++------- escpos/exceptions.py | 10 +++- escpos/printer.py | 12 ++--- 3 files changed, 105 insertions(+), 23 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 3149044..2130f1b 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -16,25 +16,47 @@ import qrcode from constants import * from exceptions import * +from abc import ABCMeta, abstractmethod # abstract base class support + class Escpos(object): """ ESC/POS Printer object """ + __metaclass__ = ABCMeta device = None + @abstractmethod + def _raw(self, msg): + """ Sends raw data to the printer + + This function has to be individually implemented by the implementations. + :param msg: message string to be sent to the printer + """ + pass + @staticmethod def _check_image_size(size): - """ Check and fix the size of the image to 32 bits """ + """ Check and fix the size of the image to 32 bits + + :param size: size of the image + :returns: tuple of image borders + :rtype: (int, int) + """ if size % 32 == 0: return 0, 0 else: image_border = 32 - (size % 32) if (image_border % 2) == 0: + # TODO check behaviour of / in newer versions of python return image_border / 2, image_border / 2 else: return image_border / 2, (image_border / 2) + 1 def _print_image(self, line, size): - """ Print formatted image """ + """ Print formatted image + + :param line: + :param size: + """ i = 0 cont = 0 pbuffer = "" @@ -55,7 +77,11 @@ class Escpos(object): cont = 0 def _convert_image(self, im): - """ Parse image and prepare it to a printable format """ + """ Parse image and prepare it to a printable format + + :param im: image data + :raises: ImageSizeError + """ pixels = [] pix_line = "" im_left = "" @@ -101,7 +127,10 @@ class Escpos(object): self._print_image(pix_line, img_size) def image(self, path_img): - """ Open image file """ + """ Open image file + + :param path_img: path to image + """ im_open = Image.open(path_img) # Remove the alpha channel on transparent images @@ -116,7 +145,10 @@ class Escpos(object): self._convert_image(im) def qr(self, text): - """ Print QR Code for the provided string """ + """ 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.add_data(text) qr_code.make(fit=True) @@ -127,7 +159,13 @@ class Escpos(object): self._convert_image(im) def charcode(self, code): - """ Set Character Code Table """ + """ Set Character Code Table + + Sends the control sequence from constants.py to the printer with :py:meth:`escpos.printer._raw()`. + + :param code: Name of CharCode + :raises: CharCodeError + """ if code.upper() == "USA": self._raw(CHARCODE_PC437) elif code.upper() == "JIS": @@ -146,8 +184,8 @@ class Escpos(object): self._raw(CHARCODE_GREEK) elif code.upper() == "HEBREW": self._raw(CHARCODE_HEBREW) - elif code.upper() == "LATVIAN": - self._raw(CHARCODE_PC755) + # elif code.upper() == "LATVIAN": # this is not listed in the constants + # self._raw(CHARCODE_PC755) elif code.upper() == "WPC1252": self._raw(CHARCODE_PC1252) elif code.upper() == "CIRILLIC2": @@ -174,7 +212,16 @@ class Escpos(object): raise CharCodeError() def barcode(self, code, bc, width, height, pos, font): - """ Print Barcode """ + """ Print Barcode + + :param code: data for barcode + :param bc: barcode format, see constants.py + :param width: barcode width, has to be between 1 and 255 + :param height: barcode height, has to be between 2 and 6 + :param pos: position of text in barcode, default when nothing supplied is below + :param font: select font, default is font A + :raises: BarcodeSizeError, BarcodeTypeError, BarcodeCodeError + """ # Align Bar Code() self._raw(TXT_ALIGN_CT) # Height @@ -225,14 +272,27 @@ class Escpos(object): raise BarcodeCodeError() def text(self, txt): - """ Print alpha-numeric text """ + """ Print alpha-numeric text + + The text has to be encoded in the currently selected codepage. + :param txt: text to be printed + :raises: TextError + """ if txt: self._raw(txt) else: raise TextError() def set(self, align='left', font='a', text_type='normal', width=1, height=1, density=9): - """ Set text properties """ + """ Set text properties by sending them to the printer + + :param align: alignment of text + :param font: font A or B + :param text_type: add bold or underlined + :param width: text width, normal or double width + :param height: text height, normal or double height + :param density: print density + """ # Width if height == 2 and width == 2: self._raw(TXT_NORMAL) @@ -299,7 +359,10 @@ class Escpos(object): pass def cut(self, mode=''): - """ Cut paper """ + """ Cut paper + + :param mode: set to 'PART' for a partial cut + """ # Fix the size between last line and cut # TODO: handle this with a line feed self._raw("\n\n\n\n\n\n") @@ -309,7 +372,12 @@ class Escpos(object): self._raw(PAPER_FULL_CUT) def cashdraw(self, pin): - """ Send pulse to kick the cash drawer """ + """ Send pulse to kick the cash drawer + + Kick cash drawer on pin 2 or pin 5. + :param pin: pin number + :raises: CashDrawerError + """ if pin == 2: self._raw(CD_KICK_2) elif pin == 5: @@ -318,7 +386,10 @@ class Escpos(object): raise CashDrawerError() def hw(self, hw): - """ Hardware operations """ + """ Hardware operations + + :param hw: hardware action + """ if hw.upper() == "INIT": self._raw(HW_INIT) elif hw.upper() == "SELECT": @@ -329,10 +400,13 @@ class Escpos(object): pass def control(self, ctl, pos=4): - """ Feed control sequences """ + """ Feed control sequences + + :raises: TabPosError + """ # Set tab positions if pos < 1 or pos > 16: - raise TabError() + raise TabPosError() else: self._raw("".join([CTL_SET_HT, hex(pos)])) # Set position diff --git a/escpos/exceptions.py b/escpos/exceptions.py index d1b1842..8235943 100644 --- a/escpos/exceptions.py +++ b/escpos/exceptions.py @@ -26,6 +26,7 @@ class Error(Exception): class BarcodeTypeError(Error): + """No Barcode type defined """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -36,6 +37,7 @@ class BarcodeTypeError(Error): class BarcodeSizeError(Error): + """ Barcode size is out of range """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -46,16 +48,18 @@ class BarcodeSizeError(Error): class BarcodeCodeError(Error): + """ No Barcode code was supplied """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg self.resultcode = 30 def __str__(self): - return "Code was not supplied" + return "No Barcode code was supplied" class ImageSizeError(Error): + """ Image height is longer than 255px and can't be printed """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -66,6 +70,7 @@ class ImageSizeError(Error): class TextError(Error): + """ Test sting must be supplied to the text() method """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -76,6 +81,7 @@ class TextError(Error): class CashDrawerError(Error): + """ Valid pin must be set to send pulse """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -86,6 +92,7 @@ class CashDrawerError(Error): class TabPosError(Error): + """ Valid tab positions must be in the range 0 to 16 """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -96,6 +103,7 @@ class TabPosError(Error): class CharCodeError(Error): + """ Valid char code must be set """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg diff --git a/escpos/printer.py b/escpos/printer.py index b6d9ca2..a82359a 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -20,11 +20,11 @@ class Usb(Escpos): def __init__(self, idVendor, idProduct, interface=0, in_ep=0x82, out_ep=0x01): """ - @param idVendor : Vendor ID - @param idProduct : Product ID - @param interface : USB device interface - @param in_ep : Input end point - @param out_ep : Output end point + :param idVendor: Vendor ID + :param idProduct: Product ID + :param interface: USB device interface + :param in_ep: Input end point + :param out_ep: Output end point """ self.idVendor = idVendor self.idProduct = idProduct @@ -34,7 +34,7 @@ class Usb(Escpos): self.open() def open(self): - """ Search device on USB tree and set is as escpos device """ + """ 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" From 8fd05eb02bc78584bde9a55c0919991c81e0fdf0 Mon Sep 17 00:00:00 2001 From: Christoph Heuel Date: Sun, 29 Nov 2015 16:03:56 +0100 Subject: [PATCH 17/51] Add flush function * Flushing the file will cause the driver to flush the cache and print * the whole buffer. --- escpos/printer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/escpos/printer.py b/escpos/printer.py index 986d366..fd4e9e8 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -176,6 +176,10 @@ class File(Escpos): if self.device is None: print("Could not open the specified file %s" % self.devfile) + def flush(self): + """Flush printing content""" + self.device.flush() + def _raw(self, msg): """ Print any command sent in raw format """ From 029549aaae7c9acb074bab9c701b27d97d3fbd7f Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 30 Dec 2015 16:58:28 +0100 Subject: [PATCH 18/51] IMPROVE packaging and testing * uses now tox (currently with no tests) on python 2.7 and 3.4 * upgraded setup.py to setuptools --- .gitignore | 4 ++++ setup.py | 35 +++++++++++++++++++++++++++++++++-- tox.ini | 7 +++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 9d3808a..c8637aa 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ $~ # temporary data temp + +# packaging and testing +.tox/ +*.egg-info/ diff --git a/setup.py b/setup.py index e031c85..eba6372 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,35 @@ #!/usr/bin/python -from distutils.core import setup +import os +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): + user_options = [('tox-args=', 'a', "Arguments to pass to tox")] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.tox_args = None + + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = [] + self.test_suite = True + + def run_tests(self): + #import here, cause outside the eggs aren't loaded + import tox + import shlex + args = self.tox_args + if args: + args = shlex.split(self.tox_args) + errno = tox.cmdline(args=args) + sys.exit(errno) setup( name='escpos', @@ -9,7 +38,7 @@ setup( download_url='https://github.com/manpaz/python-escpos.git', description='Python library to manipulate ESC/POS Printers', license='GNU GPL v3', - long_description=open('README').read(), + long_description=read('README'), author='Manuel F Martinez', author_email='manpaz@bashlinux.com', platforms=['linux'], @@ -32,4 +61,6 @@ setup( 'qrcode>=4.0', 'pyserial', ], + tests_require=['tox'], + cmdclass={'test': Tox}, ) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..880e6cc --- /dev/null +++ b/tox.ini @@ -0,0 +1,7 @@ +[tox] +envlist = py27, py34 + +[testenv] +deps = nose + coverage +# TODO: implement code coverage analysis (and of course tests at first) From 8b8ab80e5f9b1bdf22c9ddbc56f422f500df22e7 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 5 Jan 2016 10:39:28 +0100 Subject: [PATCH 19/51] ADD travis-configuration-file and comments in setup.py --- .travis.yml | 10 ++++++++++ setup.py | 4 ++++ 2 files changed, 14 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..576f5e6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: python +sudo: false +cache: pip +before_install: + - pip install codecov +after_success: + - codecov +# command to run tests +script: + - tox \ No newline at end of file diff --git a/setup.py b/setup.py index eba6372..a3e5d84 100755 --- a/setup.py +++ b/setup.py @@ -10,18 +10,22 @@ def read(fname): 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")] def initialize_options(self): + """initialize the user-options""" TestCommand.initialize_options(self) self.tox_args = None def finalize_options(self): + """finalize user-options""" TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): + """run tox and pass on user-options""" #import here, cause outside the eggs aren't loaded import tox import shlex From 3d61445e2c67be2011f5df5a150142471cfb8561 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 5 Jan 2016 12:12:33 +0100 Subject: [PATCH 20/51] IMPROVE explicitly install dependecies on travis --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 576f5e6..020a74f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,9 @@ sudo: false cache: pip before_install: - pip install codecov +# install dependencies +install: + - pip install --user -r requirements.txt after_success: - codecov # command to run tests From e5cd37bfbc5f0c70df4767fe37f7d165a6453d06 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 5 Jan 2016 12:19:18 +0100 Subject: [PATCH 21/51] clean travis configuration file 3d61445e2c67be2011f5df5a150142471cfb8561 did not really improve the travis-build. Now try to explcitly install tox. --- .travis.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 020a74f..21ba88d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,7 @@ language: python sudo: false cache: pip before_install: - - pip install codecov -# install dependencies -install: - - pip install --user -r requirements.txt -after_success: - - codecov + - pip install tox # command to run tests script: - tox \ No newline at end of file From 8b8ca76af65742ede47dd41ad0732ca34dd47467 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 5 Jan 2016 17:30:40 +0100 Subject: [PATCH 22/51] DOC improve the documentation * make API-doc more legible * improve comments in methods --- doc/api.rst | 46 -------------- doc/api/constants.rst | 10 +++ doc/api/escpos.rst | 10 +++ doc/api/exceptions.rst | 9 +++ doc/api/printer.rst | 10 +++ doc/index.rst | 13 ++-- doc/user/methods.rst | 3 +- doc/user/todo.rst | 3 + escpos/constants.py | 12 +++- escpos/escpos.py | 139 ++++++++++++++++++++++++++++++++--------- escpos/exceptions.py | 82 +++++++++++++++++------- escpos/printer.py | 84 +++++++++++++++++-------- 12 files changed, 289 insertions(+), 132 deletions(-) delete mode 100644 doc/api.rst create mode 100644 doc/api/constants.rst create mode 100644 doc/api/escpos.rst create mode 100644 doc/api/exceptions.rst create mode 100644 doc/api/printer.rst diff --git a/doc/api.rst b/doc/api.rst deleted file mode 100644 index a3b00b1..0000000 --- a/doc/api.rst +++ /dev/null @@ -1,46 +0,0 @@ -escpos package -============== - -Submodules ----------- - -escpos.constants module ------------------------ - -.. automodule:: escpos.constants - :members: - :undoc-members: - :show-inheritance: - -escpos.escpos module --------------------- - -.. automodule:: escpos.escpos - :members: - :undoc-members: - :show-inheritance: - -escpos.exceptions module ------------------------- - -.. automodule:: escpos.exceptions - :members: - :undoc-members: - :show-inheritance: - -escpos.printer module ---------------------- - -.. automodule:: escpos.printer - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: escpos - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/api/constants.rst b/doc/api/constants.rst new file mode 100644 index 0000000..0165d61 --- /dev/null +++ b/doc/api/constants.rst @@ -0,0 +1,10 @@ +Constants +--------- +Module :py:mod:`escpos.constants` + +.. automodule:: escpos.constants + :members: + :inherited-members: + :undoc-members: + :show-inheritance: + :member-order: bysource diff --git a/doc/api/escpos.rst b/doc/api/escpos.rst new file mode 100644 index 0000000..2a85256 --- /dev/null +++ b/doc/api/escpos.rst @@ -0,0 +1,10 @@ +Esc/Pos +------- +Module :py:mod:`escpos.escpos` + +.. automodule:: escpos.escpos + :members: + :inherited-members: + :undoc-members: + :show-inheritance: + :member-order: bysource diff --git a/doc/api/exceptions.rst b/doc/api/exceptions.rst new file mode 100644 index 0000000..6edb5ea --- /dev/null +++ b/doc/api/exceptions.rst @@ -0,0 +1,9 @@ +Exceptions +---------- +Module :py:mod:`escpos.exceptions` + +.. automodule:: escpos.exceptions + :members: + :inherited-members: + :show-inheritance: + :member-order: bysource \ No newline at end of file diff --git a/doc/api/printer.rst b/doc/api/printer.rst new file mode 100644 index 0000000..98654cb --- /dev/null +++ b/doc/api/printer.rst @@ -0,0 +1,10 @@ +Printer implementations +----------------------- +Module :py:mod:`escpos.printer` + +.. automodule:: escpos.printer + :members: + :inherited-members: + :undoc-members: + :show-inheritance: + :member-order: bysource diff --git a/doc/index.rst b/doc/index.rst index 14cd7be..031189f 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -25,8 +25,8 @@ sending my PayPal info so you can donate. Thank you! -User Documentation: -------------------- +User Documentation +------------------ .. toctree:: :maxdepth: 1 @@ -39,13 +39,16 @@ User Documentation: user/todo user/usage -API: ----- +API +--- .. toctree:: :maxdepth: 1 - api + api/escpos + api/printer + api/constants + api/exceptions Indices and tables ================== diff --git a/doc/user/methods.rst b/doc/user/methods.rst index b4ce495..976bde6 100644 --- a/doc/user/methods.rst +++ b/doc/user/methods.rst @@ -2,7 +2,8 @@ Methods ******* -.. note:: **TODO** Merge this page into the API-description. +.. note:: **TODO** Merge this page with the API-description. (Make the API-description more pretty and then + replace this with the API-description.) Escpos class ------------ diff --git a/doc/user/todo.rst b/doc/user/todo.rst index 3777a7d..2a54df8 100644 --- a/doc/user/todo.rst +++ b/doc/user/todo.rst @@ -21,6 +21,7 @@ Testing ~~~~~~~ * Test on many printers as possible (USB, Serial, Network) +* automate testing Design ~~~~~~ @@ -32,4 +33,6 @@ Design * Windows compatibility (hidapi instead libusb?) * PDF417 support +* use something similar to the `capabilities` in escpos-php + diff --git a/escpos/constants.py b/escpos/constants.py index af71c48..67c6e3b 100644 --- a/escpos/constants.py +++ b/escpos/constants.py @@ -1,4 +1,14 @@ -""" ESC/POS Commands (Constants) """ +""" Set of ESC/POS Commands (Constants) + +This module contains constants that are described in the esc/pos-documentation. +Since there is no definitive and unified specification for all esc/pos-like printers the constants could later be +moved to `capabilities` as in `escpos-php by @mike42 `_. + +:author: `Manuel F Martinez `_ and others +:organization: Bashlinux and `python-escpos `_ +:copyright: Copyright (c) 2012 Bashlinux +:license: GNU GPL v3 +""" # Feed control sequences CTL_LF = '\x0a' # Print and line feed diff --git a/escpos/escpos.py b/escpos/escpos.py index 65b673e..1622df3 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -1,9 +1,12 @@ #!/usr/bin/python -""" -@author: Manuel F Martinez -@organization: Bashlinux -@copyright: Copyright (c) 2012 Bashlinux -@license: GNU GPL v3 +""" Main class + +This module contains the abstract base class :py:class:`Escpos`. + +:author: `Manuel F Martinez `_ and others +:organization: Bashlinux and `python-escpos `_ +:copyright: Copyright (c) 2012 Bashlinux +:license: GNU GPL v3 """ try: @@ -22,11 +25,14 @@ from .exceptions import * from abc import ABCMeta, abstractmethod # abstract base class support class Escpos(object): - """ ESC/POS Printer object """ + """ ESC/POS Printer object + + This class is the abstract base class for an esc/pos-printer. The printer implementations are children of this + class. + """ __metaclass__ = ABCMeta device = None - def __init__(self, columns=32): """ Initialize ESCPOS Printer @@ -38,6 +44,7 @@ class Escpos(object): """ Sends raw data to the printer This function has to be individually implemented by the implementations. + :param msg: message string to be sent to the printer """ pass @@ -88,7 +95,7 @@ class Escpos(object): """ Parse image and prepare it to a printable format :param im: image data - :raises: ImageSizeError + :raises: :py:exc:`~escpos.exceptions.ImageSizeError` """ pixels = [] pix_line = "" @@ -135,9 +142,11 @@ class Escpos(object): self._print_image(pix_line, img_size) def image(self, path_img): - """ Open image file + """ Open and print an image file - :param path_img: path to image + Prints an image. The image is automatically adjusted in size in order to print it. + + :param path_img: complete filename and path to image of type `jpg`, `gif`, `png` or `bmp` """ im_open = Image.open(path_img) @@ -153,7 +162,10 @@ class Escpos(object): self._convert_image(im) def direct_image(self, image): - """ Send image to printer""" + """ Send image to printer + + :param image: + """ mask = 0x80 i = 0 temp = 0 @@ -188,6 +200,9 @@ class Escpos(object): def qr(self, text): """ Print QR Code for the provided string + Prints a QR-code. The size has been adjusted to version 4, so it is small enough to be + printed but also big enough to be read by a smartphone. + :param text: text to generate a QR-Code from """ qr_code = qrcode.QRCode(version=4, box_size=4, border=1) @@ -202,10 +217,11 @@ class Escpos(object): def charcode(self, code): """ Set Character Code Table - Sends the control sequence from constants.py to the printer with :py:meth:`escpos.printer._raw()`. + Sends the control sequence from :py:mod:`escpos.constants` to the printer + with :py:meth:`escpos.printer.'implementation'._raw()`. :param code: Name of CharCode - :raises: CharCodeError + :raises: :py:exc:`~escpos.exceptions.CharCodeError` """ if code.upper() == "USA": self._raw(CHARCODE_PC437) @@ -255,13 +271,37 @@ class Escpos(object): def barcode(self, code, bc, width, height, pos, font): """ Print Barcode - :param code: data for barcode - :param bc: barcode format, see constants.py + :param code: alphanumeric data to be printed as bar code + :param bc: barcode format, possible values are: + + * UPC-A + * UPC-E + * EAN13 + * EAN8 + * CODE39 + * ITF + * 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 - :param pos: position of text in barcode, default when nothing supplied is below - :param font: select font, default is font A - :raises: BarcodeSizeError, BarcodeTypeError, BarcodeCodeError + *default*: 3 + :param pos: where to place the text relative to the barcode, *default*: below + + * ABOVE + * BELOW + * BOTH + * OFF + + :param font: select font (see ESC/POS-documentation, the device often has two fonts), *default*: A + + * A + * B + + :raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`, + :py:exc:`~escpos.exceptions.BarcodeTypeError`, + :py:exc:`~escpos.exceptions.BarcodeCodeError` """ # Align Bar Code() self._raw(TXT_ALIGN_CT) @@ -316,28 +356,51 @@ class Escpos(object): """ Print alpha-numeric text The text has to be encoded in the currently selected codepage. + :param txt: text to be printed - :raises: TextError + :raises: :py:exc:`~escpos.exceptions.TextError` """ if txt: self._raw(txt) else: + # TODO: why is it problematic to print an empty string? raise TextError() def block_text(self, txt, columns=None): - '''Text is printed wrapped to specified columns''' + """ Text is printed wrapped to specified columns + + :param txt: text to be printed + :param columns: amount of columns + :return: None + """ colCount = self.columns if columns == 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): """ Set text properties by sending them to the printer - :param align: alignment of text - :param font: font A or B - :param text_type: add bold or underlined - :param width: text width, normal or double width - :param height: text height, normal or double height - :param density: print density + :param align: horizontal position for text, possible values are: + + * CENTER + * LEFT + * RIGHT + + *default*: LEFT + :param font: font type, possible values are A or B, *default*: A + :param text_type: text type, possible values are: + + * B for bold + * U for underlined + * B2 for bold, version 2 + * U2 for underlined, version 2 + * BU for bold and underlined + * BU2 for bold and underlined, version 2 + * NORMAL for normal text + + *default*: NORMAL + :param width: text width, normal (1) or double width (2), *default*: 1 + :param height: text height, normal (1) or double height (2), *default*: 2 + :param density: print density, value from 0-8, if something else is supplied the density remains unchanged """ # Width if height == 2 and width == 2: @@ -420,9 +483,10 @@ class Escpos(object): def cashdraw(self, pin): """ Send pulse to kick the cash drawer - Kick cash drawer on pin 2 or pin 5. - :param pin: pin number - :raises: CashDrawerError + Kick cash drawer on pin 2 or pin 5 according to parameter. + + :param pin: pin number, 2 or 5 + :raises: :py:exc:`~escpos.exceptions.CashDrawerError` """ if pin == 2: self._raw(CD_KICK_2) @@ -434,7 +498,11 @@ class Escpos(object): def hw(self, hw): """ Hardware operations - :param hw: hardware action + :param hw: hardware action, may be: + + * INIT + * SELECT + * RESET """ if hw.upper() == "INIT": self._raw(HW_INIT) @@ -448,7 +516,16 @@ class Escpos(object): def control(self, ctl, pos=4): """ Feed control sequences - :raises: TabPosError + :param ctl: string for the following control sequences: + + * LF *for Line Feed* + * FF *for Form Feed* + * CR *for Carriage Return* + * HT *for Horizontal Tab* + * VT *for Vertical Tab* + + :param pos: integer between 1 and 16, controls the horizontal tab position + :raises: :py:exc:`~escpos.exceptions.TabPosError` """ # Set tab positions if pos < 1 or pos > 16: diff --git a/escpos/exceptions.py b/escpos/exceptions.py index 8235943..bb72e24 100644 --- a/escpos/exceptions.py +++ b/escpos/exceptions.py @@ -1,4 +1,22 @@ -""" ESC/POS Exceptions classes """ +""" ESC/POS Exceptions classes + +Result/Exit codes: + + - `0` = success + - `10` = No Barcode type defined :py:exc:`~escpos.exceptions.BarcodeTypeError` + - `20` = Barcode size values are out of range :py:exc:`~escpos.exceptions.BarcodeSizeError` + - `30` = Barcode text not supplied :py:exc:`~escpos.exceptions.BarcodeCodeError` + - `40` = Image height is too large :py:exc:`~escpos.exceptions.ImageSizeError` + - `50` = No string supplied to be printed :py:exc:`~escpos.exceptions.TextError` + - `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` + +:author: `Manuel F Martinez `_ and others +:organization: Bashlinux and `python-escpos `_ +:copyright: Copyright (c) 2012 Bashlinux +:license: GNU GPL v3 +""" class Error(Exception): @@ -13,20 +31,14 @@ class Error(Exception): def __str__(self): return self.msg -# Result/Exit codes -# 0 = success -# 10 = No Barcode type defined -# 20 = Barcode size values are out of range -# 30 = Barcode text not supplied -# 40 = Image height is too large -# 50 = No string supplied to be printed -# 60 = Invalid pin to send Cash Drawer pulse -# 70 = Invalid number of tab positions -# 80 = Invalid char code - class BarcodeTypeError(Error): - """No Barcode type defined """ + """ No Barcode type defined. + + This exception indicates that no known barcode-type has been entered. The barcode-type has to be + one of those specified in :py:meth:`escpos.escpos.Escpos.barcode`. + The returned error code is `10`. + """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -37,7 +49,12 @@ class BarcodeTypeError(Error): class BarcodeSizeError(Error): - """ Barcode size is out of range """ + """ Barcode size is out of range. + + This exception indicates that the values for the barcode size are out of range. + The size of the barcode has to be in the range that is specified in :py:meth:`escpos.escpos.Escpos.barcode`. + The resulting returncode is `20`. + """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -48,7 +65,11 @@ class BarcodeSizeError(Error): class BarcodeCodeError(Error): - """ No Barcode code was supplied """ + """ No Barcode code was supplied. + + No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode`. + The returncode for this exception is `30`. + """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -59,7 +80,10 @@ class BarcodeCodeError(Error): class ImageSizeError(Error): - """ Image height is longer than 255px and can't be printed """ + """ Image height is longer than 255px and can't be printed. + + The returncode for this exception is `40`. + """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -70,7 +94,11 @@ class ImageSizeError(Error): class TextError(Error): - """ Test sting must be supplied to the text() method """ + """ Text string must be supplied to the `text()` method. + + This exception is raised when an empty string is passed to :py:meth:`escpos.escpos.Escpos.text`. + The returncode for this exception is `50`. + """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -81,7 +109,11 @@ class TextError(Error): class CashDrawerError(Error): - """ Valid pin must be set to send pulse """ + """ Valid pin must be set in order to send pulse. + + A valid pin number has to be passed onto the method :py:meth:`escpos.escpos.Escpos.cashdraw`. + The returncode for this exception is `60`. + """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -92,7 +124,11 @@ class CashDrawerError(Error): class TabPosError(Error): - """ Valid tab positions must be in the range 0 to 16 """ + """ Valid tab positions must be in the range 0 to 16. + + This exception is raised by :py:meth:`escpos.escpos.Escpos.control`. + The returncode for this exception is `70`. + """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg @@ -103,11 +139,15 @@ class TabPosError(Error): class CharCodeError(Error): - """ Valid char code must be set """ + """ Valid char code must be set. + + The supplied charcode-name in :py:meth:`escpos.escpos.Escpos.charcode` is unknown. + Ths returncode for this exception is `80`. + """ def __init__(self, msg=""): Error.__init__(self, msg) self.msg = msg - self.resultcode = 70 + self.resultcode = 80 def __str__(self): return "Valid char code must be set" diff --git a/escpos/printer.py b/escpos/printer.py index bf6dbe5..1c3f92f 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -1,9 +1,10 @@ #!/usr/bin/python -""" -@author: Manuel F Martinez -@organization: Bashlinux -@copyright: Copyright (c) 2012 Bashlinux -@license: GNU GPL v3 +""" This module contains the implentations of abstract base class :py:class:`Escpos`. + +:author: `Manuel F Martinez `_ and others +:organization: Bashlinux and `python-escpos `_ +:copyright: Copyright (c) 2012 Bashlinux +:license: GNU GPL v3 """ import usb.core @@ -17,7 +18,10 @@ from .exceptions import * class Usb(Escpos): - """ Define USB printer """ + """ USB printer + + This class describes a printer that natively speaks USB. + """ def __init__(self, idVendor, idProduct, interface=0, in_ep=0x82, out_ep=0x01, *args, **kwargs): """ @@ -62,7 +66,10 @@ class Usb(Escpos): print("Could not set configuration: %s" % str(e)) def _raw(self, msg): - """ Print any command sent in raw format """ + """ Print any command sent in raw format + + :param msg: arbitrary code to be printed + """ self.device.write(self.out_ep, msg, self.interface) def __del__(self): @@ -73,21 +80,24 @@ class Usb(Escpos): class Serial(Escpos): - """ Define Serial printer """ + """ Serial printer + + This class describes a printer that is connected by serial interface. + """ def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, - xonxoff=False , dsrdtr=True, *args, **kwargs): + xonxoff=False, dsrdtr=True, *args, **kwargs): """ - @param devfile : Device file under dev filesystem - @param baudrate : Baud rate for serial transmission - @param bytesize : Serial buffer size - @param timeout : Read/Write timeout - @param parity : Parity checking - @param stopbits : Number of stop bits - @param xonxoff : Software flow control - @param dsrdtr : Hardware flow control (False to enable RTS/CTS) + :param devfile: Device file under dev filesystem + :param baudrate: Baud rate for serial transmission + :param bytesize: Serial buffer size + :param timeout: Read/Write timeout + :param parity: Parity checking + :param stopbits: Number of stop bits + :param xonxoff: Software flow control + :param dsrdtr: Hardware flow control (False to enable RTS/CTS) """ Escpos.__init__(self, *args, **kwargs) self.devfile = devfile @@ -114,7 +124,10 @@ class Serial(Escpos): print("Unable to open serial printer on: %s" % self.devfile) def _raw(self, msg): - """ Print any command sent in raw format """ + """ Print any command sent in raw format + + :param msg: arbitrary code to be printed + """ self.device.write(msg) def __del__(self): @@ -124,12 +137,17 @@ class Serial(Escpos): class Network(Escpos): - """ Define Network printer """ + """ Network printer - def __init__(self,host,port=9100, *args, **kwargs): + This class is used to attach to a networked printer. You can also use this in order to attach to a printer that + is forwarded with `netcat`. + """ + + def __init__(self, host, port=9100, *args, **kwargs): """ - @param host : Printer's hostname or IP address - @param port : Port to write to + + :param host : Printer's hostname or IP address + :param port : Port to write to """ Escpos.__init__(self, *args, **kwargs) self.host = host @@ -145,7 +163,10 @@ class Network(Escpos): print("Could not open socket for %s" % self.host) def _raw(self, msg): - """ Print any command sent in raw format """ + """ Print any command sent in raw format + + :param msg: arbitrary code to be printed + """ self.device.send(msg) def __del__(self): @@ -154,11 +175,17 @@ class Network(Escpos): class File(Escpos): - """ Define Generic file printer """ + """ Generic file printer + + This class is used for parallel port printer or other printers that are directly attached to the filesystem. + Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable + and produce arbitrary errors. + """ def __init__(self, devfile="/dev/usb/lp0", *args, **kwargs): """ - @param devfile : Device file under dev filesystem + + :param devfile : Device file under dev filesystem """ Escpos.__init__(self, *args, **kwargs) self.devfile = devfile @@ -172,11 +199,14 @@ class File(Escpos): print("Could not open the specified file %s" % self.devfile) def flush(self): - """Flush printing content""" + """ Flush printing content """ self.device.flush() def _raw(self, msg): - """ Print any command sent in raw format """ + """ Print any command sent in raw format + + :param msg: arbitrary code to be printed + """ if type(msg) is str: self.device.write(msg.encode()); else: From c2b45748e1cc0e22b3e775658e6527bd1d7b1c70 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 5 Jan 2016 23:58:40 +0100 Subject: [PATCH 23/51] DOC hide inherited members in printer-api --- doc/api/printer.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/api/printer.rst b/doc/api/printer.rst index 98654cb..fe4554e 100644 --- a/doc/api/printer.rst +++ b/doc/api/printer.rst @@ -4,7 +4,6 @@ Module :py:mod:`escpos.printer` .. automodule:: escpos.printer :members: - :inherited-members: :undoc-members: :show-inheritance: :member-order: bysource From e88a19ef2d54c9e11049190ba88b5e49ba6dbec3 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 6 Jan 2016 12:02:30 +0100 Subject: [PATCH 24/51] DOC correct mistake in documentation --- escpos/escpos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 1622df3..3a7a83f 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -399,7 +399,7 @@ class Escpos(object): *default*: NORMAL :param width: text width, normal (1) or double width (2), *default*: 1 - :param height: text height, normal (1) or double height (2), *default*: 2 + :param height: text height, normal (1) or double height (2), *default*: 1 :param density: print density, value from 0-8, if something else is supplied the density remains unchanged """ # Width From 69680b04e9f98f54c826cd8f77e01973343e6183 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 6 Jan 2016 12:13:35 +0100 Subject: [PATCH 25/51] DOC added notice on RS232 voltage levels fixes #41 --- doc/user/raspi.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/user/raspi.rst b/doc/user/raspi.rst index 8786768..387c648 100644 --- a/doc/user/raspi.rst +++ b/doc/user/raspi.rst @@ -7,6 +7,11 @@ This instructions were tested on Raspbian. Unless you have done any distro with libusb-1.0 on the Raspberry Pi, the following instructions should works fine on your raspberry distro. +.. warning:: You should **never** directly connect an printer with RS232-interface (serial port) directly to + a Raspberry PI or similar interface (e.g. those simple USB-sticks without encasing). Those interfaces are + based on 5V- or 3,3V-logic (the latter in the case of Raspberry PI). Classical RS232 uses 12V-logic and would + **thus destroy your interface**. Connect both systems with an appropriate *level shifter*. + Dependencies ------------ From 592dbe15f6b3d1886a244706858010862e96a374 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 7 Jan 2016 12:41:27 +0100 Subject: [PATCH 26/51] ADD .directory to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c8637aa..6c58997 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ # python temporary files *.pyc -# editor autosaves +# editor autosaves, data and file browser files $~ .idea/ +.directory # temporary data temp From 020ba4145cc1d0c4fffbcb5925678b9368c08f3f Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 7 Jan 2016 23:22:31 +0100 Subject: [PATCH 27/51] DOC add python version information to setup --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index a3e5d84..a68987d 100755 --- a/setup.py +++ b/setup.py @@ -56,6 +56,10 @@ setup( 'Operating System :: GNU/Linux', 'Intended Audience :: Developers', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', 'Topic :: System :: Peripherals', 'Topic :: Software Development :: Libraries :: Python Modules', ], From 9159aafd183307d959f9eae1e31ffeee29ab4b69 Mon Sep 17 00:00:00 2001 From: Davis Goglin Date: Fri, 14 Mar 2014 09:05:47 -0700 Subject: [PATCH 28/51] 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 29/51] 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 30/51] 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 31/51] 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 32/51] 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 33/51] 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 34/51] 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 35/51] 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 36/51] 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 37/51] 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 38/51] 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 39/51] 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 40/51] 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 """ From 939e66834e624938c268f4234ae7fab19db8ca57 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 13 Jan 2016 16:06:18 +0100 Subject: [PATCH 41/51] DOC elaborate on partial cut closes #44 --- escpos/escpos.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index dc8912e..fc94f95 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -507,7 +507,11 @@ class Escpos(object): pass def cut(self, mode=''): - """ Cut paper + """ Cut paper. + + Without any arguments the paper will be cut completely. With 'mode=PART' a partial cut will + be attempted. Note however, that not all models can do a partial cut. See the documentation of + your printer for details. :param mode: set to 'PART' for a partial cut """ From d00fc5016b5328ff4e3038aca64cde712ddb8960 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 14 Jan 2016 00:53:44 +0100 Subject: [PATCH 42/51] DOC add a todo directive to the documentation .. todo:: will create Todos that are listed in the TODO in the documentation --- doc/conf.py | 4 ++++ doc/user/todo.rst | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/doc/conf.py b/doc/conf.py index dac6859..b110f59 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -34,8 +34,12 @@ extensions = [ 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', + 'sphinx.ext.todo', ] +# enable todos +todo_include_todos = True + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/doc/user/todo.rst b/doc/user/todo.rst index 2a54df8..8e79fde 100644 --- a/doc/user/todo.rst +++ b/doc/user/todo.rst @@ -35,4 +35,9 @@ Design * use something similar to the `capabilities` in escpos-php +Todos in the codebase +~~~~~~~~~~~~~~~~~~~~~ + +.. todolist:: + From cd2da59fc28730cb966aee9bb14e2133554d4403 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 14 Jan 2016 01:00:53 +0100 Subject: [PATCH 43/51] FIX and review barcode-printing --- escpos/escpos.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index fc94f95..26e4911 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -147,6 +147,9 @@ class Escpos(object): Prints an image. The image is automatically adjusted in size in order to print it. + .. todo:: Seems to be broken. Write test that simply executes function with a dummy printer in order to + check for bugs like these in the future. + :param path_img: complete filename and path to image of type `jpg`, `gif`, `png` or `bmp` """ im_open = Image.open(path_img) @@ -163,7 +166,11 @@ class Escpos(object): self._convert_image(im) def fullimage(self, img, max_height=860, width=512, histeq=True, bandsize=255): - """ Resizes and prints an arbitrarily sized image """ + """ Resizes and prints an arbitrarily sized image + + .. todo:: Seems to be broken. Write test that simply executes function with a dummy printer in order to + check for bugs like these in the future. + """ if isinstance(img, (Image, Image.Image)): im = img.convert("RGB") else: @@ -310,6 +317,13 @@ class Escpos(object): def barcode(self, code, bc, height, width, pos, font): """ Print Barcode + .. todo:: Documentation tells about default values. In fact no default values exist and supplying a default + value does not work. + .. todo:: Add a method to check barcode codes. + .. todo:: On TM-T88II width from 1 to 6 is accepted. Try to acquire command reference and correct the code. + .. todo:: Supplying pos has no effect in TM-T88II. Printed always below. Verify with other printers and , if + necessary, turn this into an issue. + :param code: alphanumeric data to be printed as bar code :param bc: barcode format, possible values are: @@ -322,10 +336,10 @@ class Escpos(object): * NW7 If none is specified, the method raises :py:exc:`~escpos.exceptions.BarcodeTypeError`. - :param height: barcode height, has to be between 2 and 6 - *default*: 3 - :param width: barcode width, has to be between 1 and 255 + :param height: barcode height, has to be between 1 and 255 *default*: 64 + :param width: barcode width, has to be between 2 and 6 + *default*: 3 :param pos: where to place the text relative to the barcode, *default*: below * ABOVE @@ -512,6 +526,7 @@ class Escpos(object): Without any arguments the paper will be cut completely. With 'mode=PART' a partial cut will be attempted. Note however, that not all models can do a partial cut. See the documentation of your printer for details. + .. todo:: Check this function on TM-T88II. :param mode: set to 'PART' for a partial cut """ From 713380baf90eed2c1ceda1ced65dc3a76c6a73cc Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 14 Jan 2016 14:04:35 +0100 Subject: [PATCH 44/51] DOC corrected todo in barcode-method --- escpos/escpos.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 26e4911..0c628af 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -321,8 +321,8 @@ class Escpos(object): value does not work. .. todo:: Add a method to check barcode codes. .. todo:: On TM-T88II width from 1 to 6 is accepted. Try to acquire command reference and correct the code. - .. todo:: Supplying pos has no effect in TM-T88II. Printed always below. Verify with other printers and , if - necessary, turn this into an issue. + .. todo:: Supplying pos does not have an effect for every barcode type. Check and document for which types this + is true. :param code: alphanumeric data to be printed as bar code :param bc: barcode format, possible values are: From 248ddf8456b70aecbf26ce6d9ebb8842cc5972b3 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 14 Jan 2016 14:51:26 +0100 Subject: [PATCH 45/51] IMPROVE barcode printing * adds default values to the auxiliary parameters * allows to suppress the centered alignment --- escpos/escpos.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 0c628af..6ec3d7e 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -314,12 +314,11 @@ class Escpos(object): else: raise CharCodeError() - def barcode(self, code, bc, height, width, pos, font): + def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A", align_ct=True): """ Print Barcode - .. todo:: Documentation tells about default values. In fact no default values exist and supplying a default - value does not work. - .. todo:: Add a method to check barcode codes. + .. todo:: Add a method to check barcode codes. Alternatively or as an addition write explanations about each + barcode-type. .. todo:: On TM-T88II width from 1 to 6 is accepted. Try to acquire command reference and correct the code. .. todo:: Supplying pos does not have an effect for every barcode type. Check and document for which types this is true. @@ -338,9 +337,11 @@ class Escpos(object): If none is specified, the method raises :py:exc:`~escpos.exceptions.BarcodeTypeError`. :param height: barcode height, has to be between 1 and 255 *default*: 64 + :type height: int :param width: barcode width, has to be between 2 and 6 *default*: 3 - :param pos: where to place the text relative to the barcode, *default*: below + :type width: int + :param pos: where to place the text relative to the barcode, *default*: BELOW * ABOVE * BELOW @@ -352,12 +353,17 @@ class Escpos(object): * A * B + :param align_ct: If this parameter is True the barcode will be centered. Otherwise no alignment command will be + issued. + :type align_ct: bool + :raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`, :py:exc:`~escpos.exceptions.BarcodeTypeError`, :py:exc:`~escpos.exceptions.BarcodeCodeError` """ # Align Bar Code() - self._raw(TXT_ALIGN_CT) + if align_ct: + self._raw(TXT_ALIGN_CT) # Height if 1 <= height <= 255: self._raw(BARCODE_HEIGHT + chr(height)) From 0c56f5c83151d830ff2b07446e22d6a139d2723e Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 14 Jan 2016 15:50:58 +0100 Subject: [PATCH 46/51] DOC improve docstring in barcode-method --- escpos/escpos.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/escpos/escpos.py b/escpos/escpos.py index 6ec3d7e..b8b8342 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -317,12 +317,28 @@ class Escpos(object): def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A", align_ct=True): """ Print Barcode + This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to + be supported by the unit. Currently you have to check manually whether your barcode text is correct. Uncorrect + barcodes may lead to unexpected printer behaviour. + .. todo:: Add a method to check barcode codes. Alternatively or as an addition write explanations about each - barcode-type. + barcode-type. Research whether the check digits can be computed autmatically. + + Use the parameters `height` and `width` for adjusting of the barcode size. Please take notice that the barcode + will not be printed if it is outside of the printable area. (Which should be impossible with this method, so + this information is probably more useful for debugging purposes.) + .. todo:: On TM-T88II width from 1 to 6 is accepted. Try to acquire command reference and correct the code. .. todo:: Supplying pos does not have an effect for every barcode type. Check and document for which types this is true. + If you do not want to center the barcode you can call the method with `align_ct=False`, which will disable + automatic centering. Please note that when you use center alignment, then the alignment of text will be changed + automatically to centered. You have to manually restore the alignment if necessary. + + .. todo:: If further barcode-types are needed they could be rendered transparently as an image. (This could also + be of help if the printer does not support types that others do.) + :param code: alphanumeric data to be printed as bar code :param bc: barcode format, possible values are: From ae37de25776ebcb775068a9049958383591ea780 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Sun, 17 Jan 2016 16:00:03 +0100 Subject: [PATCH 47/51] DOC improve doc on network printing --- escpos/printer.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/escpos/printer.py b/escpos/printer.py index 7083957..bfdd492 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -139,7 +139,17 @@ class Network(Escpos): """ Network printer This class is used to attach to a networked printer. You can also use this in order to attach to a printer that - is forwarded with `netcat`. + is forwarded with ``socat``. + + If you have a local printer on parallel port ``/dev/usb/lp0`` then you could start ``socat`` with: + + .. code-block:: none + + socat -u TCP4-LISTEN:4242,reuseaddr,fork OPEN:/dev/usb/lp0 + + Then you should be able to attach to port ``4242`` with this class. + Otherwise the normal usecase would be to have a printer with ethernet interface. This type of printer should + work the same with this class. For the address of the printer check its manuals. """ def __init__(self, host, port=9100, *args, **kwargs): @@ -154,7 +164,7 @@ class Network(Escpos): self.open() def open(self): - """ Open TCP socket and set it as escpos device """ + """ Open TCP socket with ``socket``-library and set it as escpos device """ self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.device.connect((self.host, self.port)) From 3e200a86b92b247fdf723e88268146e33f032936 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Wed, 10 Feb 2016 19:32:00 +0100 Subject: [PATCH 48/51] ADD very basic nosetest --- test/test_load_module.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 test/test_load_module.py diff --git a/test/test_load_module.py b/test/test_load_module.py new file mode 100644 index 0000000..455b074 --- /dev/null +++ b/test/test_load_module.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +"""very basic test cases that load the classes + +:author: `Patrick Kanzler `_ and others +:organization: Bashlinux and `python-escpos `_ +:copyright: Copyright (c) 2016 `python-escpos `_ +:license: GNU GPL v3 +""" + +from nose.tools import with_setup + +import escpos.printer as printer +import os + +def setup_testfile(): + """create a testfile as devfile""" + fhandle = open('testfile', 'a') + try: + os.utime('testfile', None) + finally: + fhandle.close() + +def teardown_testfile(): + """destroy testfile again""" + os.remove('testfile') + +@with_setup(setup_testfile, teardown_testfile) +def test_instantiation(): + """test the instantiation of a escpos-printer class and basic printing""" + instance = printer.File(devfile='testfile') + instance.text('This is a test\n') From d672ca12689001ece4f1218d10f311ac739446a4 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Thu, 11 Feb 2016 18:37:13 +0100 Subject: [PATCH 49/51] improve packaging and test environment --- setup.py | 10 +++++----- test/test_load_module.py | 14 ++++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 06b2933..ca26552 100755 --- a/setup.py +++ b/setup.py @@ -38,10 +38,10 @@ class Tox(TestCommand): sys.exit(errno) setup( - name='escpos', - version='1.0.9', - url='https://github.com/manpaz/python-escpos', - download_url='https://github.com/manpaz/python-escpos.git', + name='python-escpos', + version='1.0.9-dev', + url='https://github.com/python-escpos/python-escpos', + download_url='https://github.com/python-escpos/python-escpos/archive/master.zip', description='Python library to manipulate ESC/POS Printers', license='GNU GPL v3', long_description=read('README'), @@ -71,6 +71,6 @@ setup( 'qrcode>=4.0', 'pyserial', ], - tests_require=['tox'], + tests_require=['tox', 'nose'], cmdclass={'test': Tox}, ) diff --git a/test/test_load_module.py b/test/test_load_module.py index 455b074..9603cf1 100644 --- a/test/test_load_module.py +++ b/test/test_load_module.py @@ -1,8 +1,8 @@ #!/usr/bin/python """very basic test cases that load the classes -:author: `Patrick Kanzler `_ and others -:organization: Bashlinux and `python-escpos `_ +:author: `Patrick Kanzler `_ +:organization: `python-escpos `_ :copyright: Copyright (c) 2016 `python-escpos `_ :license: GNU GPL v3 """ @@ -12,20 +12,22 @@ from nose.tools import with_setup import escpos.printer as printer import os +devfile = 'testfile' + def setup_testfile(): """create a testfile as devfile""" - fhandle = open('testfile', 'a') + fhandle = open(devfile, 'a') try: - os.utime('testfile', None) + os.utime(devfile, None) finally: fhandle.close() def teardown_testfile(): """destroy testfile again""" - os.remove('testfile') + os.remove(devfile) @with_setup(setup_testfile, teardown_testfile) def test_instantiation(): """test the instantiation of a escpos-printer class and basic printing""" - instance = printer.File(devfile='testfile') + instance = printer.File(devfile=devfile) instance.text('This is a test\n') From f303a38a8d8634a624cc87b31c7550b5a3da308f Mon Sep 17 00:00:00 2001 From: Patrick Kanzler Date: Tue, 16 Feb 2016 22:55:49 +0100 Subject: [PATCH 50/51] DOC add captions to the toctrees in documentation --- doc/index.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index 031189f..e8d1fc0 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -25,11 +25,12 @@ sending my PayPal info so you can donate. Thank you! -User Documentation ------------------- +Content +------- .. toctree:: :maxdepth: 1 + :caption: User Documentation user/dependencies user/installation @@ -39,12 +40,11 @@ User Documentation user/todo user/usage -API ---- .. toctree:: :maxdepth: 1 - + :caption: API Documentation + api/escpos api/printer api/constants From b7c6edc9e11a7add16492fdd9f7cbaa50db211da Mon Sep 17 00:00:00 2001 From: Dean Rispin Date: Mon, 29 Feb 2016 09:09:11 -0800 Subject: [PATCH 51/51] Add optional timeout variable for printer network connections --- escpos/printer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/escpos/printer.py b/escpos/printer.py index bfdd492..be8aed8 100644 --- a/escpos/printer.py +++ b/escpos/printer.py @@ -152,20 +152,23 @@ class Network(Escpos): work the same with this class. For the address of the printer check its manuals. """ - def __init__(self, host, port=9100, *args, **kwargs): + def __init__(self, host, port=9100, timeout=60, *args, **kwargs): """ :param host : Printer's hostname or IP address :param port : Port to write to + :param timeout : timeout in seconds for the socket-library """ Escpos.__init__(self, *args, **kwargs) self.host = host self.port = port + self.timeout = timeout self.open() def open(self): """ Open TCP socket with ``socket``-library and set it as escpos device """ self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.device.settimeout(self.timeout) self.device.connect((self.host, self.port)) if self.device is None: