Merge b7c6edc9e11a7add16492fdd9f7cbaa50db211da into 331fe6a93a300a6bdc03dc28d7c6faec23d99d1c

This commit is contained in:
Loafdude 2016-02-29 18:01:29 +00:00
commit 05112e725b
23 changed files with 1080 additions and 330 deletions

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
# python temporary files
*.pyc
# editor autosaves, data and file browser files
$~
.idea/
.directory
# temporary data
temp
# packaging and testing
.tox/
*.egg-info/

View File

@ -1,10 +0,0 @@
# python temporary files
syntax: glob
*.pyc
# editor autosaves
$~
# temporary data
syntax: regexp
temp

8
.travis.yml Normal file
View File

@ -0,0 +1,8 @@
language: python
sudo: false
cache: pip
before_install:
- pip install tox
# command to run tests
script:
- tox

View File

@ -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:

10
doc/api/constants.rst Normal file
View File

@ -0,0 +1,10 @@
Constants
---------
Module :py:mod:`escpos.constants`
.. automodule:: escpos.constants
:members:
:inherited-members:
:undoc-members:
:show-inheritance:
:member-order: bysource

10
doc/api/escpos.rst Normal file
View File

@ -0,0 +1,10 @@
Esc/Pos
-------
Module :py:mod:`escpos.escpos`
.. automodule:: escpos.escpos
:members:
:inherited-members:
:undoc-members:
:show-inheritance:
:member-order: bysource

9
doc/api/exceptions.rst Normal file
View File

@ -0,0 +1,9 @@
Exceptions
----------
Module :py:mod:`escpos.exceptions`
.. automodule:: escpos.exceptions
:members:
:inherited-members:
:show-inheritance:
:member-order: bysource

9
doc/api/printer.rst Normal file
View File

@ -0,0 +1,9 @@
Printer implementations
-----------------------
Module :py:mod:`escpos.printer`
.. automodule:: escpos.printer
:members:
:undoc-members:
:show-inheritance:
:member-order: bysource

View File

@ -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']

View File

@ -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,13 +40,15 @@ User Documentation:
user/todo
user/usage
API:
----
.. toctree::
:maxdepth: 1
api
:caption: API Documentation
api/escpos
api/printer
api/constants
api/exceptions
Indices and tables
==================

View File

@ -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
------------

View File

@ -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
------------

View File

@ -21,6 +21,7 @@ Testing
~~~~~~~
* Test on many printers as possible (USB, Serial, Network)
* automate testing
Design
~~~~~~
@ -32,4 +33,11 @@ Design
* Windows compatibility (hidapi instead libusb?)
* PDF417 support
* use something similar to the `capabilities` in escpos-php
Todos in the codebase
~~~~~~~~~~~~~~~~~~~~~
.. todolist::

View File

@ -1 +1,5 @@
__all__ = ["constants","escpos","exceptions","printer"]
import constants
import escpos
import exceptions
import 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

