alpha release v3.0a2

This commit is contained in:
Patrick Kanzler 2017-08-04 16:48:36 +02:00 committed by GitHub
commit af29fcca77
38 changed files with 1056 additions and 247 deletions

11
.mailmap Normal file
View File

@ -0,0 +1,11 @@
<dev@pkanzler.de> <patrick.kanzler@fablab.fau.de>
<manpaz@gmail.com> <manpaz@bashlinux.com>
Manuel F Martinez <manpaz@gmail.com> manpaz <manpaz@bashlinux.com>
<emailofdavis@gmail.com> <davis.goglin@oregonicecream.com>
Davis Goglin <emailofdavis@gmail.com> davisgoglin <emailofdavis@gmail.com>
Michael Billington <michael.billington@gmail.com> Michael <michael.billington@gmail.com>
Cody (Quantified Code Bot) <cody@quantifiedcode.com> Cody <cody@quantifiedcode.com>
Renato Lorenzi <renato.lorenzi@senior.com.br> Renato.Lorenzi <renato.lorenzi@senior.com.br>
Ahmed Tahri <nyuubi.10@gmail.com> TAHRI Ahmed <nyuubi.10@gmail.com>
Michael Elsdörfer <michael@elsdoerfer.com> Michael Elsdörfer <michael@elsdoerfer.info>
csoft2k <csoft2k@hotmail.com>

View File

@ -1,6 +1,8 @@
language: python language: python
sudo: false sudo: false
cache: pip cache: pip
git:
depth: 100000
addons: addons:
apt: apt:
packages: packages:
@ -37,6 +39,7 @@ matrix:
- python: pypy3 - python: pypy3
before_install: before_install:
- pip install tox codecov 'sphinx>=1.5.1' - pip install tox codecov 'sphinx>=1.5.1'
- ./doc/generate_authors.sh --check
script: script:
- tox - tox
- codecov - codecov

28
AUTHORS Normal file
View File

@ -0,0 +1,28 @@
Ahmed Tahri
Asuki Kono
belono
Christoph Heuel
Cody (Quantified Code Bot)
csoft2k
Curtis // mashedkeyboard
Davis Goglin
Dean Rispin
Dmytro Katyukha
Hark
Joel Lehtonen
Kristi
ldos
Manuel F Martinez
Michael Billington
Michael Elsdörfer
mrwunderbar666
Nathan Bookham
Patrick Kanzler
Qian Linfeng
Renato Lorenzi
Romain Porte
Sam Cheng
Stephan Sokolow
Thijs Triemstra
Thomas van den Berg
ysuolmai

View File

@ -2,6 +2,37 @@
Changelog Changelog
********* *********
2017-08-04 - Version 3.0a2 - "It's My Party And I'll Sing If I Want To"
-----------------------------------------------------------------------
This release is the third alpha release of the new version 3.0. Please
be aware that the API will still change until v3.0 is released.
changes
^^^^^^^
- refactor of the set-method
- preliminary support of POS "line display" printing
- improvement of tests
- added ImageWidthError
- list authors in repository
- add support for software-based barcode-rendering
- fix SerialException when trying to close device on __del__
- added the DLE EOT querying command for USB and Serial
- ensure QR codes have a large enough border
- make feed for cut optional
- fix the behavior of horizontal tabs
- added test script for hard an soft barcodes
- implemented paper sensor querying command
- added weather forecast example script
- added a method for simpler newlines
contributors
^^^^^^^^^^^^
- csoft2k
- Patrick Kanzler
- mrwunderbar666
- Romain Porte
- Ahmed Tahri
2017-03-29 - Version 3.0a1 - "Headcrash" 2017-03-29 - Version 3.0a1 - "Headcrash"
---------------------------------------- ----------------------------------------
This release is the second alpha release of the new version 3.0. Please This release is the second alpha release of the new version 3.0. Please

View File

@ -12,6 +12,15 @@ The pull requests and issues will be prefilled with templates. Please fill in yo
This project uses `semantic versioning <http://semver.org/>`_ and tries to adhere to the proposed rules as This project uses `semantic versioning <http://semver.org/>`_ and tries to adhere to the proposed rules as
well as possible. well as possible.
Author-list
-----------
This project keeps a list of authors. This can be auto-generated by calling `./doc/generate-authors.sh`.
When contributing the first time, please include a commit with the output of this script in place.
Otherwise the integration-check will fail.
When you change your username or mail-address, please also update the `.mailmap` and the authors-list.
Style-Guide Style-Guide
----------- -----------

View File

@ -6,10 +6,6 @@ python-escpos - Python library to manipulate ESC/POS Printers
:target: https://travis-ci.org/python-escpos/python-escpos :target: https://travis-ci.org/python-escpos/python-escpos
:alt: Continous Integration :alt: Continous Integration
.. image:: https://www.quantifiedcode.com/api/v1/project/95748b89a3974700800b85e4ed3d32c4/badge.svg
:target: https://www.quantifiedcode.com/app/project/95748b89a3974700800b85e4ed3d32c4
:alt: Code issues
.. image:: https://landscape.io/github/python-escpos/python-escpos/master/landscape.svg?style=flat .. image:: https://landscape.io/github/python-escpos/python-escpos/master/landscape.svg?style=flat
:target: https://landscape.io/github/python-escpos/python-escpos/master :target: https://landscape.io/github/python-escpos/python-escpos/master
:alt: Code Health :alt: Code Health
@ -47,10 +43,11 @@ Dependencies
This library makes use of: This library makes use of:
* pyusb for USB-printers * `pyusb <https://github.com/walac/pyusb>`_ for USB-printers
* Pillow for image printing * `Pillow <https://github.com/python-pillow/Pillow>`_ for image printing
* qrcode for the generation of QR-codes * `qrcode <https://github.com/lincolnloop/python-qrcode>`_ for the generation of QR-codes
* pyserial for serial printers * `pyserial <https://github.com/pyserial/pyserial>`_ for serial printers
* `viivakoodi <https://github.com/kxepal/viivakoodi>`_ for the generation of barcodes
Documentation and Usage Documentation and Usage
----------------------- -----------------------

19
doc/generate_authors.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/sh
GENLIST=$(git shortlog -s -n | cut -f2 | sort)
AUTHORSFILE="$(dirname $0)/../AUTHORS"
TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile"
if [ "$#" -eq 1 ]
then
echo "$GENLIST">$TEMPAUTHORSFILE
echo "\nAuthorsfile in version control:\n"
cat $AUTHORSFILE
echo "\nNew authorsfile:\n"
cat $TEMPAUTHORSFILE
echo "\nUsing diff on files...\n"
diff -q --from-file $AUTHORSFILE $TEMPAUTHORSFILE
else
echo "$GENLIST">$AUTHORSFILE
fi

View File

