Merge pull request #78 from python-escpos/davisgoglin-merge-rebase

Davisgoglin merge rebase
This commit is contained in:
Patrick Kanzler 2016-01-13 15:41:21 +01:00
commit 198b50ac5e
7 changed files with 426 additions and 113 deletions

View File

@ -1 +1,5 @@
import constants
import escpos
import exceptions
import printer
__all__ = ["constants", "escpos", "exceptions", "printer"] __all__ = ["constants", "escpos", "exceptions", "printer"]

213
escpos/cli.py Executable file
View File

@ -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<type>\S+)\s(?P<data>[0-9X]+)$')
def echo(args): # pylint: disable=unused-argument
"""TTY-like line-by-line keyboard-to-printer echo loop."""
try:
while True:
line = raw_input()
match = re_barcode_escape.match(line)
if match and match.group('type') in KNOWN_BARCODE_TYPES:
bctype, data = match.groups()
epson.barcode(data, bctype, 48, 2, '', '')
epson.set(align='left')
else:
epson.text('{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 :

View File

@ -10,87 +10,127 @@ moved to `capabilities` as in `escpos-php by @mike42 <https://github.com/mike42/
:license: GNU GPL v3 :license: GNU GPL v3
""" """
# Control characters
# as labelled in http://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf
NUL = '\x00'
EOT = '\x04'
ENQ = '\x05'
DLE = '\x10'
DC4 = '\x14'
CAN = '\x18'
ESC = '\x1b'
FS = '\x1c'
GS = '\x1d'
# Feed control sequences # Feed control sequences
CTL_LF = '\x0a' # Print and line feed CTL_LF = '\n' # Print and line feed
CTL_FF = '\x0c' # Form feed CTL_FF = '\f' # Form feed
CTL_CR = '\x0d' # Carriage return CTL_CR = '\r' # Carriage return
CTL_HT = '\x09' # Horizontal tab CTL_HT = '\t' # Horizontal tab
CTL_SET_HT = '\x1b\x44' # Set horizontal tab positions CTL_SET_HT = ESC + '\x44' # Set horizontal tab positions
CTL_VT = '\x1b\x64\x04' # Vertical tab CTL_VT = '\v' # Vertical tab
# Printer hardware # Printer hardware
HW_INIT = '\x1b\x40' # Clear data in buffer and reset modes HW_INIT = ESC + '@' # Clear data in buffer and reset modes
HW_SELECT = '\x1b\x3d\x01' # Printer select HW_SELECT = ESC + '=\x01' # Printer select
HW_RESET = '\x1b\x3f\x0a\x00' # Reset printer hardware
# Cash Drawer HW_RESET = ESC + '\x3f\x0a\x00' # Reset printer hardware
CD_KICK_2 = '\x1b\x70\x00' # Sends a pulse to pin 2 [] # (TODO: Where is this specified?)
CD_KICK_5 = '\x1b\x70\x01' # Sends a pulse to pin 5 []
# Paper #{ Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>)
PAPER_FULL_CUT = '\x1d\x56\x00' # Full cut paper _CASH_DRAWER = lambda m, t1='', t2='': ESC + 'p' + m + chr(t1) + chr(t2)
PAPER_PART_CUT = '\x1d\x56\x01' # Partial cut paper 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 # Text format
TXT_NORMAL = '\x1b\x21\x00' # Normal text # TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll
TXT_2HEIGHT = '\x1b\x21\x10' # Double height text # Printers" and tidy up this stuff too.
TXT_2WIDTH = '\x1b\x21\x20' # Double width text TXT_NORMAL = ESC + '!\x00' # Normal text
TXT_4SQUARE = '\x1b\x21\x30' # Quad area text TXT_2HEIGHT = ESC + '!\x10' # Double height text
TXT_UNDERL_OFF = '\x1b\x2d\x00' # Underline font OFF TXT_2WIDTH = ESC + '!\x20' # Double width text
TXT_UNDERL_ON = '\x1b\x2d\x01' # Underline font 1-dot ON TXT_4SQUARE = ESC + '!\x30' # Quad area text
TXT_UNDERL2_ON = '\x1b\x2d\x02' # Underline font 2-dot ON TXT_UNDERL_OFF = ESC + '\x2d\x00' # Underline font OFF
TXT_BOLD_OFF = '\x1b\x45\x00' # Bold font OFF TXT_UNDERL_ON = ESC + '\x2d\x01' # Underline font 1-dot ON
TXT_BOLD_ON = '\x1b\x45\x01' # Bold font ON TXT_UNDERL2_ON = ESC + '\x2d\x02' # Underline font 2-dot ON
TXT_FONT_A = '\x1b\x4d\x00' # Font type A TXT_BOLD_OFF = ESC + '\x45\x00' # Bold font OFF
TXT_FONT_B = '\x1b\x4d\x01' # Font type B TXT_BOLD_ON = ESC + '\x45\x01' # Bold font ON
TXT_ALIGN_LT = '\x1b\x61\x00' # Left justification TXT_FONT_A = ESC + '\x4d\x00' # Font type A
TXT_ALIGN_CT = '\x1b\x61\x01' # Centering TXT_FONT_B = ESC + '\x4d\x01' # Font type B
TXT_ALIGN_RT = '\x1b\x61\x02' # Right justification 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 # Char code table
CHARCODE_PC437 = '\x1b\x74\x00' # USA: Standard Europe CHARCODE_PC437 = ESC + '\x74\x00' # USA: Standard Europe
CHARCODE_JIS = '\x1b\x74\x01' # Japanese Katakana CHARCODE_JIS = ESC + '\x74\x01' # Japanese Katakana
CHARCODE_PC850 = '\x1b\x74\x02' # Multilingual CHARCODE_PC850 = ESC + '\x74\x02' # Multilingual
CHARCODE_PC860 = '\x1b\x74\x03' # Portuguese CHARCODE_PC860 = ESC + '\x74\x03' # Portuguese
CHARCODE_PC863 = '\x1b\x74\x04' # Canadian-French CHARCODE_PC863 = ESC + '\x74\x04' # Canadian-French
CHARCODE_PC865 = '\x1b\x74\x05' # Nordic CHARCODE_PC865 = ESC + '\x74\x05' # Nordic
CHARCODE_WEU = '\x1b\x74\x06' # Simplified Kanji, Hirakana CHARCODE_WEU = ESC + '\x74\x06' # Simplified Kanji, Hirakana
CHARCODE_GREEK = '\x1b\x74\x07' # Simplified Kanji CHARCODE_GREEK = ESC + '\x74\x07' # Simplified Kanji
CHARCODE_HEBREW = '\x1b\x74\x08' # Simplified Kanji CHARCODE_HEBREW = ESC + '\x74\x08' # Simplified Kanji
CHARCODE_PC1252 = '\x1b\x74\x11' # Western European Windows Code Set CHARCODE_PC1252 = ESC + '\x74\x11' # Western European Windows Code Set
CHARCODE_PC866 = '\x1b\x74\x12' # Cirillic #2 CHARCODE_PC866 = ESC + '\x74\x12' # Cirillic #2
CHARCODE_PC852 = '\x1b\x74\x13' # Latin 2 CHARCODE_PC852 = ESC + '\x74\x13' # Latin 2
CHARCODE_PC858 = '\x1b\x74\x14' # Euro CHARCODE_PC858 = ESC + '\x74\x14' # Euro
CHARCODE_THAI42 = '\x1b\x74\x15' # Thai character code 42 CHARCODE_THAI42 = ESC + '\x74\x15' # Thai character code 42
CHARCODE_THAI11 = '\x1b\x74\x16' # Thai character code 11 CHARCODE_THAI11 = ESC + '\x74\x16' # Thai character code 11
CHARCODE_THAI13 = '\x1b\x74\x17' # Thai character code 13 CHARCODE_THAI13 = ESC + '\x74\x17' # Thai character code 13
CHARCODE_THAI14 = '\x1b\x74\x18' # Thai character code 14 CHARCODE_THAI14 = ESC + '\x74\x18' # Thai character code 14
CHARCODE_THAI16 = '\x1b\x74\x19' # Thai character code 16 CHARCODE_THAI16 = ESC + '\x74\x19' # Thai character code 16
CHARCODE_THAI17 = '\x1b\x74\x1a' # Thai character code 17 CHARCODE_THAI17 = ESC + '\x74\x1a' # Thai character code 17
CHARCODE_THAI18 = '\x1b\x74\x1b' # Thai character code 18 CHARCODE_THAI18 = ESC + '\x74\x1b' # Thai character code 18
# Barcode format # Barcode format
BARCODE_TXT_OFF = '\x1d\x48\x00' # HRI barcode chars OFF _SET_BARCODE_TXT_POS = lambda n: GS + 'H' + n
BARCODE_TXT_ABV = '\x1d\x48\x01' # HRI barcode chars above BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS('\x00') # HRI barcode chars OFF
BARCODE_TXT_BLW = '\x1d\x48\x02' # HRI barcode chars below BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS('\x01') # HRI barcode chars above
BARCODE_TXT_BTH = '\x1d\x48\x03' # HRI barcode chars both above and below BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS('\x02') # HRI barcode chars below
BARCODE_FONT_A = '\x1d\x66\x00' # Font type A for HRI barcode chars BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS('\x03') # HRI both above and below
BARCODE_FONT_B = '\x1d\x66\x01' # Font type B for HRI barcode chars
BARCODE_HEIGHT = '\x1d\x68\x64' # Barcode Height [1-255] _SET_HRI_FONT = lambda n: GS + 'f' + n
BARCODE_WIDTH = '\x1d\x77\x03' # Barcode Width [2-6] BARCODE_FONT_A = _SET_HRI_FONT('\x00') # Font type A for HRI barcode chars
BARCODE_UPC_A = '\x1d\x6b\x00' # Barcode type UPC-A BARCODE_FONT_B = _SET_HRI_FONT('\x01') # Font type B for HRI barcode chars
BARCODE_UPC_E = '\x1d\x6b\x01' # Barcode type UPC-E
BARCODE_EAN13 = '\x1d\x6b\x02' # Barcode type EAN13 BARCODE_HEIGHT = GS + 'h' # Barcode Height [1-255]
BARCODE_EAN8 = '\x1d\x6b\x03' # Barcode type EAN8 BARCODE_WIDTH = GS + 'w' # Barcode Width [2-6]
BARCODE_CODE39 = '\x1d\x6b\x04' # Barcode type CODE39
BARCODE_ITF = '\x1d\x6b\x05' # Barcode type ITF #NOTE: This isn't actually an ESC/POS command. It's the common prefix to the
BARCODE_NW7 = '\x1d\x6b\x06' # Barcode type NW7 # two "print bar code" commands:
# - "GS k <type as integer> <data> NUL"
# - "GS k <type as letter> <data length> <data>"
# 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 # Image format
S_RASTER_N = '\x1d\x76\x30\x00' # Set raster image normal size # NOTE: _PRINT_RASTER_IMG is the obsolete ESC/POS "print raster bit image"
S_RASTER_2W = '\x1d\x76\x30\x01' # Set raster image double width # command. The constants include a fragment of the data's header.
S_RASTER_2H = '\x1d\x76\x30\x02' # Set raster image double height _PRINT_RASTER_IMG = lambda data: GS + 'v0' + data
S_RASTER_Q = '\x1d\x76\x30\x03' # Set raster image quadruple 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 # Printing Density
PD_N50 = '\x1d\x7c\x00' # Printing Density -50% PD_N50 = GS + '\x7c\x00' # Printing Density -50%
PD_N37 = '\x1d\x7c\x01' # Printing Density -37.5% PD_N37 = GS + '\x7c\x01' # Printing Density -37.5%
PD_N25 = '\x1d\x7c\x02' # Printing Density -25% PD_N25 = GS + '\x7c\x02' # Printing Density -25%
PD_N12 = '\x1d\x7c\x03' # Printing Density -12.5% PD_N12 = GS + '\x7c\x03' # Printing Density -12.5%
PD_0 = '\x1d\x7c\x04' # Printing Density 0% PD_0 = GS + '\x7c\x04' # Printing Density 0%
PD_P50 = '\x1d\x7c\x08' # Printing Density +50% PD_P50 = GS + '\x7c\x08' # Printing Density +50%
PD_P37 = '\x1d\x7c\x07' # Printing Density +37.5% PD_P37 = GS + '\x7c\x07' # Printing Density +37.5%
PD_P25 = '\x1d\x7c\x06' # Printing Density +25% PD_P25 = GS + '\x7c\x06' # Printing Density +25%
PD_P12 = '\x1d\x7c\x05' # Printing Density +12.5% PD_P12 = GS + '\x7c\x05' # Printing Density +12.5%

View File

@ -15,15 +15,16 @@ except ImportError:
from PIL import Image from PIL import Image
import qrcode import qrcode
import time
import textwrap import textwrap
import binascii import binascii
import operator
from .constants import * from .constants import *
from .exceptions import * from .exceptions import *
from abc import ABCMeta, abstractmethod # abstract base class support from abc import ABCMeta, abstractmethod # abstract base class support
class Escpos(object): class Escpos(object):
""" ESC/POS Printer object """ ESC/POS Printer object
@ -62,9 +63,9 @@ class Escpos(object):
else: else:
image_border = 32 - (size % 32) image_border = 32 - (size % 32)
if (image_border % 2) == 0: if (image_border % 2) == 0:
return (round(image_border / 2), round(image_border / 2)) return round(image_border / 2), round(image_border / 2)
else: 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): def _print_image(self, line, size):
""" Print formatted image """ Print formatted image
@ -161,6 +162,44 @@ class Escpos(object):
# Convert the RGB image in printable image # Convert the RGB image in printable image
self._convert_image(im) 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): def direct_image(self, image):
""" Send image to printer """ Send image to printer
@ -181,16 +220,16 @@ class Escpos(object):
#self._raw(binascii.unhexlify(buf)) #self._raw(binascii.unhexlify(buf))
for y in range(height): for y in range(height):
for x in range(width): for x in range(width):
value = image.getpixel((x,y)) value = image.getpixel((x, y))
value = (value << 8) | value; value |= (value << 8)
if value == 0: if value == 0:
temp |= mask temp |= mask
mask = mask >> 1 mask >>= 1
i += 1 i += 1
if i == 8: if i == 8:
buf += ("%02X" % temp) buf += ("%02X" % temp)
mask = 0x80 mask = 0x80
i = 0 i = 0
temp = 0 temp = 0
@ -205,7 +244,7 @@ class Escpos(object):
:param text: text to generate a QR-Code from :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.add_data(text)
qr_code.make(fit=True) qr_code.make(fit=True)
qr_img = qr_code.make_image() qr_img = qr_code.make_image()
@ -268,7 +307,7 @@ class Escpos(object):
else: else:
raise CharCodeError() raise CharCodeError()
def barcode(self, code, bc, width, height, pos, font): def barcode(self, code, bc, height, width, pos, font):
""" Print Barcode """ Print Barcode
:param code: alphanumeric data to be printed as bar code :param code: alphanumeric data to be printed as bar code
@ -283,10 +322,10 @@ class Escpos(object):
* NW7 * NW7
If none is specified, the method raises :py:exc:`~escpos.exceptions.BarcodeTypeError`. 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 height: barcode height, has to be between 2 and 6
*default*: 3 *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 :param pos: where to place the text relative to the barcode, *default*: below
* ABOVE * ABOVE
@ -306,15 +345,15 @@ class Escpos(object):
# Align Bar Code() # Align Bar Code()
self._raw(TXT_ALIGN_CT) self._raw(TXT_ALIGN_CT)
# Height # Height
if height >= 2 or height <= 6: if 1 <= height <= 255:
self._raw(BARCODE_HEIGHT) self._raw(BARCODE_HEIGHT + chr(height))
else: else:
raise BarcodeSizeError() raise BarcodeSizeError("height = {height}".format(height=height))
# Width # Width
if width >= 1 or width <= 255: if 2 <= width <= 6:
self._raw(BARCODE_WIDTH) self._raw(BARCODE_WIDTH + chr(width))
else: else:
raise BarcodeSizeError() raise BarcodeSizeError("width = {width}".format(width=width))
# Font # Font
if font.upper() == "B": if font.upper() == "B":
self._raw(BARCODE_FONT_B) self._raw(BARCODE_FONT_B)
@ -342,10 +381,10 @@ class Escpos(object):
self._raw(BARCODE_CODE39) self._raw(BARCODE_CODE39)
elif bc.upper() == "ITF": elif bc.upper() == "ITF":
self._raw(BARCODE_ITF) self._raw(BARCODE_ITF)
elif bc.upper() == "NW7": elif bc.upper() in ("NW7", "CODABAR"):
self._raw(BARCODE_NW7) self._raw(BARCODE_NW7)
else: else:
raise BarcodeTypeError() raise BarcodeTypeError(bc)
# Print Code # Print Code
if code: if code:
self._raw(code) self._raw(code)
@ -373,7 +412,7 @@ class Escpos(object):
:param columns: amount of columns :param columns: amount of columns
:return: None :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)) self.text(textwrap.fill(txt, colCount))
def set(self, align='left', font='a', text_type='normal', width=1, height=1, density=9): def set(self, align='left', font='a', text_type='normal', width=1, height=1, density=9):

View File

@ -11,6 +11,7 @@ Result/Exit codes:
- `60` = Invalid pin to send Cash Drawer pulse :py:exc:`~escpos.exceptions.CashDrawerError` - `60` = Invalid pin to send Cash Drawer pulse :py:exc:`~escpos.exceptions.CashDrawerError`
- `70` = Invalid number of tab positions :py:exc:`~escpos.exceptions.TabPosError` - `70` = Invalid number of tab positions :py:exc:`~escpos.exceptions.TabPosError`
- `80` = Invalid char code :py:exc:`~escpos.exceptions.CharCodeError` - `80` = Invalid char code :py:exc:`~escpos.exceptions.CharCodeError`
- `90` = USB device not found :py:exc:`~escpos.exceptions.USBNotFoundError`
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others :author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_ :organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
@ -45,7 +46,7 @@ class BarcodeTypeError(Error):
self.resultcode = 10 self.resultcode = 10
def __str__(self): def __str__(self):
return "No Barcode type is defined" return "No Barcode type is defined ({msg})".format(msg=self.msg)
class BarcodeSizeError(Error): class BarcodeSizeError(Error):
@ -61,7 +62,7 @@ class BarcodeSizeError(Error):
self.resultcode = 20 self.resultcode = 20
def __str__(self): 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): class BarcodeCodeError(Error):
@ -151,3 +152,18 @@ class CharCodeError(Error):
def __str__(self): def __str__(self):
return "Valid char code must be set" 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"

View File

@ -13,7 +13,6 @@ import serial
import socket import socket
from .escpos import * from .escpos import *
from .constants import *
from .exceptions import * from .exceptions import *
@ -32,7 +31,7 @@ class Usb(Escpos):
:param out_ep: Output end point :param out_ep: Output end point
""" """
Escpos.__init__(self, *args, **kwargs) Escpos.__init__(self, *args, **kwargs)
self.idVendor = idVendor self.idVendor = idVendor
self.idProduct = idProduct self.idProduct = idProduct
self.interface = interface self.interface = interface
self.in_ep = in_ep self.in_ep = in_ep
@ -43,7 +42,7 @@ class Usb(Escpos):
""" Search device on USB tree and set it 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) self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct)
if self.device is None: if self.device is None:
print("Cable isn't plugged in") raise USBNotFoundError("Device not found or cable not plugged in.")
check_driver = None check_driver = None
@ -57,13 +56,13 @@ class Usb(Escpos):
self.device.detach_kernel_driver(0) self.device.detach_kernel_driver(0)
except usb.core.USBError as e: except usb.core.USBError as e:
if check_driver is not None: 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: try:
self.device.set_configuration() self.device.set_configuration()
self.device.reset() self.device.reset()
except usb.core.USBError as e: 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): def _raw(self, msg):
""" Print any command sent in raw format """ Print any command sent in raw format
@ -100,10 +99,10 @@ class Serial(Escpos):
:param dsrdtr: Hardware flow control (False to enable RTS/CTS) :param dsrdtr: Hardware flow control (False to enable RTS/CTS)
""" """
Escpos.__init__(self, *args, **kwargs) Escpos.__init__(self, *args, **kwargs)
self.devfile = devfile self.devfile = devfile
self.baudrate = baudrate self.baudrate = baudrate
self.bytesize = bytesize self.bytesize = bytesize
self.timeout = timeout self.timeout = timeout
self.parity = parity self.parity = parity
self.stopbits = stopbits self.stopbits = stopbits
self.xonxoff = xonxoff self.xonxoff = xonxoff
@ -121,7 +120,7 @@ class Serial(Escpos):
if self.device is not None: if self.device is not None:
print("Serial printer enabled") print("Serial printer enabled")
else: 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): def _raw(self, msg):
""" Print any command sent in raw format """ Print any command sent in raw format
@ -160,14 +159,14 @@ class Network(Escpos):
self.device.connect((self.host, self.port)) self.device.connect((self.host, self.port))
if self.device is None: 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): 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 :param msg: arbitrary code to be printed
""" """
self.device.send(msg) self.device.sendall(msg)
def __del__(self): def __del__(self):
""" Close TCP connection """ """ Close TCP connection """
@ -196,7 +195,7 @@ class File(Escpos):
self.device = open(self.devfile, "wb") self.device = open(self.devfile, "wb")
if self.device is None: 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): def flush(self):
""" Flush printing content """ """ Flush printing content """
@ -208,9 +207,9 @@ class File(Escpos):
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
""" """
if type(msg) is str: if type(msg) is str:
self.device.write(msg.encode()); self.device.write(msg.encode())
else: else:
self.device.write(msg); self.device.write(msg)
def __del__(self): def __del__(self):
""" Close system file """ """ Close system file """

View File

@ -5,10 +5,12 @@ import sys
from setuptools import setup from setuptools import setup
from setuptools.command.test import test as TestCommand from setuptools.command.test import test as TestCommand
def read(fname): def read(fname):
"""read file from same path as setup.py""" """read file from same path as setup.py"""
return open(os.path.join(os.path.dirname(__file__), fname)).read() return open(os.path.join(os.path.dirname(__file__), fname)).read()
class Tox(TestCommand): class Tox(TestCommand):
"""proxy class that enables tox to be run with setup.py test""" """proxy class that enables tox to be run with setup.py test"""
user_options = [('tox-args=', 'a', "Arguments to pass to tox")] user_options = [('tox-args=', 'a', "Arguments to pass to tox")]
@ -26,7 +28,7 @@ class Tox(TestCommand):
def run_tests(self): def run_tests(self):
"""run tox and pass on user-options""" """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 tox
import shlex import shlex
args = self.tox_args args = self.tox_args