diff --git a/.gitignore b/.gitignore index 05228da..6edbbd5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ *.py[cod] .DS_Store build +escpos.egg-info +MANIFEST +dist +.idea \ No newline at end of file diff --git a/doge.bmp b/doge.bmp new file mode 100644 index 0000000..5def119 Binary files /dev/null and b/doge.bmp differ diff --git a/escpos/constants.py b/escpos/constants.py index da13f98..5d06a09 100644 --- a/escpos/constants.py +++ b/escpos/constants.py @@ -1,92 +1,93 @@ """ ESC/POS Commands (Constants) """ +import binascii as ba + #{ Control characters # as labelled in http://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf -NUL = '\x00' -EOT = '\x04' -ENQ = '\x05' -DLE = '\x10' -DC4 = '\x14' -CAN = '\x18' -ESC = '\x1b' -FS = '\x1c' -GS = '\x1d' +NUL = ba.hexlify(b'\x00') +EOT = ba.hexlify(b'\x04') +ENQ = ba.hexlify(b'\x05') +DLE = ba.hexlify(b'\x10') +DC4 = ba.hexlify(b'\x14') +CAN = ba.hexlify(b'\x18') +ESC = ba.hexlify(b'\x1b') +FS = ba.hexlify(b'\x1c') +GS = ba.hexlify(b'\x1d') #{ Feed control sequences -CTL_LF = '\n' # Print and line feed -CTL_FF = '\f' # Form feed -CTL_CR = '\r' # Carriage return -CTL_HT = '\t' # Horizontal tab -CTL_VT = '\v' # Vertical tab +CTL_LF = ba.hexlify(b'\n') # Print and line feed +CTL_FF = ba.hexlify(b'\f') # Form feed +CTL_CR = ba.hexlify(b'\r') # Carriage return +CTL_HT = ba.hexlify(b'\t') # Horizontal tab +CTL_VT = ba.hexlify(b'\v') # Vertical tab #{ Printer hardware -HW_INIT = ESC + '@' # Clear data in buffer and reset modes -HW_SELECT = ESC + '=\x01' # Printer select - -HW_RESET = ESC + '\x3f\x0a\x00' # Reset printer hardware - # (TODO: Where is this specified?) +HW_INIT = ESC + ba.hexlify(b'@') # Clear data in buffer and reset modes +HW_SELECT = ESC + ba.hexlify(b'=\x01') # Printer select +HW_RESET = ESC + ba.hexlify(b'\x3f\x0a\x00') # Reset printer hardware + # (TODO: Where is this specified?) #{ 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 [] +_CASH_DRAWER = lambda m, t1='', t2='': ESC + ba.hexlify(b'p' + m + chr(t1) + chr(t2)) +CD_KICK_2 = _CASH_DRAWER(b'\x00', 50, 50) # Sends a pulse to pin 2 [] +CD_KICK_5 = _CASH_DRAWER(b'\x01', 50, 50) # 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 +_CUT_PAPER = lambda m: GS + ba.hexlify(b'V' + m) +PAPER_FULL_CUT = _CUT_PAPER(b'\x00') # Full cut paper +PAPER_PART_CUT = _CUT_PAPER(b'\x01') # Partial cut paper #{ Text format # 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 +TXT_NORMAL = ESC + ba.hexlify(b'!\x00') # Normal text +TXT_2HEIGHT = ESC + ba.hexlify(b'!\x10') # Double height text +TXT_2WIDTH = ESC + ba.hexlify(b'!\x20') # Double width text +TXT_4SQUARE = ESC + ba.hexlify(b'!\x30') # Quad area text +TXT_UNDERL_OFF = ESC + ba.hexlify(b'\x2d\x00') # Underline font OFF +TXT_UNDERL_ON = ESC + ba.hexlify(b'\x2d\x01') # Underline font 1-dot ON +TXT_UNDERL2_ON = ESC + ba.hexlify(b'\x2d\x02') # Underline font 2-dot ON +TXT_BOLD_OFF = ESC + ba.hexlify(b'\x45\x00') # Bold font OFF +TXT_BOLD_ON = ESC + ba.hexlify(b'\x45\x01') # Bold font ON +TXT_FONT_A = ESC + ba.hexlify(b'\x4d\x00') # Font type A +TXT_FONT_B = ESC + ba.hexlify(b'\x4d\x01') # Font type B +TXT_ALIGN_LT = ESC + ba.hexlify(b'\x61\x00') # Left justification +TXT_ALIGN_CT = ESC + ba.hexlify(b'\x61\x01') # Centering +TXT_ALIGN_RT = ESC + ba.hexlify(b'\x61\x02') # Right justification #{ Barcode format -_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_BARCODE_TXT_POS = lambda n: GS + ba.hexlify(b'H' + n) +BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS(b'\x00') # HRI barcode chars OFF +BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS(b'\x01') # HRI barcode chars above +BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS(b'\x02') # HRI barcode chars below +BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS(b'\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 +_SET_HRI_FONT = lambda n: GS + b'f' + n +BARCODE_FONT_A = _SET_HRI_FONT(b'\x00') # Font type A for HRI barcode chars +BARCODE_FONT_B = _SET_HRI_FONT(b'\x01') # Font type B for HRI barcode chars -BARCODE_HEIGHT = GS + 'h' # Barcode Height [1-255] -BARCODE_WIDTH = GS + 'w' # Barcode Width [2-6] +BARCODE_HEIGHT = GS + ba.hexlify(b'h') # Barcode Height [1-255] +BARCODE_WIDTH = GS + ba.hexlify(b'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: # - "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 +_SET_BARCODE_TYPE = lambda m: GS + ba.hexlify(b'k' + m) +BARCODE_UPC_A = _SET_BARCODE_TYPE(b'\x00') # Barcode type UPC-A +BARCODE_UPC_E = _SET_BARCODE_TYPE(b'\x01') # Barcode type UPC-E +BARCODE_EAN13 = _SET_BARCODE_TYPE(b'\x02') # Barcode type EAN13 +BARCODE_EAN8 = _SET_BARCODE_TYPE(b'\x03') # Barcode type EAN8 +BARCODE_CODE39 = _SET_BARCODE_TYPE(b'\x04') # Barcode type CODE39 +BARCODE_ITF = _SET_BARCODE_TYPE(b'\x05') # Barcode type ITF +BARCODE_NW7 = _SET_BARCODE_TYPE(b'\x06') # Barcode type NW7 #{ Image format # 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 +_PRINT_RASTER_IMG = lambda data: GS + b'v0' + data +S_RASTER_N = _PRINT_RASTER_IMG(b'\x00') # Set raster image normal size +S_RASTER_2W = _PRINT_RASTER_IMG(b'\x01') # Set raster image double width +S_RASTER_2H = _PRINT_RASTER_IMG(b'\x02') # Set raster image double height +S_RASTER_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple diff --git a/escpos/escpos.py b/escpos/escpos.py index 9f06bc8..78ff338 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -5,21 +5,21 @@ @copyright: Copyright (c) 2012 Bashlinux @license: GPL ''' - +import struct import os import time import qrcode import operator -from PIL import Image +from PIL import Image, ImageOps -from constants import * -from exceptions import * +from escpos.utils import * +from escpos.constants import * +from escpos.exceptions import * -class Escpos: +class Escpos(object): """ ESC/POS Printer object """ device = None - def _check_image_size(self, size): """ Check and fix the size of the image to 32 bits """ if size % 32 == 0: @@ -27,31 +27,20 @@ class Escpos: 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): + def _print_image(self, imagedata, n_rows, col_bytes): """ 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], 0) - self._raw(buffer.decode('hex')) - buffer = "" - - while i < len(line): - hex_string = int(line[i:i+8],2) - buffer += "%02X" % hex_string - i += 8 - cont += 1 - if cont % 4 == 0: - self._raw(buffer.decode("hex")) - buffer = "" - cont = 0 + buffer = struct.pack(' 512: - print "WARNING: Image is wider than 512 and could be truncated at print time " - if im.size[1] > 255: + im = im.convert("L") + im = ImageOps.invert(im) + im = im.convert("1") + + if im.size[0] > 640: + print("WARNING: Image is wider than 640 and could be truncated at print time ") + if im.size[1] > 640: raise ImageSizeError() + + orig_width, height = im.size + width = ((orig_width + 31) // 32) * 32 + new_image = Image.new("1", (width, height)) + new_image.paste(im, (0, 0, orig_width, height)) - im_border = self._check_image_size(im.size[0]) - for i in range(im_border[0]): - im_left += "0" - for i in range(im_border[1]): - im_right += "0" - - for y in range(im.size[1]): - img_size[1] += 1 - pix_line += im_left - img_size[0] += im_border[0] - for x in range(im.size[0]): - img_size[0] += 1 - RGB = im.getpixel((x, y)) - im_color = (RGB[0] + RGB[1] + RGB[2]) - im_pattern = "1X0" - pattern_len = len(im_pattern) - switch = (switch - 1 ) * (-1) - for x in range(pattern_len): - if im_color <= (255 * 3 / pattern_len * (x+1)): - if im_pattern[x] == "X": - pix_line += "%d" % switch - else: - pix_line += im_pattern[x] - break - elif im_color > (255 * 3 / pattern_len * pattern_len) and im_color <= (255 * 3): - pix_line += im_pattern[-1] - break - pix_line += im_right - img_size[0] += im_border[1] - - self._print_image(pix_line, img_size) + the_bytes = new_image.tobytes() + self._print_image(the_bytes, n_rows=height, col_bytes=width//8) def qr(self, text): """ Print QR Code for the provided string """ @@ -150,9 +116,14 @@ class Escpos: qr_code.make(fit=True) qr_img = qr_code.make_image() # Convert the RGB image in printable image - im = qr_img._img.convert("RGB") - self.text('\n') - self.set(align='center') + im = qr_img._img.convert("1") + width = im.size[0] + height = im.size[1] + while width * 2 <= 640: + width *= 2 + height *= 2 + + im = im.resize((width, height)) self.image(im) self.text('\n') @@ -207,7 +178,6 @@ class Escpos: else: raise exception.BarcodeCodeError() - def text(self, txt): """ Print alpha-numeric text """ if txt: @@ -215,7 +185,6 @@ class Escpos: else: raise TextError() - def set(self, align='left', font='a', type='normal', width=1, height=1): """ Set text properties """ # Width @@ -273,7 +242,6 @@ class Escpos: else: # DEFAULT MODE: FULL CUT self._raw(PAPER_FULL_CUT) - def cashdraw(self, pin): """ Send pulse to kick the cash drawer """ if pin == 2: @@ -283,7 +251,6 @@ class Escpos: else: raise CashDrawerError() - def hw(self, hw): """ Hardware operations """ if hw.upper() == "INIT": @@ -295,7 +262,6 @@ class Escpos: else: # DEFAULT: DOES NOTHING pass - def control(self, ctl): """ Feed control sequences """ if ctl.upper() == "LF": diff --git a/escpos/printer.py b/escpos/printer.py index 73f624b..37a0e33 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.escpos import * +from escpos.constants import * +from escpos.exceptions import * class Usb(Escpos): """ Define USB printer """ @@ -44,13 +44,13 @@ class Usb(Escpos): 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): @@ -150,10 +150,10 @@ class File(Escpos): def open(self): """ Open system file """ - self.device = open(self.devfile, "wb") + 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): diff --git a/escpos/utils.py b/escpos/utils.py new file mode 100644 index 0000000..de6ca7c --- /dev/null +++ b/escpos/utils.py @@ -0,0 +1,9 @@ +try: + bytes.fromhex + def hex2bytes(hex_string): + return bytes.fromhex(hex_string) + +except: + def hex2bytes(hex_string): + return hex_string.decode('hex') + diff --git a/setup.py b/setup.py index 27197d0..b00a77e 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ #!/usr/bin/python +from setuptools import setup from distutils.core import setup setup(