@ -5,3 +5,4 @@ pyserial
sphinx-rtd-theme sphinx-rtd-theme
setuptools-scm setuptools-scm
docutils>=0.12 docutils>=0.12
viivakoodi

View File

@ -1,6 +1,7 @@
***** *****
Usage Usage
***** *****
:Last Reviewed: 2017-06-10
Define your printer Define your printer
------------------- -------------------
@ -133,13 +134,13 @@ format. For windows it is probably at::
And for linux:: And for linux::
$HOME/.config/python-escpos/config.yaml $HOME/.config/python-escpos/config.yaml
If you aren't sure, run:: If you aren't sure, run::
from escpos import config from escpos import config
c = config.Config() c = config.Config()
c.load() c.load()
If it can't find the configuration file in the default location, it will tell If it can't find the configuration file in the default location, it will tell
you where it's looking. You can always pass a path, or a list of paths, to you where it's looking. You can always pass a path, or a list of paths, to
@ -147,9 +148,9 @@ the ``load()`` method.
To load the configured printer, run:: To load the configured printer, run::
from escpos import config from escpos import config
c = config.Config() c = config.Config()
printer = c.printer() printer = c.printer()
The printer section The printer section
@ -157,23 +158,34 @@ The printer section
The ``printer`` configuration section defines a default printer to create. The ``printer`` configuration section defines a default printer to create.
The only required paramter is ``type``. The value of this should be one of the The only required paramter is ``type``. The value of this has to be one of the
printers defined in :doc:`/user/printers`. printers defined in :doc:`/user/printers`.
The rest of the parameters are whatever you want to pass to the printer. The rest of the given parameters will be passed on to the initialization of the printer class.
Use these to overwrite the default values as specified in :doc:`/user/printers`.
This implies that the parameters have to match the parameter-names of the respective printer class.
An example file printer:: An example file printer::
printer: printer:
type: File type: File
devfile: /dev/someprinter devfile: /dev/someprinter
And for a network printer:: And for a network printer::
printer: printer:
type: network type: Network
host: 127.0.0.1 host: 127.0.0.1
port: 9000 port: 9000
An USB-printer could be defined by::
printer:
type: Usb
idVendor: 0x1234
idProduct: 0x5678
in_ep: 0x66
out_ep: 0x01
Printing text right Printing text right
------------------- -------------------

11
examples/barcodes.py Normal file
View File