@ -1,86 +1,136 @@
""" 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 <https://github.com/mike42/escpos-php>`_.
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012 Bashlinux
: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
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 = '\n' # Print and line feed
CTL_FF = '\f' # Form feed
CTL_CR = '\r' # Carriage return
CTL_HT = '\t' # Horizontal tab
CTL_SET_HT = ESC + '\x44' # Set horizontal tab positions
CTL_VT = '\v' # 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
# 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 []
# 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
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?)
#{ Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>)
_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
# 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\x10' # 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
# 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
_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 <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
# 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%

View File

@ -1,9 +1,12 @@
#!/usr/bin/python
"""
@author: Manuel F Martinez <manpaz@bashlinux.com>
@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 <manpaz@bashlinux.com>`_ and others
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012 Bashlinux
:license: GNU GPL v3
"""
try:
@ -12,62 +15,98 @@ except ImportError:
from PIL import Image
import qrcode
import time
import textwrap
import binascii
import operator
from constants import *
from exceptions import *
from .constants import *
from .exceptions import *
class Escpos:
""" ESC/POS Printer object """
device = None
from abc import ABCMeta, abstractmethod # abstract base class support
def _check_image_size(self, size):
""" Check and fix the size of the image to 32 bits """
class Escpos(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
:param columns: Text columns used by the printer. Defaults to 32."""
self.columns = columns
@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
:param size: size of the image
:returns: tuple of image borders
:rtype: (int, int)
"""
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 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):
""" Print formatted image """
""" Print formatted image
:param line:
:param size:
"""
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(binascii.unhexlify(pbuffer))
pbuffer = ""
while i < len(line):
hex_string = int(line[i:i+8],2)
buffer += "%02X" % hex_string
hex_string = int(line[i:i+8], 2)
pbuffer += "%02X" % hex_string
i += 8
cont += 1
if cont % 4 == 0:
self._raw(buffer.decode("hex"))
buffer = ""
self._raw(binascii.unhexlify(pbuffer))
pbuffer = ""
cont = 0
def _convert_image(self, im):
""" Parse image and prepare it to a printable format """
pixels = []
pix_line = ""
im_left = ""
im_right = ""
switch = 0
img_size = [ 0, 0 ]
""" Parse image and prepare it to a printable format
:param im: image data
:raises: :py:exc:`~escpos.exceptions.ImageSizeError`
"""
pixels = []
pix_line = ""
im_left = ""
im_right = ""
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 +126,7 @@ class Escpos:
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":
@ -95,34 +134,124 @@ 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
break
pix_line += im_right
img_size[0] += im_border[1]
self._print_image(pix_line, img_size)
def image(self, path_img):
""" Open and print an image file
def image(self,path_img):
""" Open image file """
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)
# 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 fullimage(self, img, max_height=860, width=512, histeq=True, bandsize=255):
""" Resizes and prints an arbitrarily sized image
def qr(self,text):
""" Print QR Code for the provided string """
qr_code = qrcode.QRCode(version=4, box_size=4, border=1)
.. 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:
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
:param image:
"""
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)
if value == 0:
temp |= mask
mask >>= 1
i += 1
if i == 8:
buf += ("%02X" % temp)
mask = 0x80
i = 0
temp = 0
self._raw(binascii.unhexlify(bytes(buf, "ascii")))
self._raw('\n')
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, error_correction=qrcode.constants.ERROR_CORRECT_H)
qr_code.add_data(text)
qr_code.make(fit=True)
qr_img = qr_code.make_image()
@ -131,9 +260,15 @@ class Escpos:
# Convert the RGB image in printable image
self._convert_image(im)
def charcode(self, code):
""" Set Character Code Table
def charcode(self,code):
""" Set Character Code Table """
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: :py:exc:`~escpos.exceptions.CharCodeError`
"""
if code.upper() == "USA":
self._raw(CHARCODE_PC437)
elif code.upper() == "JIS":
@ -152,8 +287,8 @@ class Escpos:
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":
@ -179,24 +314,86 @@ class Escpos:
else:
raise CharCodeError()
def barcode(self, code, bc, width, height, pos, font):
""" Print Barcode """
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. 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:
* UPC-A
* UPC-E
* EAN13
* EAN8
* CODE39
* ITF
* NW7
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
:type width: int
: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
: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 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)
else: # DEFAULT FONT: A
else: # DEFAULT FONT: A
self._raw(BARCODE_FONT_A)
# Position
if pos.upper() == "OFF":
@ -205,9 +402,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":
@ -220,27 +417,66 @@ class Escpos:
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)
else:
raise exception.BarcodeCodeError()
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: :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
def set(self, align='left', font='a', type='normal', width=1, height=1, density=9):
""" Set text properties """
:param txt: text to be printed
:param columns: amount of columns
:return: None
"""
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):
""" Set text properties by sending them to the printer
: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*: 1
:param density: print density, value from 0-8, if something else is supplied the density remains unchanged
"""
# Width
if height == 2 and width == 2:
self._raw(TXT_NORMAL)
@ -251,25 +487,25 @@ class Escpos:
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":
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
@ -303,23 +539,35 @@ class Escpos:
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 """
""" 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.
.. todo:: Check this function on TM-T88II.
: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")
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 """
""" Send pulse to kick the cash drawer
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)
elif pin == 5:
@ -327,26 +575,43 @@ class Escpos:
else:
raise CashDrawerError()
def hw(self, hw):
""" Hardware operations """
""" Hardware operations
:param hw: hardware action, may be:
* INIT
* SELECT
* RESET
"""
if hw.upper() == "INIT":
self._raw(HW_INIT)
elif hw.upper() == "SELECT":
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 """
""" Feed control sequences
: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:
raise TabError()
raise TabPosError()
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)

View File

@ -1,6 +1,24 @@
""" 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`
- `90` = USB device not found :py:exc:`~escpos.exceptions.USBNotFoundError`
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012 Bashlinux
:license: GNU GPL v3
"""
import os
class Error(Exception):
""" Base class for ESC/POS errors """
@ -14,46 +32,59 @@ 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.
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
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):
""" 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
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):
""" 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
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.
The returncode for this exception is `40`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@ -62,7 +93,13 @@ class ImageSizeError(Error):
def __str__(self):
return "Image height is longer than 255px and can't be printed"
class TextError(Error):
""" 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
@ -73,6 +110,11 @@ class TextError(Error):
class CashDrawerError(Error):
""" 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
@ -82,7 +124,12 @@ class CashDrawerError(Error):
return "Valid pin must be set to send pulse"
class TabError(Error):
class TabPosError(Error):
""" 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
@ -93,10 +140,30 @@ class TabError(Error):
class CharCodeError(Error):
""" 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"
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

@ -1,9 +1,10 @@
#!/usr/bin/python
"""
@author: Manuel F Martinez <manpaz@bashlinux.com>
@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 <manpaz@bashlinux.com>`_ and others
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012 Bashlinux
:license: GNU GPL v3
"""
import usb.core
@ -11,34 +12,37 @@ import usb.util
import serial
import socket
from escpos import *
from constants import *
from exceptions import *
from .escpos import *
from .exceptions import *
class Usb(Escpos):
""" Define USB printer """
""" USB printer
def __init__(self, idVendor, idProduct, interface=0, in_ep=0x82, out_ep=0x01):
This class describes a printer that natively speaks USB.
"""
def __init__(self, idVendor, idProduct, interface=0, in_ep=0x82, out_ep=0x01, *args, **kwargs):
"""
@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
Escpos.__init__(self, *args, **kwargs)
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 """
""" 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
@ -52,19 +56,20 @@ 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 """
self.device.write(self.out_ep, msg, self.interface)
""" 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):
""" Release USB interface """
@ -73,36 +78,37 @@ class Usb(Escpos):
self.device = None
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):
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)
"""
self.devfile = devfile
Escpos.__init__(self, *args, **kwargs)
self.devfile = devfile
self.baudrate = baudrate
self.bytesize = bytesize
self.timeout = timeout
self.timeout = timeout
self.parity = parity
self.stopbits = stopbits
self.xonxoff = xonxoff
self.dsrdtr = dsrdtr
self.open()
self.open()
def open(self):
""" Setup serial port and set is as escpos device """
@ -112,15 +118,16 @@ class Serial(Escpos):
xonxoff=self.xonxoff, dsrdtr=self.dsrdtr)
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: {0}".format(str(self.devfile)))
def _raw(self, msg):
""" Print any command sent in raw format """
self.device.write(msg)
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
"""
self.device.write(msg)
def __del__(self):
""" Close Serial interface """
@ -128,63 +135,94 @@ class Serial(Escpos):
self.device.close()
class Network(Escpos):
""" Define Network printer """
""" Network printer
def __init__(self,host,port=9100):
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 ``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, timeout=60, *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
: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 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.settimeout(self.timeout)
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 """
self.device.send(msg)
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
"""
self.device.sendall(msg)
def __del__(self):
""" Close TCP connection """
self.device.close()
class File(Escpos):
""" Define Generic file printer """
""" Generic file printer
def __init__(self, devfile="/dev/usb/lp0"):
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
self.open()
def open(self):
""" Open system file """
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 """
self.device.flush()
def _raw(self, msg):
""" Print any command sent in raw format """
self.device.write(msg);
""" 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:
self.device.write(msg)
def __del__(self):
""" Close system file """

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
-e .

View File

@ -1,15 +1,50 @@
#!/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):
"""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
args = self.tox_args
if args:
args = shlex.split(self.tox_args)
errno = tox.cmdline(args=args)
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=open('README').read(),
long_description=read('README'),
author='Manuel F Martinez',
author_email='manpaz@bashlinux.com',
platforms=['linux'],
@ -23,7 +58,19 @@ setup(
'Operating System :: GNU/Linux',
'Intended Audience :: Developers',
'Programming Language :: Python',
'Topic :: System :: Pheripherals',
'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',
],
install_requires=[
'pyusb',
'Pillow>=2.0',
'qrcode>=4.0',
'pyserial',
],
tests_require=['tox', 'nose'],
cmdclass={'test': Tox},
)

33
test/test_load_module.py Normal file
View File

@ -0,0 +1,33 @@
#!/usr/bin/python
"""very basic test cases that load the classes
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: GNU GPL v3
"""
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(devfile, 'a')
try:
os.utime(devfile, None)
finally:
fhandle.close()
def teardown_testfile():
"""destroy testfile again"""
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=devfile)
instance.text('This is a test\n')

7
tox.ini Normal file
View File

@ -0,0 +1,7 @@
[tox]
envlist = py27, py34
[testenv]
deps = nose
coverage
# TODO: implement code coverage analysis (and of course tests at first)