diff --git a/escpos/__init__.py b/escpos/__init__.py index 0459ce8..1d72614 100644 --- a/escpos/__init__.py +++ b/escpos/__init__.py @@ -1 +1,5 @@ +import constants +import escpos +import exceptions +import printer __all__ = ["constants", "escpos", "exceptions", "printer"] diff --git a/escpos/cli.py b/escpos/cli.py new file mode 100755 index 0000000..4b52381 --- /dev/null +++ b/escpos/cli.py @@ -0,0 +1,213 @@ +#!/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('{0}\n'.format(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{0}\n'.format(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 : diff --git a/escpos/constants.py b/escpos/constants.py index 67c6e3b..c7e5309 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 + 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 +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' # 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: +# - "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% diff --git a/escpos/escpos.py b/escpos/escpos.py index 3a7a83f..dc8912e 100644 --- a/escpos/escpos.py +++ b/escpos/escpos.py @@ -15,15 +15,16 @@ except ImportError: from PIL import Image import qrcode -import time import textwrap import binascii +import operator from .constants import * from .exceptions import * from abc import ABCMeta, abstractmethod # abstract base class support + class Escpos(object): """ ESC/POS Printer object @@ -62,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 @@ -161,6 +162,44 @@ 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, bandsize=255): + """ 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) + + 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 max_height and im.size[1] > max_height: + im = im.crop((0, 0, im.size[0], max_height)) + + # Divide into bands + current = 0 + while current < im.size[1]: + self.image(im.crop((0, current, width or im.size[0], + min(im.size[1], current + bandsize)))) + current += bandsize + def direct_image(self, image): """ Send image to printer @@ -181,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 @@ -205,7 +244,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() @@ -268,7 +307,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 +322,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,15 +345,15 @@ 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() + raise BarcodeSizeError("height = {height}".format(height=height)) # Width - if width >= 1 or width <= 255: - self._raw(BARCODE_WIDTH) + if 2 <= width <= 6: + self._raw(BARCODE_WIDTH + chr(width)) else: - raise BarcodeSizeError() + raise BarcodeSizeError("width = {width}".format(width=width)) # Font if font.upper() == "B": self._raw(BARCODE_FONT_B) @@ -342,10 +381,10 @@ 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() + raise BarcodeTypeError(bc) # Print Code if code: self._raw(code) @@ -373,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 bb72e24..e91ed5e 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 `_ @@ -45,7 +46,7 @@ class BarcodeTypeError(Error): self.resultcode = 10 def __str__(self): - return "No Barcode type is defined" + return "No Barcode type is defined ({msg})".format(msg=self.msg) class BarcodeSizeError(Error): @@ -61,7 +62,7 @@ class BarcodeSizeError(Error): self.resultcode = 20 def __str__(self): - return "Barcode size is out of range" + return "Barcode size is out of range ({msg})".format(msg=self.msg) class BarcodeCodeError(Error): @@ -151,3 +152,18 @@ 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 1c3f92f..7083957 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 @@ -43,7 +42,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 @@ -57,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 @@ -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 @@ -121,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 @@ -160,14 +159,14 @@ 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 :param msg: arbitrary code to be printed """ - self.device.send(msg) + self.device.sendall(msg) def __del__(self): """ Close TCP connection """ @@ -196,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 """ @@ -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