@ -0,0 +1,11 @@
from escpos.printer import Usb
# Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890")
# Print software and then hardware barcode with the same content
p.soft_barcode('code39', '123456')
p.text('\n')
p.text('\n')
p.barcode('123456', 'CODE39')

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,10 @@
# Climacons by Adam Whitcroft
75 climatically categorised pictographs for web and UI design by [@adamwhitcroft](http://www.twitter.com/#!/adamwhitcroft).
Visit the [Climacons](http://adamwhitcroft.com/climacons/) website for more information.
Visit [Adam Whitcroft on GitHub](https://github.com/AdamWhitcroft)
## License
You are free to use any of the Climacons Icons (the "icons") in any personal or commercial work without obligation of payment (monetary or otherwise) or attribution, however a credit for the work would be appreciated. **Do not** redistribute or sell and **do not** claim creative credit. Intellectual property rights are not transferred with the download of the icons.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

19
examples/qr_code.py Normal file
View File

@ -0,0 +1,19 @@
import sys
from escpos.printer import Usb
def usage():
print("usage: qr_code.py <content>")
if __name__ == '__main__':
if len(sys.argv) != 2:
usage()
sys.exit(1)
content = sys.argv[1]
# Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890")
p.qr(content)

View File

@ -0,0 +1,9 @@
from escpos.printer import Usb
# Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890")
# Some software barcodes
p.soft_barcode('code128', 'Hello')
p.soft_barcode('code39', '123456')

127
examples/weather.py Normal file
View File

@ -0,0 +1,127 @@
#!/usr/bin/python
# Adapted script from Adafruit
# Weather forecast for Raspberry Pi w/Adafruit Mini Thermal Printer.
# Retrieves data from DarkSky.net's API, prints current conditions and
# forecasts for next two days.
# Weather example using nice bitmaps.
# Written by Adafruit Industries. MIT license.
# Adapted and enhanced for escpos library by MrWunderbar666
# Icons taken from http://adamwhitcroft.com/climacons/
# Check out his github: https://github.com/AdamWhitcroft/climacons
from __future__ import print_function
from datetime import datetime
import calendar
import urllib
import json
import time
import os
from escpos.printer import Usb
""" Setting up the main pathing """
this_dir, this_filename = os.path.split(__file__)
GRAPHICS_PATH = os.path.join(this_dir, "graphics/climacons/")
# Adapt to your needs
printer = Usb(0x0416, 0x5011, profile="POS-5890")
# You can get your API Key on www.darksky.net and register a dev account.
# Technically you can use any other weather service, of course :)
API_KEY = "YOUR API KEY"
LAT = "22.345490" # Your Location
LONG = "114.189945" # Your Location
def forecast_icon(idx):
icon = data['daily']['data'][idx]['icon']
image = GRAPHICS_PATH + icon + ".png"
return image
# Dumps one forecast line to the printer
def forecast(idx):
date = datetime.fromtimestamp(int(data['daily']['data'][idx]['time']))
day = calendar.day_name[date.weekday()]
lo = data['daily']['data'][idx]['temperatureMin']
hi = data['daily']['data'][idx]['temperatureMax']
cond = data['daily']['data'][idx]['summary']
print(date)
print(day)
print(lo)
print(hi)
print(cond)
time.sleep(1)
printer.set(
font='a',
height=2,
align='left',
bold=False,
double_height=False)
printer.text(day + ' \n ')
time.sleep(5) # Sleep to prevent printer buffer overflow
printer.text('\n')
printer.image(forecast_icon(idx))
printer.text('low ' + str(lo))
printer.text(deg)
printer.text('\n')
printer.text(' high ' + str(hi))
printer.text(deg)
printer.text('\n')
# take care of pesky unicode dash
printer.text(cond.replace(u'\u2013', '-').encode('utf-8'))
printer.text('\n \n')
def icon():
icon = data['currently']['icon']
image = GRAPHICS_PATH + icon + ".png"
return image
deg = ' C' # Degree symbol on thermal printer, need to find a better way to use a proper degree symbol
# if you want Fahrenheit change units= to 'us'
url = "https://api.darksky.net/forecast/" + API_KEY + "/" + LAT + "," + LONG + \
"?exclude=[alerts,minutely,hourly,flags]&units=si" # change last bit to 'us' for Fahrenheit
response = urllib.urlopen(url)
data = json.loads(response.read())
printer.print_and_feed(n=1)
printer.control("LF")
printer.set(font='a', height=2, align='center', bold=True, double_height=True)
printer.text("Weather Forecast")
printer.text("\n")
printer.set(align='center')
# Print current conditions
printer.set(font='a', height=2, align='center', bold=True, double_height=False)
printer.text('Current conditions: \n')
printer.image(icon())
printer.text("\n")
printer.set(font='a', height=2, align='left', bold=False, double_height=False)
temp = data['currently']['temperature']
cond = data['currently']['summary']
printer.text(temp)
printer.text(' ')
printer.text(deg)
printer.text(' ')
printer.text('\n')
printer.text('Sky: ' + cond)
printer.text('\n')
printer.text('\n')
# Print forecast
printer.set(font='a', height=2, align='center', bold=True, double_height=False)
printer.text('Forecast: \n')
forecast(0)
forecast(1)
printer.cut
printer.control("LF")

View File

@ -100,6 +100,8 @@ setup(
'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy', 'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
@ -115,7 +117,8 @@ setup(
'pyyaml', 'pyyaml',
'argparse', 'argparse',
'argcomplete', 'argcomplete',
'future' 'future',
'viivakoodi>=0.8'
], ],
setup_requires=[ setup_requires=[
'setuptools_scm', 'setuptools_scm',
@ -123,7 +126,7 @@ setup(
tests_require=[ tests_require=[
'jaconv', 'jaconv',
'tox', 'tox',
'pytest', 'pytest!=3.2.0',
'pytest-cov', 'pytest-cov',
'pytest-mock', 'pytest-mock',
'nose', 'nose',

View File

@ -64,6 +64,11 @@ _PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n)
PANEL_BUTTON_ON = _PANEL_BUTTON(0) # enable all panel buttons PANEL_BUTTON_ON = _PANEL_BUTTON(0) # enable all panel buttons
PANEL_BUTTON_OFF = _PANEL_BUTTON(1) # disable all panel buttons PANEL_BUTTON_OFF = _PANEL_BUTTON(1) # disable all panel buttons
# Line display printing
LINE_DISPLAY_OPEN = ESC + b'\x3d\x02'
LINE_DISPLAY_CLEAR = ESC + b'\x40'
LINE_DISPLAY_CLOSE = ESC + b'\x3d\x01'
# Sheet modes # Sheet modes
SHEET_SLIP_MODE = ESC + b'\x63\x30\x04' # slip paper SHEET_SLIP_MODE = ESC + b'\x63\x30\x04' # slip paper
SHEET_ROLL_MODE = ESC + b'\x63\x30\x01' # paper roll SHEET_ROLL_MODE = ESC + b'\x63\x30\x01' # paper roll
@ -71,51 +76,90 @@ SHEET_ROLL_MODE = ESC + b'\x63\x30\x01' # paper roll
# Text format # Text format
# TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll # TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll
# Printers" and tidy up this stuff too. # Printers" and tidy up this stuff too.
TXT_FLIP_ON = ESC + b'\x7b\x01'
TXT_FLIP_OFF = ESC + b'\x7b\x00'
TXT_SMOOTH_ON = GS + b'\x62\x01'
TXT_SMOOTH_OFF = GS + b'\x62\x00'
TXT_SIZE = GS + b'!' TXT_SIZE = GS + b'!'
TXT_WIDTH = {1: 0x00,
2: 0x10,
3: 0x20,
4: 0x30,
5: 0x40,
6: 0x50,
7: 0x60,
8: 0x70}
TXT_HEIGHT = {1: 0x00,
2: 0x01,
3: 0x02,
4: 0x03,
5: 0x04,
6: 0x05,
7: 0x06,
8: 0x07}
TXT_NORMAL = ESC + b'!\x00' # Normal text TXT_NORMAL = ESC + b'!\x00' # Normal text
TXT_2HEIGHT = ESC + b'!\x10' # Double height text
TXT_2WIDTH = ESC + b'!\x20' # Double width text
TXT_4SQUARE = ESC + b'!\x30' # Quad area text TXT_STYLE = {
TXT_UNDERL_OFF = ESC + b'\x2d\x00' # Underline font OFF 'bold': {
TXT_UNDERL_ON = ESC + b'\x2d\x01' # Underline font 1-dot ON False: ESC + b'\x45\x00', # Bold font OFF
TXT_UNDERL2_ON = ESC + b'\x2d\x02' # Underline font 2-dot ON True: ESC + b'\x45\x01' # Bold font ON
TXT_BOLD_OFF = ESC + b'\x45\x00' # Bold font OFF },
TXT_BOLD_ON = ESC + b'\x45\x01' # Bold font ON 'underline': {
TXT_ALIGN_LT = ESC + b'\x61\x00' # Left justification 0: ESC + b'\x2d\x00', # Underline font OFF
TXT_ALIGN_CT = ESC + b'\x61\x01' # Centering 1: ESC + b'\x2d\x01', # Underline font 1-dot ON
TXT_ALIGN_RT = ESC + b'\x61\x02' # Right justification 2: ESC + b'\x2d\x02' # Underline font 2-dot ON
TXT_INVERT_ON = GS + b'\x42\x01' # Inverse Printing ON },
TXT_INVERT_OFF = GS + b'\x42\x00' # Inverse Printing OFF 'size': {
'normal': TXT_NORMAL + ESC + b'!\x00', # Normal text
'2h': TXT_NORMAL + ESC + b'!\x10', # Double height text
'2w': TXT_NORMAL + ESC + b'!\x20', # Double width text
'2x': TXT_NORMAL + ESC + b'!\x30' # Quad area text
},
'font': {
'a': ESC + b'\x4d\x00', # Font type A
'b': ESC + b'\x4d\x00' # Font type B
},
'align': {
'left': ESC + b'\x61\x00', # Left justification
'center': ESC + b'\x61\x01', # Centering
'right': ESC + b'\x61\x02' # Right justification
},
'invert': {
True: GS + b'\x42\x01', # Inverse Printing ON
False: GS + b'\x42\x00' # Inverse Printing OFF
},
'color': {
'black': ESC + b'\x72\x00', # Default Color
'red': ESC + b'\x72\x01' # Alternative Color, Usually Red
},
'flip': {
True: ESC + b'\x7b\x01', # Flip ON
False: ESC + b'\x7b\x00' # Flip OFF
},
'density': {
0: GS + b'\x7c\x00', # Printing Density -50%
1: GS + b'\x7c\x01', # Printing Density -37.5%
2: GS + b'\x7c\x02', # Printing Density -25%
3: GS + b'\x7c\x03', # Printing Density -12.5%
4: GS + b'\x7c\x04', # Printing Density 0%
5: GS + b'\x7c\x08', # Printing Density +50%
6: GS + b'\x7c\x07', # Printing Density +37.5%
7: GS + b'\x7c\x06', # Printing Density +25%
8: GS + b'\x7c\x05' # Printing Density +12.5%
},
'smooth': {
True: GS + b'\x62\x01', # Smooth ON
False: GS + b'\x62\x00' # Smooth OFF
},
'height': { # Custom text height
1: 0x00,
2: 0x01,
3: 0x02,
4: 0x03,
5: 0x04,
6: 0x05,
7: 0x06,
8: 0x07
},
'width': { # Custom text width
1: 0x00,
2: 0x10,
3: 0x20,
4: 0x30,
5: 0x40,
6: 0x50,
7: 0x60,
8: 0x70
}
}
# Fonts # Fonts
SET_FONT = lambda n: ESC + b'\x4d' + n SET_FONT = lambda n: ESC + b'\x4d' + n
TXT_FONT_A = SET_FONT(b'\x00') # Font type A TXT_FONT_A = SET_FONT(b'\x00') # Font type A
TXT_FONT_B = SET_FONT(b'\x01') # Font type B TXT_FONT_B = SET_FONT(b'\x01') # Font type B
# Text colors
TXT_COLOR_BLACK = ESC + b'\x72\x00' # Default Color
TXT_COLOR_RED = ESC + b'\x72\x01' # Alternative Color (Usually Red)
# Spacing # Spacing
LINESPACING_RESET = ESC + b'2' LINESPACING_RESET = ESC + b'2'
LINESPACING_FUNCS = { LINESPACING_FUNCS = {
@ -206,13 +250,11 @@ S_RASTER_2W = _PRINT_RASTER_IMG(b'\x01') # Set raster image double width
S_RASTER_2H = _PRINT_RASTER_IMG(b'\x02') # Set raster image double height S_RASTER_2H = _PRINT_RASTER_IMG(b'\x02') # Set raster image double height
S_RASTER_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple S_RASTER_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple
# Printing Density # Status Command
PD_N50 = GS + b'\x7c\x00' # Printing Density -50% RT_STATUS = DLE + EOT
PD_N37 = GS + b'\x7c\x01' # Printing Density -37.5% RT_STATUS_ONLINE = RT_STATUS + b'\x01'
PD_N25 = GS + b'\x7c\x02' # Printing Density -25% RT_STATUS_PAPER = RT_STATUS + b'\x04'
PD_N12 = GS + b'\x7c\x03' # Printing Density -12.5% RT_MASK_ONLINE = 8
PD_0 = GS + b'\x7c\x04' # Printing Density 0% RT_MASK_PAPER = 18
PD_P50 = GS + b'\x7c\x08' # Printing Density +50% RT_MASK_LOWPAPER = 30
PD_P37 = GS + b'\x7c\x07' # Printing Density +37.5% RT_MASK_NOPAPER = 114
PD_P25 = GS + b'\x7c\x06' # Printing Density +25%
PD_P12 = GS + b'\x7c\x05' # Printing Density +12.5%

View File

@ -18,22 +18,29 @@ from __future__ import unicode_literals
import qrcode import qrcode
import textwrap import textwrap
import six import six
import time
import barcode
from barcode.writer import ImageWriter
from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q
from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO, BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO, BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH
from .constants import TXT_ALIGN_CT, TXT_ALIGN_LT, TXT_ALIGN_RT, BARCODE_FONT_A, BARCODE_FONT_B from .constants import BARCODE_FONT_A, BARCODE_FONT_B
from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW
from .constants import TXT_HEIGHT, TXT_WIDTH, TXT_SIZE, TXT_NORMAL, TXT_SMOOTH_OFF, TXT_SMOOTH_ON from .constants import TXT_SIZE, TXT_NORMAL
from .constants import TXT_FLIP_OFF, TXT_FLIP_ON, TXT_2WIDTH, TXT_2HEIGHT, TXT_4SQUARE from .constants import SET_FONT
from .constants import TXT_UNDERL_OFF, TXT_UNDERL_ON, TXT_BOLD_OFF, TXT_BOLD_ON, SET_FONT, TXT_UNDERL2_ON from .constants import LINESPACING_FUNCS, LINESPACING_RESET
from .constants import TXT_INVERT_OFF, TXT_INVERT_ON, LINESPACING_FUNCS, LINESPACING_RESET from .constants import LINE_DISPLAY_OPEN, LINE_DISPLAY_CLEAR, LINE_DISPLAY_CLOSE
from .constants import PD_0, PD_N12, PD_N25, PD_N37, PD_N50, PD_P50, PD_P37, PD_P25, PD_P12
from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT
from .constants import HW_RESET, HW_SELECT, HW_INIT from .constants import HW_RESET, HW_SELECT, HW_INIT
from .constants import CTL_VT, CTL_HT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON from .constants import CTL_VT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON
from .constants import TXT_STYLE
from .constants import RT_STATUS_ONLINE, RT_MASK_ONLINE
from .constants import RT_STATUS_PAPER, RT_MASK_PAPER, RT_MASK_LOWPAPER, RT_MASK_NOPAPER
from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError
from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError
from .exceptions import ImageWidthError
from .magicencode import MagicEncode from .magicencode import MagicEncode
@ -73,6 +80,12 @@ class Escpos(object):
""" """
pass pass
def _read(self, msg):
""" Returns a NotImplementedError if the instance of the class doesn't override this method.
:raises NotImplementedError
"""
raise NotImplementedError()
def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster", def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster",
fragment_height=960): fragment_height=960):
""" Print an image """ Print an image
@ -99,6 +112,17 @@ class Escpos(object):
""" """
im = EscposImage(img_source) im = EscposImage(img_source)
try:
max_width = int(self.profile.profile_data['media']['width']['pixels'])
if im.width > max_width:
raise ImageWidthError('{} > {}'.format(im.width, max_width))
except KeyError:
# If the printer's pixel width is not known, print anyways...
pass
except ValueError:
# If the max_width cannot be converted to an int, print anyways...
pass
if im.height > fragment_height: if im.height > fragment_height:
fragments = im.split(fragment_height) fragments = im.split(fragment_height)
for fragment in fragments: for fragment in fragments:
@ -188,7 +212,10 @@ class Escpos(object):
qr_img = qr_code.make_image() qr_img = qr_code.make_image()
im = qr_img._img.convert("RGB") im = qr_img._img.convert("RGB")
# Convert the RGB image in printable image # Convert the RGB image in printable image
self.text('\n')
self.image(im) self.image(im)
self.text('\n')
self.text('\n')
return return
# Native 2D code printing # Native 2D code printing
cn = b'1' # Code type for QR code cn = b'1' # Code type for QR code
@ -224,9 +251,9 @@ class Escpos(object):
""" """
max_input = (256 << (out_bytes * 8) - 1) max_input = (256 << (out_bytes * 8) - 1)
if not 1 <= out_bytes <= 4: if not 1 <= out_bytes <= 4:
raise ValueError("Can only output 1-4 byes") raise ValueError("Can only output 1-4 bytes")
if not 0 <= inp_number <= max_input: if not 0 <= inp_number <= max_input:
raise ValueError("Number too large. Can only output up to {0} in {1} byes".format(max_input, out_bytes)) raise ValueError("Number too large. Can only output up to {0} in {1} bytes".format(max_input, out_bytes))
outp = b'' outp = b''
for _ in range(0, out_bytes): for _ in range(0, out_bytes):
outp += six.int2byte(inp_number % 256) outp += six.int2byte(inp_number % 256)
@ -356,7 +383,7 @@ class Escpos(object):
# Align Bar Code() # Align Bar Code()
if align_ct: if align_ct:
self._raw(TXT_ALIGN_CT) self._raw(TXT_STYLE['align']['center'])
# Height # Height
if 1 <= height <= 255: if 1 <= height <= 255:
self._raw(BARCODE_HEIGHT + six.int2byte(height)) self._raw(BARCODE_HEIGHT + six.int2byte(height))
@ -396,6 +423,31 @@ class Escpos(object):
if function_type.upper() == "A": if function_type.upper() == "A":
self._raw(NUL) self._raw(NUL)
def soft_barcode(self, barcode_type, data, impl='bitImageColumn',
module_height=5, module_width=0.2, text_distance=1):
image_writer = ImageWriter()
# Check if barcode type exists
if barcode_type not in barcode.PROVIDED_BARCODES:
raise BarcodeTypeError(
'Barcode type {} not supported by software barcode renderer'
.format(barcode_type))
# Render the barcode to a fake file
barcode_class = barcode.get_barcode_class(barcode_type)
my_code = barcode_class(data, writer=image_writer)
my_code.write("/dev/null", {
'module_height': module_height,
'module_width': module_width,
'text_distance': text_distance
})
# Retrieve the Pillow image and print it
image = my_code.writer._image
self.image(image, impl=impl)
def text(self, txt): def text(self, txt):
""" Print alpha-numeric text """ Print alpha-numeric text
@ -408,132 +460,113 @@ class Escpos(object):
txt = six.text_type(txt) txt = six.text_type(txt)
self.magic.write(txt) self.magic.write(txt)
def textln(self, txt=''):
"""Print alpha-numeric text with a newline
The text has to be encoded in the currently selected codepage.
The input text has to be encoded in unicode.
:param txt: text to be printed with a newline
:raises: :py:exc:`~escpos.exceptions.TextError`
"""
self.text('{}\n'.format(txt))
def ln(self, count=1):
"""Print a newline or more
:param count: number of newlines to print
:raises: :py:exc:`ValueError` if count < 0
"""
if count < 0:
raise ValueError('Count cannot be lesser than 0')
if count > 0:
self.text('\n' * count)
def block_text(self, txt, font=None, columns=None): def block_text(self, txt, font=None, columns=None):
""" Text is printed wrapped to specified columns """ Text is printed wrapped to specified columns
Text has to be encoded in unicode. Text has to be encoded in unicode.
:param txt: text to be printed :param txt: text to be printed
:param font: font to be used, can be :code:`a` or :code`b` :param font: font to be used, can be :code:`a` or :code:`b`
:param columns: amount of columns :param columns: amount of columns
:return: None :return: None
""" """
col_count = self.profile.get_columns(font) if columns is None else columns col_count = self.profile.get_columns(font) if columns is None else columns
self.text(textwrap.fill(txt, col_count)) self.text(textwrap.fill(txt, col_count))
def set(self, align='left', font='a', text_type='normal', width=1, def set(self, align='left', font='a', bold=False, underline=0, width=1,
height=1, density=9, invert=False, smooth=False, flip=False): height=1, density=9, invert=False, smooth=False, flip=False,
double_width=False, double_height=False, custom_size=False):
""" Set text properties by sending them to the printer """ Set text properties by sending them to the printer
:param align: horizontal position for text, possible values are: :param align: horizontal position for text, possible values are:
* CENTER * 'center'
* LEFT * 'left'
* RIGHT * 'right'
*default*: LEFT *default*: 'left'
:param font: font given as an index, a name, or one of the :param font: font given as an index, a name, or one of the
special values 'a' or 'b', refering to fonts 0 and 1. special values 'a' or 'b', referring to fonts 0 and 1.
:param text_type: text type, possible values are: :param bold: text in bold, *default*: False
:param underline: underline mode for text, decimal range 0-2, *default*: 0
* B for bold :param double_height: doubles the height of the text
* U for underlined :param double_width: doubles the width of the text
* U2 for underlined, version 2 :param custom_size: uses custom size specified by width and height
* BU for bold and underlined parameters. Cannot be used with double_width or double_height.
* BU2 for bold and underlined, version 2 :param width: text width multiplier when custom_size is used, decimal range 1-8, *default*: 1
* NORMAL for normal text :param height: text height multiplier when custom_size is used, decimal range 1-8, *default*: 1
*default*: NORMAL
:param width: text width multiplier, decimal range 1-8, *default*: 1
:param height: text height multiplier, decimal range 1-8, *default*: 1
:param density: print density, value from 0-8, if something else is supplied the density remains unchanged :param density: print density, value from 0-8, if something else is supplied the density remains unchanged
:param invert: True enables white on black printing, *default*: False :param invert: True enables white on black printing, *default*: False
:param smooth: True enables text smoothing. Effective on 4x4 size text and larger, *default*: False :param smooth: True enables text smoothing. Effective on 4x4 size text and larger, *default*: False
:param flip: True enables upside-down printing, *default*: False :param flip: True enables upside-down printing, *default*: False
:type invert: bool
"""
# Width
if height == 2 and width == 2:
self._raw(TXT_NORMAL)
self._raw(TXT_4SQUARE)
elif height == 2 and width == 1:
self._raw(TXT_NORMAL)
self._raw(TXT_2HEIGHT)
elif width == 2 and height == 1:
self._raw(TXT_NORMAL)
self._raw(TXT_2WIDTH)
elif width == 1 and height == 1:
self._raw(TXT_NORMAL)
elif 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and isinstance(height, int):
self._raw(TXT_SIZE + six.int2byte(TXT_WIDTH[width] + TXT_HEIGHT[height]))
else:
raise SetVariableError()
# Upside down
if flip:
self._raw(TXT_FLIP_ON)
else:
self._raw(TXT_FLIP_OFF)
# Smoothing
if smooth:
self._raw(TXT_SMOOTH_ON)
else:
self._raw(TXT_SMOOTH_OFF)
# Type
if text_type.upper() == "B":
self._raw(TXT_BOLD_ON)
self._raw(TXT_UNDERL_OFF)
elif text_type.upper() == "U":
self._raw(TXT_BOLD_OFF)
self._raw(TXT_UNDERL_ON)
elif text_type.upper() == "U2":
self._raw(TXT_BOLD_OFF)
self._raw(TXT_UNDERL2_ON)
elif text_type.upper() == "BU":
self._raw(TXT_BOLD_ON)
self._raw(TXT_UNDERL_ON)
elif text_type.upper() == "BU2":
self._raw(TXT_BOLD_ON)
self._raw(TXT_UNDERL2_ON)
elif text_type.upper() == "NORMAL":
self._raw(TXT_BOLD_OFF)
self._raw(TXT_UNDERL_OFF)
# Font
self._raw(SET_FONT(six.int2byte(self.profile.get_font(font))))
# Align :type font: str
if align.upper() == "CENTER": :type invert: bool
self._raw(TXT_ALIGN_CT) :type bold: bool
elif align.upper() == "RIGHT": :type underline: bool
self._raw(TXT_ALIGN_RT) :type smooth: bool
elif align.upper() == "LEFT": :type flip: bool
self._raw(TXT_ALIGN_LT) :type custom_size: bool
# Density :type double_width: bool
if density == 0: :type double_height: bool
self._raw(PD_N50) :type align: str
elif density == 1: :type width: int
self._raw(PD_N37) :type height: int
elif density == 2: :type density: int
self._raw(PD_N25) """
elif density == 3:
self._raw(PD_N12) if custom_size:
elif density == 4: if 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and\
self._raw(PD_0) isinstance(height, int):
elif density == 5: size_byte = TXT_STYLE['width'][width] + TXT_STYLE['height'][height]
self._raw(PD_P12) self._raw(TXT_SIZE + six.int2byte(size_byte))
elif density == 6: else:
self._raw(PD_P25) raise SetVariableError()
elif density == 7:
self._raw(PD_P37)
elif density == 8:
self._raw(PD_P50)
else: # DEFAULT: DOES NOTHING
pass
# Invert Printing
if invert:
self._raw(TXT_INVERT_ON)
else: else:
self._raw(TXT_INVERT_OFF) self._raw(TXT_NORMAL)
if double_width and double_height:
self._raw(TXT_STYLE['size']['2x'])
elif double_width:
self._raw(TXT_STYLE['size']['2w'])
elif double_height:
self._raw(TXT_STYLE['size']['2h'])
else:
self._raw(TXT_STYLE['size']['normal'])
self._raw(TXT_STYLE['flip'][flip])
self._raw(TXT_STYLE['smooth'][smooth])
self._raw(TXT_STYLE['bold'][bold])
self._raw(TXT_STYLE['underline'][underline])
self._raw(SET_FONT(six.int2byte(self.profile.get_font(font))))
self._raw(TXT_STYLE['align'][align])
if density != 9:
self._raw(TXT_STYLE['density'][density])
self._raw(TXT_STYLE['invert'][invert])
def line_spacing(self, spacing=None, divisor=180): def line_spacing(self, spacing=None, divisor=180):
""" Set line character spacing. """ Set line character spacing.
@ -564,7 +597,7 @@ class Escpos(object):
self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing)) self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing))
def cut(self, mode='FULL'): def cut(self, mode='FULL', feed=True):
""" Cut paper. """ Cut paper.
Without any arguments the paper will be cut completely. With 'mode=PART' a partial cut will Without any arguments the paper will be cut completely. With 'mode=PART' a partial cut will
@ -574,8 +607,14 @@ class Escpos(object):
.. todo:: Check this function on TM-T88II. .. todo:: Check this function on TM-T88II.
:param mode: set to 'PART' for a partial cut. default: 'FULL' :param mode: set to 'PART' for a partial cut. default: 'FULL'
:param feed: print and feed before cutting. default: true
:raises ValueError: if mode not in ('FULL', 'PART') :raises ValueError: if mode not in ('FULL', 'PART')
""" """
if not feed:
self._raw(GS + b'V' + six.int2byte(66) + b'\x00')
return
self.print_and_feed(6) self.print_and_feed(6)
mode = mode.upper() mode = mode.upper()
@ -612,6 +651,41 @@ class Escpos(object):
except: except:
raise CashDrawerError() raise CashDrawerError()
def linedisplay_select(self, select_display=False):
""" Selects the line display or the printer
This method is used for line displays that are daisy-chained between your computer and printer.
If you set `select_display` to true, only the display is selected and if you set it to false,
only the printer is selected.
:param select_display: whether the display should be selected or the printer
:type select_display: bool
"""
if select_display:
self._raw(LINE_DISPLAY_OPEN)
else:
self._raw(LINE_DISPLAY_CLOSE)
def linedisplay_clear(self):
""" Clears the line display and resets the cursor
This method is used for line displays that are daisy-chained between your computer and printer.
"""
self._raw(LINE_DISPLAY_CLEAR)
def linedisplay(self, text):
"""
Display text on a line display connected to your printer
You should connect a line display to your printer. You can do this by daisy-chaining
the display between your computer and printer.
:param text: Text to display
"""
self.linedisplay_select(select_display=True)
self.linedisplay_clear()
self.text(text)
self.linedisplay_select(select_display=False)
def hw(self, hw): def hw(self, hw):
""" Hardware operations """ Hardware operations
@ -644,7 +718,7 @@ class Escpos(object):
else: else:
raise ValueError("n must be betwen 0 and 255") raise ValueError("n must be betwen 0 and 255")
def control(self, ctl, pos=4): def control(self, ctl, count=5, tab_size=8):
""" Feed control sequences """ Feed control sequences
:param ctl: string for the following control sequences: :param ctl: string for the following control sequences:
@ -655,7 +729,8 @@ class Escpos(object):
* HT *for Horizontal Tab* * HT *for Horizontal Tab*
* VT *for Vertical Tab* * VT *for Vertical Tab*
:param pos: integer between 1 and 16, controls the horizontal tab position :param count: integer between 1 and 32, controls the horizontal tab count. Defaults to 5.
:param tab_size: integer between 1 and 255, controls the horizontal tab size in characters. Defaults to 8
:raises: :py:exc:`~escpos.exceptions.TabPosError` :raises: :py:exc:`~escpos.exceptions.TabPosError`
""" """
# Set position # Set position
@ -666,13 +741,16 @@ class Escpos(object):
elif ctl.upper() == "CR": elif ctl.upper() == "CR":
self._raw(CTL_CR) self._raw(CTL_CR)
elif ctl.upper() == "HT": elif ctl.upper() == "HT":
if not (1 <= pos <= 16): if not (0 <= count <= 32 and
1 <= tab_size <= 255 and
count * tab_size < 256):
raise TabPosError() raise TabPosError()
else: else:
# Set tab positions # Set tab positions
self._raw(CTL_SET_HT + six.int2byte(pos)) self._raw(CTL_SET_HT)
for iterator in range(1, count):
self._raw(CTL_HT) self._raw(six.int2byte(iterator * tab_size))
self._raw(NUL)
elif ctl.upper() == "VT": elif ctl.upper() == "VT":
self._raw(CTL_VT) self._raw(CTL_VT)
@ -699,6 +777,41 @@ class Escpos(object):
else: else:
self._raw(PANEL_BUTTON_OFF) self._raw(PANEL_BUTTON_OFF)
def query_status(self, mode):
""" Queries the printer for its status, and returns an array of integers containing it.
:param mode: Integer that sets the status mode queried to the printer.
RT_STATUS_ONLINE: Printer status.
RT_STATUS_PAPER: Paper sensor.
:rtype: array(integer)"""
self._raw(mode)
time.sleep(1)
status = self._read()
return status
def is_online(self):
""" Queries the printer its online status.
When online, returns True; False otherwise.
:rtype: bool: True if online, False if offline."""
status = self.query_status(RT_STATUS_ONLINE)
if len(status) == 0:
return False
return not (status & RT_MASK_ONLINE)
def paper_status(self):
""" Queries the printer its paper status.
Returns 2 if there is plenty of paper, 1 if the paper has arrived to
the near-end sensor and 0 if there is no paper.
:rtype: int: 2: Paper is adequate. 1: Paper ending. 0: No paper."""
status = self.query_status(RT_STATUS_PAPER)
if len(status) == 0:
return 2
if (status[0] & RT_MASK_NOPAPER == RT_MASK_NOPAPER):
return 0
if (status[0] & RT_MASK_LOWPAPER == RT_MASK_LOWPAPER):
return 1
if (status[0] & RT_MASK_PAPER == RT_MASK_PAPER):
return 2
class EscposIO(object): class EscposIO(object):
"""ESC/POS Printer IO object """ESC/POS Printer IO object

View File

@ -8,6 +8,7 @@ Result/Exit codes:
- `20` = Barcode size values are out of range :py:exc:`~escpos.exceptions.BarcodeSizeError` - `20` = Barcode size values are out of range :py:exc:`~escpos.exceptions.BarcodeSizeError`
- `30` = Barcode text not supplied :py:exc:`~escpos.exceptions.BarcodeCodeError` - `30` = Barcode text not supplied :py:exc:`~escpos.exceptions.BarcodeCodeError`
- `40` = Image height is too large :py:exc:`~escpos.exceptions.ImageSizeError` - `40` = Image height is too large :py:exc:`~escpos.exceptions.ImageSizeError`
- `41` = Image width is too large :py:exc:`~escpos.exceptions.ImageWidthError`
- `50` = No string supplied to be printed :py:exc:`~escpos.exceptions.TextError` - `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` - `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`
@ -104,6 +105,20 @@ class ImageSizeError(Error):
return "Image height is longer than 255px and can't be printed ({msg})".format(msg=self.msg) return "Image height is longer than 255px and can't be printed ({msg})".format(msg=self.msg)
class ImageWidthError(Error):
""" Image width is too large.
The return code for this exception is `41`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 41
def __str__(self):
return "Image width is too large ({msg})".format(msg=self.msg)
class TextError(Error): class TextError(Error):
""" Text string must be supplied to the `text()` method. """ Text string must be supplied to the `text()` method.
@ -135,7 +150,8 @@ class CashDrawerError(Error):
class TabPosError(Error): class TabPosError(Error):
""" Valid tab positions must be in the range 0 to 16. """ Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values.
Both values multiplied must not exceed 255, since it is the maximum tab value.
This exception is raised by :py:meth:`escpos.escpos.Escpos.control`. This exception is raised by :py:meth:`escpos.escpos.Escpos.control`.
The returncode for this exception is `70`. The returncode for this exception is `70`.

View File

@ -84,6 +84,10 @@ class Usb(Escpos):
""" """
self.device.write(self.out_ep, msg, self.timeout) self.device.write(self.out_ep, msg, self.timeout)
def _read(self):
""" Reads a data buffer and returns it to the caller. """
return self.device.read(self.in_ep, 16)
def close(self): def close(self):
""" Release USB interface """ """ Release USB interface """
if self.device: if self.device:
@ -131,6 +135,8 @@ class Serial(Escpos):
def open(self): def open(self):
""" Setup serial port and set is as escpos device """ """ Setup serial port and set is as escpos device """
if self.device is not None and self.device.is_open:
self.close()
self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate, self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate,
bytesize=self.bytesize, parity=self.parity, bytesize=self.bytesize, parity=self.parity,
stopbits=self.stopbits, timeout=self.timeout, stopbits=self.stopbits, timeout=self.timeout,
@ -149,9 +155,13 @@ class Serial(Escpos):
""" """
self.device.write(msg) self.device.write(msg)
def _read(self):
""" Reads a data buffer and returns it to the caller. """
return self.device.read(16)
def close(self): def close(self):
""" Close Serial interface """ """ Close Serial interface """
if self.device is not None: if self.device is not None and self.device.is_open:
self.device.flush() self.device.flush()
self.device.close() self.device.close()

17
test/test_function_cut.py Normal file
View File

@ -0,0 +1,17 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import six
import escpos.printer as printer
from escpos.constants import GS
def test_cut_without_feed():
"""Test cut without feeding paper"""
instance = printer.Dummy()
instance.cut(feed=False)
expected = GS + b'V' + six.int2byte(66) + b'\x00'
assert(instance.output == expected)

View File

@ -12,9 +12,13 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import escpos.printer as printer import pytest
from PIL import Image from PIL import Image
import escpos.printer as printer
from escpos.exceptions import ImageWidthError
# Raster format print # Raster format print
def test_bit_image_black(): def test_bit_image_black():
@ -139,3 +143,22 @@ def test_large_graphics():
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/black_white.png', impl="bitImageRaster", fragment_height=1) instance.image('test/resources/black_white.png', impl="bitImageRaster", fragment_height=1)
assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00') assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00')
def test_width_too_large():
"""
Test printing an image that is too large in width.
"""
instance = printer.Dummy()
instance.profile.profile_data = {
'media': {
'width': {
'pixels': 384
}
}
}
with pytest.raises(ImageWidthError):
instance.image(Image.new("RGB", (385, 200)))
instance.image(Image.new("RGB", (384, 200)))

View File

@ -0,0 +1,35 @@
#!/usr/bin/python
"""tests for line display
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2017 `python-escpos <https://github.com/python-escpos>`_
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer
def test_function_linedisplay_select_on():
"""test the linedisplay_select function (activate)"""
instance = printer.Dummy()
instance.linedisplay_select(select_display=True)
assert(instance.output == b'\x1B\x3D\x02')
def test_function_linedisplay_select_off():
"""test the linedisplay_select function (deactivate)"""
instance = printer.Dummy()
instance.linedisplay_select(select_display=False)
assert(instance.output == b'\x1B\x3D\x01')
def test_function_linedisplay_clear():
"""test the linedisplay_clear function"""
instance = printer.Dummy()
instance.linedisplay_clear()
assert(instance.output == b'\x1B\x40')

View File

@ -12,43 +12,18 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
from nose.tools import with_setup
import escpos.printer as printer 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_function_panel_button_on(): def test_function_panel_button_on():
"""test the panel button function (enabling) by comparing output""" """test the panel button function (enabling) by comparing output"""
instance = printer.File(devfile=devfile) instance = printer.Dummy()
instance.panel_buttons() instance.panel_buttons()
instance.flush() assert(instance.output == b'\x1B\x63\x35\x00')
with open(devfile, "rb") as f:
assert(f.read() == b'\x1B\x63\x35\x00')
@with_setup(setup_testfile, teardown_testfile)
def test_function_panel_button_off(): def test_function_panel_button_off():
"""test the panel button function (disabling) by comparing output""" """test the panel button function (disabling) by comparing output"""
instance = printer.File(devfile=devfile) instance = printer.Dummy()
instance.panel_buttons(False) instance.panel_buttons(False)
instance.flush() assert(instance.output == b'\x1B\x63\x35\x01')
with open(devfile, "rb") as f:
assert(f.read() == b'\x1B\x63\x35\x01')

View File

@ -86,9 +86,11 @@ def test_image():
instance = printer.Dummy() instance = printer.Dummy()
instance.qr("1", native=False, size=1) instance.qr("1", native=False, size=1)
print(instance.output) print(instance.output)
expected = b'\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et' \ expected = b'\x1bt\x00\n' \
b'\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et' \
b']ItA=\x04\x7fU\xfc\x00\x0c\x00y~t4\x7f =\xa84j\xd9\xf0\x05\xd4\x90\x00' \ b']ItA=\x04\x7fU\xfc\x00\x0c\x00y~t4\x7f =\xa84j\xd9\xf0\x05\xd4\x90\x00' \
b'i(\x7f<\xa8A \xd8]\'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00' b'i(\x7f<\xa8A \xd8]\'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00' \
b'\n\n'
assert(instance.output == expected) assert(instance.output == expected)

280
test/test_function_set.py Normal file
View File

@ -0,0 +1,280 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import six
import escpos.printer as printer
from escpos.constants import TXT_NORMAL, TXT_STYLE, SET_FONT
from escpos.constants import TXT_SIZE
# Default test, please copy and paste this block to test set method calls
def test_default_values():
instance = printer.Dummy()
instance.set()
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
)
assert(instance.output == b''.join(expected_sequence))
# Size tests
def test_set_size_2h():
instance = printer.Dummy()
instance.set(double_height=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['2h'], # Double height text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
def test_set_size_2w():
instance = printer.Dummy()
instance.set(double_width=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['2w'], # Double width text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
def test_set_size_2x():
instance = printer.Dummy()
instance.set(double_height=True, double_width=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['2x'], # Double text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
def test_set_size_custom():
instance = printer.Dummy()
instance.set(custom_size=True, width=8, height=7)
expected_sequence = (
TXT_SIZE, # Custom text size, no normal reset
six.int2byte(TXT_STYLE['width'][8] + TXT_STYLE['height'][7]),
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
# Flip
def test_set_flip():
instance = printer.Dummy()
instance.set(flip=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][True], # Flip ON
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
# Smooth
def test_smooth():
instance = printer.Dummy()
instance.set(smooth=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][True], # Smooth ON
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
)
assert(instance.output == b''.join(expected_sequence))
# Type
def test_set_bold():
instance = printer.Dummy()
instance.set(bold=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][True], # Bold ON
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
def test_set_underline():
instance = printer.Dummy()
instance.set(underline=1)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][1], # Underline ON, type 1
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
def test_set_underline2():
instance = printer.Dummy()
instance.set(underline=2)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][2], # Underline ON, type 2
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
# Align
def test_align_center():
instance = printer.Dummy()
instance.set(align='center')
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['center'], # Align center
TXT_STYLE['invert'][False] # Inverted OFF
)
assert(instance.output == b''.join(expected_sequence))
def test_align_right():
instance = printer.Dummy()
instance.set(align='right')
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['right'], # Align right
TXT_STYLE['invert'][False] # Inverted OFF
)
assert(instance.output == b''.join(expected_sequence))
# Densities
def test_densities():
for density in range(8):
instance = printer.Dummy()
instance.set(density=density)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['density'][density], # Custom density from 0 to 8
TXT_STYLE['invert'][False] # Inverted OFF
)
assert(instance.output == b''.join(expected_sequence))
# Invert
def test_invert():
instance = printer.Dummy()
instance.set(invert=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][True] # Inverted ON
)
assert(instance.output == b''.join(expected_sequence))

View File

@ -39,3 +39,27 @@ def test_block_text():
"All the presidents men were eating falafel for breakfast.", font='a') "All the presidents men were eating falafel for breakfast.", font='a')
assert printer.output == \ assert printer.output == \
b'All the presidents men were eating falafel\nfor breakfast.' b'All the presidents men were eating falafel\nfor breakfast.'
def test_textln():
printer = get_printer()
printer.textln('hello, world')
assert printer.output == b'hello, world\n'
def test_textln_empty():
printer = get_printer()
printer.textln()
assert printer.output == b'\n'
def test_ln():
printer = get_printer()
printer.ln()
assert printer.output == b'\n'
def test_multiple_ln():
printer = get_printer()
printer.ln(3)
assert printer.output == b'\n\n\n'

View File

@ -12,30 +12,10 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
from nose.tools import with_setup
import escpos.printer as printer 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(): def test_instantiation():
"""test the instantiation of a escpos-printer class and basic printing""" """test the instantiation of a escpos-printer class and basic printing"""
instance = printer.File(devfile=devfile) instance = printer.Dummy()
instance.text('This is a test\n') instance.text('This is a test\n')

View File

@ -7,10 +7,11 @@ deps = nose
coverage coverage
scripttest scripttest
mock mock
pytest pytest!=3.2.0
pytest-cov pytest-cov
pytest-mock pytest-mock
hypothesis hypothesis
viivakoodi
commands = py.test --cov escpos commands = py.test --cov escpos
[testenv:docs] [testenv:docs]
@ -18,6 +19,7 @@ basepython = python
changedir = doc changedir = doc
deps = sphinx>=1.5.1 deps = sphinx>=1.5.1
setuptools_scm setuptools_scm
viivakoodi
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
[testenv:flake8] [testenv:flake8]