1
0
mirror of https://github.com/python-escpos/python-escpos synced 2025-10-23 09:30:00 +00:00

5 Commits

Author SHA1 Message Date
Christoph Heuel
83185bf2f3 Change doc 2017-04-22 01:09:24 +02:00
Christoph Heuel
57ce8389d7 Merge branch 'master' into debian/jessie 2017-04-01 01:03:06 +02:00
Christoph Heuel
b2ce102ca5 Release 1.0.8-3 2015-12-13 14:31:07 +01:00
Christoph Heuel
d50c407edb Merge branch 'master' into debian/jessie 2015-12-13 14:25:19 +01:00
Christoph Heuel
2a2ba9a0e2 Debian packaging
* Add Debian packaging file
* Use pybuild system to generate Python packages
2015-12-12 21:50:56 +01:00
67 changed files with 675 additions and 1756 deletions

1
.gitignore vendored
View File

@@ -20,7 +20,6 @@ dist/
.coverage .coverage
src/escpos/version.py src/escpos/version.py
.hypothesis .hypothesis
.pytest_cache/
# testing temporary directories # testing temporary directories
test/test-cli-output/ test/test-cli-output/

1
.gitmodules vendored
View File

@@ -1,4 +1,3 @@
[submodule "capabilities-data"] [submodule "capabilities-data"]
path = capabilities-data path = capabilities-data
url = https://github.com/receipt-print-hq/escpos-printer-db.git url = https://github.com/receipt-print-hq/escpos-printer-db.git
branch = master

View File

@@ -1,14 +0,0 @@
<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>
Juanmi Taboada <juanmi@juanmitaboada.com> Juanmi Taboada <juanmi@juanmitaboada.com>
csoft2k <csoft2k@hotmail.com>
Sergio Pulgarin <sergio.pulgarin@gmail.com>
reck31 <rakesh.gunduka@gmail.com>

View File

@@ -1,19 +1,16 @@
language: python language: python
sudo: false sudo: false
cache: pip cache: pip
git:
depth: 100000
addons: addons:
apt: apt:
packages: packages:
- graphviz - graphviz
env:
global:
- ESCPOS_CAPABILITIES_FILE=/home/travis/build/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
matrix: matrix:
include: include:
- python: 2.7 - python: 2.7
env: TOXENV=py27 env: TOXENV=py27
- python: 3.3
env: TOXENV=py33
- python: 3.4 - python: 3.4
env: TOXENV=py34 env: TOXENV=py34
- python: 3.5 - python: 3.5
@@ -22,8 +19,6 @@ matrix:
env: TOXENV=py36 env: TOXENV=py36
- python: 3.6-dev - python: 3.6-dev
env: TOXENV=py36 env: TOXENV=py36
- python: 3.7-dev
env: TOXENV=py37
- python: nightly - python: nightly
env: TOXENV=py37 env: TOXENV=py37
- python: pypy - python: pypy
@@ -38,12 +33,10 @@ matrix:
env: TOXENV=flake8 env: TOXENV=flake8
allow_failures: allow_failures:
- python: 3.6-dev - python: 3.6-dev
- python: 3.7-dev
- python: nightly - python: nightly
- 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
@@ -63,4 +56,4 @@ deploy:
tags: true tags: true
repo: python-escpos/python-escpos repo: python-escpos/python-escpos
branch: master branch: master
condition: $TRAVIS_PYTHON_VERSION = "3.6" condition: $TRAVIS_PYTHON_VERSION = "3.5"

33
AUTHORS
View File

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

View File

@@ -1,79 +1,6 @@
********* *********
Changelog Changelog
********* *********
2018-05-15 - Version 3.0a4 - "Kakistocrat"
------------------------------------------
This release is the fifth alpha release of the new version 3.0. Please
be aware that the API will still change until v3.0 is released.
changes
^^^^^^^
- raise exception when TypeError occurs in cashdraw (#268)
- Feature/clear content in dummy printer (#271)
- fix is_online() (#282)
- improve documentation
- Modified submodule to always pull from master branch (#283)
- parameter for implementation of nonnative qrcode (#289)
- improve platform independence (#296)
contributors
^^^^^^^^^^^^
- Christoph Heuel
- Patrick Kanzler
- kennedy
- primax79
- reck31
- Thijs Triemstra
2017-10-08 - Version 3.0a3 - "Just Testing"
-------------------------------------------
This release is the fourth alpha release of the new version 3.0. Please
be aware that the API will still change until v3.0 is released.
changes
^^^^^^^
- minor changes in documentation, tests and examples
- pickle capabilities for faster startup
- first implementation of centering images and QR
- check barcodes based on regex
contributors
^^^^^^^^^^^^
- Patrick Kanzler
- Lucy Linder
- Romain Porte
- Sergio Pulgarin
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"
---------------------------------------- ----------------------------------------

View File

@@ -12,16 +12,6 @@ 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.
You can find a good documentation on the mapping-feature in the `documentation of git-shortlog <https://git-scm.com/docs/git-shortlog#_mapping_authors>`_.
Style-Guide Style-Guide
----------- -----------
@@ -48,11 +38,9 @@ Often you can achieve compatibility quite easily with a tool from the `six`-pack
PEP8 PEP8
^^^^ ^^^^
The entire codebase adheres to the rules of PEP8. This is not yet consequently done in every piece of code, but please try to ensure
These rules are enforced by running `flake8` in the integration-checks. that your code honors PEP8.
Please adhere to these rules as your contribution can only be merged if the check succeeds. The checks by Landscape and QuantifiedCode that run on every PR will provide you with hints.
You can use flake8 or similar tools locally in order to check your code.
Apart from that the travis-log and the check by Landscape will provide you with hints.
GIT GIT
^^^ ^^^

View File

@@ -6,6 +6,10 @@ 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
@@ -43,11 +47,10 @@ Dependencies
This library makes use of: This library makes use of:
* `pyusb <https://github.com/walac/pyusb>`_ for USB-printers * pyusb for USB-printers
* `Pillow <https://github.com/python-pillow/Pillow>`_ for image printing * Pillow for image printing
* `qrcode <https://github.com/lincolnloop/python-qrcode>`_ for the generation of QR-codes * qrcode for the generation of QR-codes
* `pyserial <https://github.com/pyserial/pyserial>`_ for serial printers * pyserial for serial printers
* `viivakoodi <https://github.com/kxepal/viivakoodi>`_ for the generation of barcodes
Documentation and Usage Documentation and Usage
----------------------- -----------------------
@@ -59,9 +62,9 @@ The basic usage is:
from escpos.printer import Usb from escpos.printer import Usb
""" Seiko Epson Corp. Receipt Printer (EPSON TM-T88III) """ """ Seiko Epson Corp. Receipt Printer (EPSON TM-T88III) """
p = Usb(0x04b8, 0x0202, 0, profile='TM-T88III') p = Usb(0x04b8, 0x0202, 0, profile="TM-T88III")
p.text('Hello World\n') p.text("Hello World\n")
p.image('logo.gif') p.image("logo.gif")
p.barcode('1324354657687', 'EAN13', 64, 2, '', '') p.barcode('1324354657687', 'EAN13', 64, 2, '', '')
p.cut() p.cut()
@@ -70,13 +73,4 @@ The full project-documentation is available on `Read the Docs <https://python-es
Contributing Contributing
------------ ------------
This project is open for any contribution! Please see `CONTRIBUTING.rst <http://python-escpos.readthedocs.io/en/latest/dev/contributing.html>`_ for more information. This project is open for any contribution! Please see CONTRIBUTING.rst for more information.
Disclaimer
----------
None of the vendors cited in this project agree or endorse any of the patterns or implementations.
Its names are used only to maintain context.

94
debian/changelog vendored Normal file
View File

@@ -0,0 +1,94 @@
python-escpos (1.0.8-3) unstable; urgency=medium
[ Christoph Heuel ]
* Fix text wrapping error after image
[ Patrick Kanzler ]
* moved .hgignore to .gitignore
* REFACTOR chained boolean expression in escpos
* REFACTOR use new-style class for Escpos
* REFACTOR style and PEP8, fixes #66
* REFACTOR do not shadow built-ins
* ADD requirements.txt and requirements to setup.py
* DOC, IMPROVE improve docstrings and add abstract method _raw to Escpos
* FIX constant definition for PC1252
-- Christoph Heuel <mail@christoph-heuel.net> Sun, 13 Dec 2015 14:27:27 +0100
python-escpos (1.0.8-2) unstable; urgency=low
* Imported source
* First debianization
-- Christoph Heuel <mail@christoph-heuel.net> Sat, 12 Dec 2015 19:13:33 +0100
python-escpos (1.0.8-1) unstable; urgency=low
[ Manuel F Martinez ]
* Added donation message
[ Joel Lehtonen ]
* Support for images vertically longer than 256 pixels
[ Christoph Heuel ]
* Fix mixed tabs/space error
[ Hark ]
* Prevent crash when using libusb0 printers
[ Manuel F Martinez ]
* Updated README and documentation
[ Christoph Heuel ]
* Add flush function
* Debian packaging
-- Manuel F Martinez <manpaz@gmail.com> Sat, 12 Dec 2015 20:59:53 +0100
python-escpos (1.0.7-1) unstable; urgency=low
[ Kristi ]
* Raising the right error when wrong charcode is used
[ Christoph Heuel ]
* After running 2to3 tool
* Fix for string operation
* Integer is needed, not float
* Add text wrapping
[ Manuel F Martinez ]
* Updated URL for the documentation
* Updated documentation URL to local wiki
* Updated setup URLs
[ Christoph Heuel ]
* Introduce new direct_image
[ Manuel F Martinez ]
* Fixed License version mismatch
[ Christoph Heuel ]
* Use unhexlify
* Hexlify text
[ Manuel F Martinez ]
* Updated accordingly to the wiki
[ ldos ]
* Extended params for serial printers
[ Manuel F Martinez ]
* Fixed issues with transparent images
* Updated project version
-- Manuel F Martinez <manpaz@gmail.com> Sat, 12 Dec 2015 20:59:53 +0100
python-escpos (1.0.4-1) unstable; urgency=medium
[ Manuel F Martinez ]
* Added density support
* Added quad support
* fixed code tabulators
* Updated version
-- Manuel F Martinez <manpaz@gmail.com> Sat, 12 Dec 2015 20:59:53 +0100

1
debian/compat vendored Normal file
View File

@@ -0,0 +1 @@
9

55
debian/control vendored Normal file
View File

@@ -0,0 +1,55 @@
Source: python-escpos
Section: unknown
Priority: optional
Maintainer: Christoph Heuel <mail@christoph-heuel.net>
Build-Depends: debhelper (>= 9), dh-python
Standards-Version: 3.9.5
Homepage: https://github.com/braveheuel/python-escpos
#Vcs-Git: git@github.com:braveheuel/python-escpos.git
#Vcs-Browser: https://github.com/braveheuel/python-escpos
Package: python-escpos
Architecture: all
Depends: ${misc:Depends}
Description: Python library to manipulate ESC/POS Printers (Python 2)
Python ESC/POS is a library which lets the user have access to all
those printers handled by ESC/POS commands, as defined by Epson,
from a Python application.
.
The standard usage is send raw text to the printer, but in also
helps the user to enhance the experience with those printers by
facilitating the bar code printing in many different standards,
as well as manipulating images so they can be printed as brand
logo or any other usage images migh have.
.
Text can be aligned/justified and fonts can be changed by size,
type and weight.
.
Also, this module handles some hardware functionalities like, cut
paper, carrier return, printer reset and others concerned to the
carriage alignment.
.
This package covers Python 2 code.
Package: python3-escpos
Architecture: all
Depends: ${misc:Depends}
Description: Python library to manipulate ESC/POS Printers (Python 3)
Python ESC/POS is a library which lets the user have access to all
those printers handled by ESC/POS commands, as defined by Epson,
from a Python application.
.
The standard usage is send raw text to the printer, but in also
helps the user to enhance the experience with those printers by
facilitating the bar code printing in many different standards,
as well as manipulating images so they can be printed as brand
logo or any other usage images migh have.
.
Text can be aligned/justified and fonts can be changed by size,
type and weight.
.
Also, this module handles some hardware functionalities like, cut
paper, carrier return, printer reset and others concerned to the
carriage alignment.
.
This package covers Python 3 code.

26
debian/copyright vendored Normal file
View File

@@ -0,0 +1,26 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: python-escpos
Source: https://github.com/manpaz/python-escpos
Files: *
Copyright: 2015 Manuel F Martinez <manpaz@bashlinux.com>
License: GPL v3
Files: debian/*
Copyright: 2015 Christoph Heuel <mail@christoph-heuel.net>
License: GPL-2+
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
.
On Debian systems, the complete text of the GNU General
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".

1
debian/docs vendored Normal file
View File

@@ -0,0 +1 @@
README.rst

6
debian/gbp.conf vendored Normal file
View File

@@ -0,0 +1,6 @@
[DEFAULT]
upstream-branch=master
upstream-tree = tag
debian-branch = debian/jessie
upstream-tag = v%(version)s
debian-tag = v%(version)s

28
debian/rules vendored Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/make -f
# See debhelper(7) (uncomment to enable)
# output every command that modifies files on the build system.
#DH_VERBOSE = 1
# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/*
DPKG_EXPORT_BUILDFLAGS = 1
include /usr/share/dpkg/default.mk
# see FEATURE AREAS in dpkg-buildflags(1)
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# see ENVIRONMENT in dpkg-buildflags(1)
# package maintainers to append CFLAGS
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
# package maintainers to append LDFLAGS
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
# main packaging script based on dh7 syntax
%:
dh $@ --with python2,python3 --buildsystem=pybuild
# debmake generated override targets
# This is example for Cmake (See http://bugs.debian.org/641051 )
#override_dh_auto_configure:
# dh_auto_configure -- \
# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)

1
debian/source/format vendored Normal file
View File

@@ -0,0 +1 @@
3.0 (quilt)

View File

@@ -1,19 +0,0 @@
#!/bin/sh
GENLIST=$(git shortlog -s -n | cut -f2 | sort -f)
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,4 +5,3 @@ pyserial
sphinx-rtd-theme sphinx-rtd-theme
setuptools-scm setuptools-scm
docutils>=0.12 docutils>=0.12
viivakoodi

View File

@@ -1,7 +1,6 @@
***** *****
Usage Usage
***** *****
:Last Reviewed: 2017-06-10
Define your printer Define your printer
------------------- -------------------
@@ -67,7 +66,7 @@ IP by DHCP or you set it manually.
:: ::
Epson = printer.Network('192.168.1.99') Epson = printer.Network("192.168.1.99")
Serial printer Serial printer
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
@@ -81,7 +80,7 @@ to.
:: ::
Epson = printer.Serial('/dev/tty0') Epson = printer.Serial("/dev/tty0")
Other printers Other printers
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
@@ -93,7 +92,7 @@ passing the device node name.
:: ::
Epson = printer.File('/dev/usb/lp1') Epson = printer.File("/dev/usb/lp1")
The default is "/dev/usb/lp0", so if the printer is located on that The default is "/dev/usb/lp0", so if the printer is located on that
node, then you don't necessary need to pass the node name. node, then you don't necessary need to pass the node name.
@@ -110,11 +109,11 @@ on a USB interface.
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """ """ Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
Epson = printer.Usb(0x04b8,0x0202) Epson = printer.Usb(0x04b8,0x0202)
# Print text # Print text
Epson.text('Hello World\n') Epson.text("Hello World\n")
# Print image # Print image
Epson.image('logo.gif') Epson.image("logo.gif")
# Print QR Code # Print QR Code
Epson.qr('You can readme from your smartphone') Epson.qr("You can readme from your smartphone")
# Print barcode # Print barcode
Epson.barcode('1324354657687','EAN13',64,2,'','') Epson.barcode('1324354657687','EAN13',64,2,'','')
# Cut paper # Cut paper
@@ -134,13 +133,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
@@ -148,9 +147,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
@@ -158,34 +157,23 @@ 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 has to be one of the The only required paramter is ``type``. The value of this should be one of the
printers defined in :doc:`/user/printers`. printers defined in :doc:`/user/printers`.
The rest of the given parameters will be passed on to the initialization of the printer class. The rest of the parameters are whatever you want to pass to the printer.
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
------------------- -------------------
@@ -214,7 +202,7 @@ advantage of the fact that `_raw()` accepts binary strings.)
from escpos import printer from escpos import printer
p = printer.Serial() # adapt this to your printer model p = printer.Serial() # adapt this to your printer model
file = open('binary-blob.bin', 'rb') # read in the file containing your commands in binary-mode file = open("binary-blob.bin", "rb") # read in the file containing your commands in binary-mode
data = file.read() data = file.read()
file.close() file.close()
@@ -273,8 +261,8 @@ This is probably best explained by an example:
d = Dummy() d = Dummy()
# create ESC/POS for the print job, this should go really fast # create ESC/POS for the print job, this should go really fast
d.text('This is my image:\n') d.text("This is my image:\n")
d.image('funny_cat.png') d.image("funny_cat.png")
d.cut() d.cut()
# send code to printer # send code to printer

View File

@@ -1,17 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
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')

View File

@@ -1,17 +1,13 @@
# -*- coding: utf-8 -*-
"""Prints code page tables. """Prints code page tables.
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import print_function
import six
import sys import sys
from escpos import printer from escpos import printer
from escpos.constants import CODEPAGE_CHANGE, CTL_CR, CTL_FF, CTL_HT, CTL_LF, CTL_VT, ESC from escpos.constants import CODEPAGE_CHANGE, ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT
import six
def main(): def main():
@@ -21,9 +17,9 @@ def main():
for codepage in sys.argv[1:] or ['USA']: for codepage in sys.argv[1:] or ['USA']:
dummy.set(height=2, width=2) dummy.set(height=2, width=2)
dummy._raw(codepage + '\n\n\n') dummy._raw(codepage + "\n\n\n")
print_codepage(dummy, codepage) print_codepage(dummy, codepage)
dummy._raw('\n\n') dummy._raw("\n\n")
dummy.cut() dummy.cut()
@@ -34,22 +30,22 @@ def print_codepage(printer, codepage):
if codepage.isdigit(): if codepage.isdigit():
codepage = int(codepage) codepage = int(codepage)
printer._raw(CODEPAGE_CHANGE + six.int2byte(codepage)) printer._raw(CODEPAGE_CHANGE + six.int2byte(codepage))
printer._raw('after') printer._raw("after")
else: else:
printer.charcode(codepage) printer.charcode(codepage)
sep = '' sep = ""
# Table header # Table header
printer.set(font='b') printer.set(text_type='B')
printer._raw(' {}\n'.format(sep.join(map(lambda s: hex(s)[2:], range(0, 16))))) printer._raw(" {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16)))))
printer.set() printer.set()
# The table # The table
for x in range(0, 16): for x in range(0, 16):
# First column # First column
printer.set(font='b') printer.set(text_type='B')
printer._raw('{} '.format(hex(x)[2:])) printer._raw("{} ".format(hex(x)[2:]))
printer.set() printer.set()
for y in range(0, 16): for y in range(0, 16):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -1,10 +0,0 @@
# 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.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
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, center=True)

View File

@@ -1,15 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
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')

View File

@@ -1,130 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 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 absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import calendar
import json
import os
import time
import urllib
from datetime import datetime
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

@@ -9,13 +9,4 @@ universal=1
[flake8] [flake8]
exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py
max-line-length = 120 max-line-length = 120
accept-encoding = utf-8, utf-16 # future-imports = absolute_import, division, print_function, unicode_literals # we are not there yet
require-code = True
# FI12 __future__ import "with_statement" missing
# FI15 __future__ import "generator_stop" missing
# FI16 __future__ import "nested_scopes" missing
# FI17 __future__ import "generators" missing
# FI5x __future__ import "xxx" present
# I101 Imported names are in the wrong order.
ignore = FI12,FI15,FI16,FI17,FI5, I101
copyright-check = True

View File

@@ -1,19 +1,13 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
# from __future__ import unicode_literals
import os import os
import sys import sys
from setuptools import find_packages, setup from setuptools import find_packages, setup
from setuptools.command.test import test as test_command
base_dir = os.path.dirname(__file__) base_dir = os.path.dirname(__file__)
src_dir = os.path.join(base_dir, 'src') src_dir = os.path.join(base_dir, "src")
# When executing the setup.py, we need to be able to import ourselves, this # When executing the setup.py, we need to be able to import ourselves, this
# means that we need to add the src/ directory to the sys.path. # means that we need to add the src/ directory to the sys.path.
@@ -25,6 +19,33 @@ def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read() return open(os.path.join(os.path.dirname(__file__), fname)).read()
class Tox(test_command):
"""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"""
test_command.initialize_options(self)
self.tox_args = None
def finalize_options(self):
"""finalize user-options"""
test_command.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)
setuptools_scm_template = """\ setuptools_scm_template = """\
# coding: utf-8 # coding: utf-8
# file generated by setuptools_scm # file generated by setuptools_scm
@@ -47,6 +68,7 @@ setup(
url='https://github.com/python-escpos/python-escpos', url='https://github.com/python-escpos/python-escpos',
download_url='https://github.com/python-escpos/python-escpos/archive/master.zip', download_url='https://github.com/python-escpos/python-escpos/archive/master.zip',
description='Python library to manipulate ESC/POS Printers', description='Python library to manipulate ESC/POS Printers',
bugtrack_url='https://github.com/python-escpos/python-escpos/issues',
license='MIT', license='MIT',
long_description=read('README.rst'), long_description=read('README.rst'),
author='Manuel F Martinez and others', author='Manuel F Martinez and others',
@@ -61,8 +83,8 @@ setup(
'receipt,', 'receipt,',
], ],
platforms='any', platforms='any',
package_dir={'': 'src'}, package_dir={"": "src"},
packages=find_packages(where='src', exclude=['tests', 'tests.*']), packages=find_packages(where="src", exclude=["tests", "tests.*"]),
package_data={'': ['COPYING', 'src/escpos/capabilities.json']}, package_data={'': ['COPYING', 'src/escpos/capabilities.json']},
include_package_data=True, include_package_data=True,
classifiers=[ classifiers=[
@@ -75,10 +97,9 @@ setup(
'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 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',
@@ -91,11 +112,10 @@ setup(
'pyserial', 'pyserial',
'six', 'six',
'appdirs', 'appdirs',
'PyYAML', 'pyyaml',
'argparse', 'argparse',
'argcomplete', 'argcomplete',
'future', 'future'
'viivakoodi>=0.8'
], ],
setup_requires=[ setup_requires=[
'setuptools_scm', 'setuptools_scm',
@@ -103,15 +123,16 @@ setup(
tests_require=[ tests_require=[
'jaconv', 'jaconv',
'tox', 'tox',
'pytest!=3.2.0,!=3.3.0', 'pytest',
'pytest-cov', 'pytest-cov',
'pytest-mock', 'pytest-mock',
'nose', 'nose',
'scripttest', 'scripttest',
'mock', 'mock',
'hypothesis!=3.56.9', 'hypothesis',
'flake8' 'flake8'
], ],
cmdclass={'test': Tox},
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'python-escpos = escpos.cli:main' 'python-escpos = escpos.cli:main'

View File

@@ -7,7 +7,7 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
__all__ = ['constants', 'escpos', 'exceptions', 'printer'] __all__ = ["constants", "escpos", "exceptions", "printer"]
try: try:
from .version import version as __version__ # noqa from .version import version as __version__ # noqa

View File

@@ -1,62 +1,22 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import logging
import pickle
import platform
import re import re
import time
from os import environ, path
from tempfile import gettempdir
import six import six
from os import environ, path
import yaml import yaml
logging.basicConfig()
logger = logging.getLogger(__name__)
pickle_dir = environ.get('ESCPOS_CAPABILITIES_PICKLE_DIR', gettempdir())
pickle_path = path.join(pickle_dir, '{v}.capabilities.pickle'.format(v=platform.python_version()))
capabilities_path = environ.get(
'ESCPOS_CAPABILITIES_FILE',
path.join(path.dirname(__file__), 'capabilities.json'))
# Load external printer database # Load external printer database
t0 = time.time() if 'ESCPOS_CAPABILITIES_FILE' in environ:
logger.debug('Using capabilities from file: %s', capabilities_path) file_path = environ['ESCPOS_CAPABILITIES_FILE']
if path.exists(pickle_path):
if path.getmtime(capabilities_path) > path.getmtime(pickle_path):
logger.debug('Found a more recent capabilities file')
full_load = True
else:
full_load = False
logger.debug('Loading capabilities from pickle in %s', pickle_path)
with open(pickle_path, 'rb') as cf:
CAPABILITIES = pickle.load(cf)
else: else:
logger.debug('Capabilities pickle file not found: %s', pickle_path) file_path = path.join(path.dirname(__file__), 'capabilities.json')
full_load = True
if full_load:
logger.debug('Loading and pickling capabilities')
with open(capabilities_path) as cp, open(pickle_path, 'wb') as pp:
CAPABILITIES = yaml.load(cp)
pickle.dump(CAPABILITIES, pp, protocol=2)
logger.debug('Finished loading capabilities took %.2fs', time.time() - t0)
with open(file_path) as f:
CAPABILITIES = yaml.load(f)
PROFILES = CAPABILITIES['profiles'] PROFILES = CAPABILITIES['profiles']
class NotSupported(Exception): class NotSupported(Exception):
"""Raised if a requested feature is not supported by the """Raised if a requested feature is not suppored by the
printer profile. printer profile.
""" """
pass pass
@@ -99,7 +59,7 @@ class BaseProfile(object):
return self.features.get(feature) return self.features.get(feature)
def get_code_pages(self): def get_code_pages(self):
"""Return the support code pages as a ``{name: index}`` dict. """Return the support code pages as a {name: index} dict.
""" """
return {v: k for k, v in self.codePages.items()} return {v: k for k, v in self.codePages.items()}

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# PYTHON_ARGCOMPLETE_OK # PYTHON_ARGCOMPLETE_OK
""" CLI """ CLI
@@ -16,15 +15,13 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import argparse import argparse
import sys
try: try:
import argcomplete import argcomplete
except ImportError: except ImportError:
# this CLI works nevertheless without argcomplete # this CLI works nevertheless without argcomplete
pass # noqa pass # noqa
import sys
import six import six
from . import config from . import config
from . import version from . import version
@@ -553,7 +550,7 @@ def main():
# print command with args # print command with args
getattr(printer, target_command)(**command_arguments) getattr(printer, target_command)(**command_arguments)
if target_command in REQUIRES_NEWLINE: if target_command in REQUIRES_NEWLINE:
printer.text('\n') printer.text("\n")
else: else:
command_arguments['printer'] = printer command_arguments['printer'] = printer
globals()[target_command](**command_arguments) globals()[target_command](**command_arguments)

View File

@@ -1,11 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from .capabilities import CAPABILITIES from .capabilities import CAPABILITIES

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" ESC/POS configuration manager. """ ESC/POS configuration manager.
This module contains the implentations of abstract base class :py:class:`Config`. This module contains the implentations of abstract base class :py:class:`Config`.
@@ -11,13 +10,11 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import appdirs import appdirs
import yaml import yaml
from . import exceptions
from . import printer from . import printer
from . import exceptions
class Config(object): class Config(object):

View File

@@ -64,11 +64,6 @@ _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
@@ -76,90 +71,51 @@ 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_STYLE = { TXT_4SQUARE = ESC + b'!\x30' # Quad area text
'bold': { TXT_UNDERL_OFF = ESC + b'\x2d\x00' # Underline font OFF
False: ESC + b'\x45\x00', # Bold font OFF TXT_UNDERL_ON = ESC + b'\x2d\x01' # Underline font 1-dot ON
True: ESC + b'\x45\x01' # Bold font ON TXT_UNDERL2_ON = ESC + b'\x2d\x02' # Underline font 2-dot ON
}, TXT_BOLD_OFF = ESC + b'\x45\x00' # Bold font OFF
'underline': { TXT_BOLD_ON = ESC + b'\x45\x01' # Bold font ON
0: ESC + b'\x2d\x00', # Underline font OFF TXT_ALIGN_LT = ESC + b'\x61\x00' # Left justification
1: ESC + b'\x2d\x01', # Underline font 1-dot ON TXT_ALIGN_CT = ESC + b'\x61\x01' # Centering
2: ESC + b'\x2d\x02' # Underline font 2-dot ON TXT_ALIGN_RT = ESC + b'\x61\x02' # Right justification
}, TXT_INVERT_ON = GS + b'\x42\x01' # Inverse Printing ON
'size': { TXT_INVERT_OFF = GS + b'\x42\x00' # Inverse Printing OFF
'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 = {
@@ -225,24 +181,6 @@ BARCODE_TYPE_B = {
'GS1 DATABAR EXPANDED': _SET_BARCODE_TYPE(78), 'GS1 DATABAR EXPANDED': _SET_BARCODE_TYPE(78),
} }
BARCODE_FORMATS = {
'UPC-A': ([(11, 12)], "^[0-9]{11,12}$"),
'UPC-E': ([(7, 8), (11, 12)], "^([0-9]{7,8}|[0-9]{11,12})$"),
'EAN13': ([(12, 13)], "^[0-9]{12,13}$"),
'EAN8': ([(7, 8)], "^[0-9]{7,8}$"),
'CODE39': ([(1, 255)], "^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"),
'ITF': ([(2, 255)], "^([0-9]{2})+$"),
'NW7': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"),
'CODABAR': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), # Same as NW7
'CODE93': ([(1, 255)], "^[\\x00-\\x7F]+$"),
'CODE128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"),
'GS1-128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), # same as CODE128
'GS1 DATABAR OMNIDIRECTIONAL': ([(13,13)], "^[0-9]{13}$"),
'GS1 DATABAR TRUNCATED': ([(13,13)], "^[0-9]{13}$"), # same as GS1 omnidirectional
'GS1 DATABAR LIMITED': ([(13,13)], "^[01][0-9]{12}$"),
'GS1 DATABAR EXPANDED': ([(2,255)], "^\([0-9][A-Za-z0-9 \!\"\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$"),
}
BARCODE_TYPES = { BARCODE_TYPES = {
'A': BARCODE_TYPE_A, 'A': BARCODE_TYPE_A,
'B': BARCODE_TYPE_B, 'B': BARCODE_TYPE_B,
@@ -268,11 +206,13 @@ 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
# Status Command # Printing Density
RT_STATUS = DLE + EOT PD_N50 = GS + b'\x7c\x00' # Printing Density -50%
RT_STATUS_ONLINE = RT_STATUS + b'\x01' PD_N37 = GS + b'\x7c\x01' # Printing Density -37.5%
RT_STATUS_PAPER = RT_STATUS + b'\x04' PD_N25 = GS + b'\x7c\x02' # Printing Density -25%
RT_MASK_ONLINE = 8 PD_N12 = GS + b'\x7c\x03' # Printing Density -12.5%
RT_MASK_PAPER = 18 PD_0 = GS + b'\x7c\x04' # Printing Density 0%
RT_MASK_LOWPAPER = 30 PD_P50 = GS + b'\x7c\x08' # Printing Density +50%
RT_MASK_NOPAPER = 114 PD_P37 = GS + b'\x7c\x07' # Printing Density +37.5%
PD_P25 = GS + b'\x7c\x06' # Printing Density +25%
PD_P12 = GS + b'\x7c\x05' # Printing Density +12.5%

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" Main class """ Main class
@@ -15,41 +15,32 @@ 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 os
import textwrap
import time
from abc import ABCMeta, abstractmethod # abstract base class support
from re import match as re_match
import barcode
from barcode.writer import ImageWriter
import qrcode import qrcode
import textwrap
import six import six
from .capabilities import BARCODE_B, get_profile
from .constants import BARCODE_FONT_A, BARCODE_FONT_B, BARCODE_FORMATS
from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW
from .constants import BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH
from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT
from .constants import CTL_VT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON
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 HW_INIT, HW_RESET, HW_SELECT from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO, BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH
from .constants import LINESPACING_FUNCS, LINESPACING_RESET from .constants import TXT_ALIGN_CT, TXT_ALIGN_LT, TXT_ALIGN_RT, BARCODE_FONT_A, BARCODE_FONT_B
from .constants import LINE_DISPLAY_OPEN, LINE_DISPLAY_CLEAR, LINE_DISPLAY_CLOSE from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW
from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO from .constants import TXT_HEIGHT, TXT_WIDTH, TXT_SIZE, TXT_NORMAL, TXT_SMOOTH_OFF, TXT_SMOOTH_ON
from .constants import RT_MASK_ONLINE, RT_STATUS_ONLINE from .constants import TXT_FLIP_OFF, TXT_FLIP_ON, TXT_2WIDTH, TXT_2HEIGHT, TXT_4SQUARE
from .constants import RT_STATUS_PAPER, RT_MASK_PAPER, RT_MASK_LOWPAPER, RT_MASK_NOPAPER from .constants import TXT_UNDERL_OFF, TXT_UNDERL_ON, TXT_BOLD_OFF, TXT_BOLD_ON, SET_FONT, TXT_UNDERL2_ON
from .constants import SET_FONT from .constants import TXT_INVERT_OFF, TXT_INVERT_ON, LINESPACING_FUNCS, LINESPACING_RESET
from .constants import TXT_SIZE, TXT_NORMAL from .constants import PD_0, PD_N12, PD_N25, PD_N37, PD_N50, PD_P50, PD_P37, PD_P25, PD_P12
from .constants import TXT_STYLE from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT
from .exceptions import BarcodeCodeError, BarcodeSizeError, BarcodeTypeError from .constants import HW_RESET, HW_SELECT, HW_INIT
from .exceptions import CashDrawerError, ImageWidthError from .constants import CTL_VT, CTL_HT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON
from .exceptions import SetVariableError, TabPosError
from .image import EscposImage from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError
from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError
from .magicencode import MagicEncode from .magicencode import MagicEncode
from abc import ABCMeta, abstractmethod # abstract base class support
from escpos.image import EscposImage
from escpos.capabilities import get_profile, BARCODE_B
@six.add_metaclass(ABCMeta) @six.add_metaclass(ABCMeta)
class Escpos(object): class Escpos(object):
@@ -82,14 +73,8 @@ class Escpos(object):
""" """
pass pass
def _read(self): def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster",
""" Returns a NotImplementedError if the instance of the class doesn't override this method. fragment_height=960):
:raises NotImplementedError
"""
raise NotImplementedError()
def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl='bitImageRaster',
fragment_height=960, center=False):
""" Print an image """ Print an image
You can select whether the printer should print in high density or not. The default value is high density. You can select whether the printer should print in high density or not. The default value is high density.
@@ -110,26 +95,10 @@ class Escpos(object):
:param high_density_horizontal: print in high density in horizontal direction *default:* True :param high_density_horizontal: print in high density in horizontal direction *default:* True
:param impl: choose image printing mode between `bitImageRaster`, `graphics` or `bitImageColumn` :param impl: choose image printing mode between `bitImageRaster`, `graphics` or `bitImageColumn`
:param fragment_height: Images larger than this will be split into multiple fragments *default:* 960 :param fragment_height: Images larger than this will be split into multiple fragments *default:* 960
:param center: Center image horizontally *default:* False
""" """
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))
if center:
im.center(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:
@@ -140,14 +109,14 @@ class Escpos(object):
fragment_height=fragment_height) fragment_height=fragment_height)
return return
if impl == 'bitImageRaster': if impl == "bitImageRaster":
# GS v 0, raster format bit image # GS v 0, raster format bit image
density_byte = (0 if high_density_horizontal else 1) + (0 if high_density_vertical else 2) density_byte = (0 if high_density_horizontal else 1) + (0 if high_density_vertical else 2)
header = GS + b'v0' + six.int2byte(density_byte) + self._int_low_high(im.width_bytes, 2) +\ header = GS + b"v0" + six.int2byte(density_byte) + self._int_low_high(im.width_bytes, 2) +\
self._int_low_high(im.height, 2) self._int_low_high(im.height, 2)
self._raw(header + im.to_raster_format()) self._raw(header + im.to_raster_format())
if impl == 'graphics': if impl == "graphics":
# GS ( L raster format graphics # GS ( L raster format graphics
img_header = self._int_low_high(im.width, 2) + self._int_low_high(im.height, 2) img_header = self._int_low_high(im.width, 2) + self._int_low_high(im.height, 2)
tone = b'0' tone = b'0'
@@ -159,14 +128,14 @@ class Escpos(object):
self._image_send_graphics_data(b'0', b'p', header + raster_data) self._image_send_graphics_data(b'0', b'p', header + raster_data)
self._image_send_graphics_data(b'0', b'2', b'') self._image_send_graphics_data(b'0', b'2', b'')
if impl == 'bitImageColumn': if impl == "bitImageColumn":
# ESC *, column format bit image # ESC *, column format bit image
density_byte = (1 if high_density_horizontal else 0) + (32 if high_density_vertical else 0) density_byte = (1 if high_density_horizontal else 0) + (32 if high_density_vertical else 0)
header = ESC + b'*' + six.int2byte(density_byte) + self._int_low_high(im.width, 2) header = ESC + b"*" + six.int2byte(density_byte) + self._int_low_high(im.width, 2)
outp = [ESC + b'3' + six.int2byte(16)] # Adjust line-feed size outp = [ESC + b"3" + six.int2byte(16)] # Adjust line-feed size
for blob in im.to_column_format(high_density_vertical): for blob in im.to_column_format(high_density_vertical):
outp.append(header + blob + b'\n') outp.append(header + blob + b"\n")
outp.append(ESC + b'2') # Reset line-feed size outp.append(ESC + b"2") # Reset line-feed size
self._raw(b''.join(outp)) self._raw(b''.join(outp))
def _image_send_graphics_data(self, m, fn, data): def _image_send_graphics_data(self, m, fn, data):
@@ -180,8 +149,7 @@ class Escpos(object):
header = self._int_low_high(len(data) + 2, 2) header = self._int_low_high(len(data) + 2, 2)
self._raw(GS + b'(L' + header + m + fn + data) self._raw(GS + b'(L' + header + m + fn + data)
def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2, def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2, native=False):
native=False, center=False, impl='bitImageRaster'):
""" Print QR Code for the provided string """ Print QR Code for the provided string
:param content: The content of the code. Numeric data will be more efficiently compacted. :param content: The content of the code. Numeric data will be more efficiently compacted.
@@ -193,22 +161,21 @@ class Escpos(object):
by all printers). by all printers).
:param native: True to render the code on the printer, False to render the code as an image and send it to the :param native: True to render the code on the printer, False to render the code as an image and send it to the
printer (Default) printer (Default)
:param center: Centers the code *default:* False
""" """
# Basic validation # Basic validation
if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]: if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]:
raise ValueError('Invalid error correction level') raise ValueError("Invalid error correction level")
if not 1 <= size <= 16: if not 1 <= size <= 16:
raise ValueError('Invalid block size (must be 1-16)') raise ValueError("Invalid block size (must be 1-16)")
if model not in [QR_MODEL_1, QR_MODEL_2, QR_MICRO]: if model not in [QR_MODEL_1, QR_MODEL_2, QR_MICRO]:
raise ValueError('Invalid QR model (must be one of QR_MODEL_1, QR_MODEL_2, QR_MICRO)') raise ValueError("Invalid QR model (must be one of QR_MODEL_1, QR_MODEL_2, QR_MICRO)")
if content == '': if content == "":
# Handle edge case by printing nothing. # Handle edge case by printing nothing.
return return
if not native: if not native:
# Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image # Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image
if model != QR_MODEL_2: if model != QR_MODEL_2:
raise ValueError('Invalid QR model for qrlib rendering (must be QR_MODEL_2)') raise ValueError("Invalid QR model for qrlib rendering (must be QR_MODEL_2)")
python_qr_ec = { python_qr_ec = {
QR_ECLEVEL_H: qrcode.constants.ERROR_CORRECT_H, QR_ECLEVEL_H: qrcode.constants.ERROR_CORRECT_H,
QR_ECLEVEL_L: qrcode.constants.ERROR_CORRECT_L, QR_ECLEVEL_L: qrcode.constants.ERROR_CORRECT_L,
@@ -219,18 +186,10 @@ class Escpos(object):
qr_code.add_data(content) qr_code.add_data(content)
qr_code.make(fit=True) qr_code.make(fit=True)
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, center=center, impl=impl)
self.text('\n')
self.text('\n')
return return
if center:
raise NotImplementedError('Centering not implemented for native QR rendering')
# Native 2D code printing # Native 2D code printing
cn = b'1' # Code type for QR code cn = b'1' # Code type for QR code
# Select model: 1, 2 or micro. # Select model: 1, 2 or micro.
@@ -252,7 +211,7 @@ class Escpos(object):
:param m: Modifier/variant for function. Often '0' where used. :param m: Modifier/variant for function. Often '0' where used.
""" """
if len(m) > 1 or len(cn) != 1 or len(fn) != 1: if len(m) > 1 or len(cn) != 1 or len(fn) != 1:
raise ValueError('cn and fn must be one byte each.') raise ValueError("cn and fn must be one byte each.")
header = self._int_low_high(len(data) + len(m) + 2, 2) header = self._int_low_high(len(data) + len(m) + 2, 2)
self._raw(GS + b'(k' + header + cn + fn + m + data) self._raw(GS + b'(k' + header + cn + fn + m + data)
@@ -265,16 +224,16 @@ 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 bytes') raise ValueError("Can only output 1-4 byes")
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} bytes'.format(max_input, out_bytes)) raise ValueError("Number too large. Can only output up to {0} in {1} byes".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)
inp_number //= 256 inp_number //= 256
return outp return outp
def charcode(self, code='AUTO'): def charcode(self, code="AUTO"):
""" Set Character Code Table """ Set Character Code Table
Sets the control sequence from ``CHARCODE`` in :py:mod:`escpos.constants` as active. It will be sent with Sets the control sequence from ``CHARCODE`` in :py:mod:`escpos.constants` as active. It will be sent with
@@ -284,47 +243,22 @@ class Escpos(object):
:param code: Name of CharCode :param code: Name of CharCode
:raises: :py:exc:`~escpos.exceptions.CharCodeError` :raises: :py:exc:`~escpos.exceptions.CharCodeError`
""" """
if code.upper() == 'AUTO': if code.upper() == "AUTO":
self.magic.force_encoding(False) self.magic.force_encoding(False)
else: else:
self.magic.force_encoding(code) self.magic.force_encoding(code)
@staticmethod def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A",
def check_barcode(bc, code): align_ct=True, function_type=None):
"""
This method checks if the barcode is in the proper format.
The validation concerns the barcode length and the set of characters, but won't compute/validate any checksum.
The full set of requirement for each barcode type is available in the ESC/POS documentation.
As an example, using EAN13, the barcode `12345678901` will be correct, because it can be rendered by the
printer. But it does not suit the EAN13 standard, because the checksum digit is missing. Adding a wrong
checksum in the end will also be considered correct, but adding a letter won't (EAN13 is numeric only).
.. todo:: Add a method to compute the checksum for the different standards
.. todo:: For fixed-length standards with mandatory checksum (EAN, UPC),
compute and add the checksum automatically if missing.
:param bc: barcode format, see :py:func`~escpos.Escpos.barcode`
:param code: alphanumeric data to be printed as bar code, see :py:func`~escpos.Escpos.barcode`
:return: bool
"""
if bc not in BARCODE_FORMATS:
return False
bounds, regex = BARCODE_FORMATS[bc]
return any(bound[0] <= len(code) <= bound[1] for bound in bounds) and re_match(regex, code)
def barcode(self, code, bc, height=64, width=3, pos='BELOW', font='A',
align_ct=True, function_type=None, check=True):
""" Print Barcode """ Print Barcode
This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to 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. By default, this method will check whether your barcode text is correct, that is be supported by the unit. Currently you have to check manually whether your barcode text is correct. Uncorrect
the characters and lengths are supported by ESCPOS. Call the method with `check=False` to disable the check, but barcodes may lead to unexpected printer behaviour. There are two forms of the barcode function. Type A is
note that uncorrect barcodes may lead to unexpected printer behaviour. default but has fewer barcodes, while type B has some more to choose from.
There are two forms of the barcode function. Type A is default but has fewer barcodes,
while type B has some more to choose from. .. 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 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 will not be printed if it is outside of the printable area. (Which should be impossible with this method, so
@@ -392,10 +326,6 @@ class Escpos(object):
function based on the current profile. function based on the current profile.
*default*: A *default*: A
:param check: If this parameter is True, the barcode format will be checked to ensure it meets the bc
requirements as defigned in the esc/pos documentation. See py:func:`~escpos.Escpos.check_barcode`
for more information. *default*: True.
:raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`, :raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`,
:py:exc:`~escpos.exceptions.BarcodeTypeError`, :py:exc:`~escpos.exceptions.BarcodeTypeError`,
:py:exc:`~escpos.exceptions.BarcodeCodeError` :py:exc:`~escpos.exceptions.BarcodeCodeError`
@@ -408,60 +338,53 @@ class Escpos(object):
if bc in BARCODE_TYPES['B']: if bc in BARCODE_TYPES['B']:
if not self.profile.supports(BARCODE_B): if not self.profile.supports(BARCODE_B):
raise BarcodeTypeError(( raise BarcodeTypeError((
"Barcode type '{bc}' not supported for " "Barcode type '{bc} not supported for "
'the current printer profile').format(bc=bc)) "the current printer profile").format(bc=bc))
function_type = 'B' function_type = 'B'
else: else:
raise BarcodeTypeError(( raise BarcodeTypeError((
"Barcode type '{bc}' is not valid").format(bc=bc)) "Barcode type '{bc} is not valid").format(bc=bc))
bc_types = BARCODE_TYPES[function_type.upper()] bc_types = BARCODE_TYPES[function_type.upper()]
if bc.upper() not in bc_types.keys(): if bc.upper() not in bc_types.keys():
raise BarcodeTypeError(( raise BarcodeTypeError((
"Barcode '{bc}' not valid for barcode function type " "Barcode type '{bc}' not valid for barcode function type "
'{function_type}').format( "{function_type}").format(
bc=bc, bc=bc,
function_type=function_type,) function_type=function_type,
) ))
if check and not self.check_barcode(bc, code):
raise BarcodeCodeError((
"Barcode '{code}' not in a valid format for type '{bc}'").format(
code=code,
bc=bc,
))
# Align Bar Code() # Align Bar Code()
if align_ct: if align_ct:
self._raw(TXT_STYLE['align']['center']) self._raw(TXT_ALIGN_CT)
# Height # Height
if 1 <= height <= 255: if 1 <= height <= 255:
self._raw(BARCODE_HEIGHT + six.int2byte(height)) self._raw(BARCODE_HEIGHT + six.int2byte(height))
else: else:
raise BarcodeSizeError('height = {height}'.format(height=height)) raise BarcodeSizeError("height = {height}".format(height=height))
# Width # Width
if 2 <= width <= 6: if 2 <= width <= 6:
self._raw(BARCODE_WIDTH + six.int2byte(width)) self._raw(BARCODE_WIDTH + six.int2byte(width))
else: else:
raise BarcodeSizeError('width = {width}'.format(width=width)) raise BarcodeSizeError("width = {width}".format(width=width))
# Font # Font
if font.upper() == 'B': if font.upper() == "B":
self._raw(BARCODE_FONT_B) self._raw(BARCODE_FONT_B)
else: # DEFAULT FONT: A else: # DEFAULT FONT: A
self._raw(BARCODE_FONT_A) self._raw(BARCODE_FONT_A)
# Position # Position
if pos.upper() == 'OFF': if pos.upper() == "OFF":
self._raw(BARCODE_TXT_OFF) self._raw(BARCODE_TXT_OFF)
elif pos.upper() == 'BOTH': elif pos.upper() == "BOTH":
self._raw(BARCODE_TXT_BTH) self._raw(BARCODE_TXT_BTH)
elif pos.upper() == 'ABOVE': elif pos.upper() == "ABOVE":
self._raw(BARCODE_TXT_ABV) self._raw(BARCODE_TXT_ABV)
else: # DEFAULT POSITION: BELOW else: # DEFAULT POSITION: BELOW
self._raw(BARCODE_TXT_BLW) self._raw(BARCODE_TXT_BLW)
self._raw(bc_types[bc.upper()]) self._raw(bc_types[bc.upper()])
if function_type.upper() == 'B': if function_type.upper() == "B":
self._raw(six.int2byte(len(code))) self._raw(six.int2byte(len(code)))
# Print Code # Print Code
@@ -470,35 +393,9 @@ class Escpos(object):
else: else:
raise BarcodeCodeError() raise BarcodeCodeError()
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)
with open(os.devnull, 'wb') as nullfile:
my_code.write(nullfile, {
'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
@@ -511,112 +408,132 @@ 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', bold=False, underline=0, width=1, def set(self, align='left', font='a', text_type='normal', 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', referring to fonts 0 and 1. special values 'a' or 'b', refering to fonts 0 and 1.
:param bold: text in bold, *default*: False :param text_type: text type, possible values are:
:param underline: underline mode for text, decimal range 0-2, *default*: 0
:param double_height: doubles the height of the text * B for bold
:param double_width: doubles the width of the text * U for underlined
:param custom_size: uses custom size specified by width and height * U2 for underlined, version 2
parameters. Cannot be used with double_width or double_height. * BU for bold and underlined
:param width: text width multiplier when custom_size is used, decimal range 1-8, *default*: 1 * BU2 for bold and underlined, version 2
:param height: text height multiplier when custom_size is used, decimal range 1-8, *default*: 1 * NORMAL for normal text
*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 font: str
:type invert: bool :type invert: bool
:type bold: bool
:type underline: bool
:type smooth: bool
:type flip: bool
:type custom_size: bool
:type double_width: bool
:type double_height: bool
:type align: str
:type width: int
:type height: int
:type density: int
""" """
# Width
if custom_size: if height == 2 and width == 2:
if 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and isinstance(height, int):
size_byte = TXT_STYLE['width'][width] + TXT_STYLE['height'][height]
self._raw(TXT_SIZE + six.int2byte(size_byte))
else:
raise SetVariableError()
else:
self._raw(TXT_NORMAL) self._raw(TXT_NORMAL)
if double_width and double_height: self._raw(TXT_4SQUARE)
self._raw(TXT_STYLE['size']['2x']) elif height == 2 and width == 1:
elif double_width: self._raw(TXT_NORMAL)
self._raw(TXT_STYLE['size']['2w']) self._raw(TXT_2HEIGHT)
elif double_height: elif width == 2 and height == 1:
self._raw(TXT_STYLE['size']['2h']) self._raw(TXT_NORMAL)
else: self._raw(TXT_2WIDTH)
self._raw(TXT_STYLE['size']['normal']) elif width == 1 and height == 1:
self._raw(TXT_NORMAL)
self._raw(TXT_STYLE['flip'][flip]) elif 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and isinstance(height, int):
self._raw(TXT_STYLE['smooth'][smooth]) self._raw(TXT_SIZE + six.int2byte(TXT_WIDTH[width] + TXT_HEIGHT[height]))
self._raw(TXT_STYLE['bold'][bold]) else:
self._raw(TXT_STYLE['underline'][underline]) 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)))) self._raw(SET_FONT(six.int2byte(self.profile.get_font(font))))
self._raw(TXT_STYLE['align'][align])
if density != 9: # Align
self._raw(TXT_STYLE['density'][density]) if align.upper() == "CENTER":
self._raw(TXT_ALIGN_CT)
self._raw(TXT_STYLE['invert'][invert]) elif align.upper() == "RIGHT":
self._raw(TXT_ALIGN_RT)
elif align.upper() == "LEFT":
self._raw(TXT_ALIGN_LT)
# Density
if density == 0:
self._raw(PD_N50)
elif density == 1:
self._raw(PD_N37)
elif density == 2:
self._raw(PD_N25)
elif density == 3:
self._raw(PD_N12)
elif density == 4:
self._raw(PD_0)
elif density == 5:
self._raw(PD_P12)
elif density == 6:
self._raw(PD_P25)
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:
self._raw(TXT_INVERT_OFF)
def line_spacing(self, spacing=None, divisor=180): def line_spacing(self, spacing=None, divisor=180):
""" Set line character spacing. """ Set line character spacing.
@@ -638,15 +555,16 @@ class Escpos(object):
return return
if divisor not in LINESPACING_FUNCS: if divisor not in LINESPACING_FUNCS:
raise ValueError('divisor must be either 360, 180 or 60') raise ValueError("divisor must be either 360, 180 or 60")
if (divisor in [360, 180] and (not(0 <= spacing <= 255))): if (divisor in [360, 180]
raise ValueError('spacing must be a int between 0 and 255 when divisor is 360 or 180') and (not(0 <= spacing <= 255))):
raise ValueError("spacing must be a int between 0 and 255 when divisor is 360 or 180")
if divisor == 60 and (not(0 <= spacing <= 85)): if divisor == 60 and (not(0 <= spacing <= 85)):
raise ValueError('spacing must be a int between 0 and 85 when divisor is 60') raise ValueError("spacing must be a int between 0 and 85 when divisor is 60")
self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing)) self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing))
def cut(self, mode='FULL', feed=True): def cut(self, mode='FULL'):
""" 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
@@ -656,26 +574,20 @@ 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()
if mode not in ('FULL', 'PART'): if mode not in ('FULL', 'PART'):
raise ValueError("Mode must be one of ('FULL', 'PART')") raise ValueError("Mode must be one of ('FULL', 'PART')")
if mode == 'PART': if mode == "PART":
if self.profile.supports('paperPartCut'): if self.profile.supports('paperPartCut'):
self._raw(PAPER_PART_CUT) self._raw(PAPER_PART_CUT)
elif self.profile.supports('paperFullCut'): elif self.profile.supports('paperFullCut'):
self._raw(PAPER_FULL_CUT) self._raw(PAPER_FULL_CUT)
elif mode == 'FULL': elif mode == "FULL":
if self.profile.supports('paperFullCut'): if self.profile.supports('paperFullCut'):
self._raw(PAPER_FULL_CUT) self._raw(PAPER_FULL_CUT)
elif self.profile.supports('paperPartCut'): elif self.profile.supports('paperPartCut'):
@@ -697,44 +609,8 @@ class Escpos(object):
else: else:
try: try:
self._raw(CD_KICK_DEC_SEQUENCE(*pin)) self._raw(CD_KICK_DEC_SEQUENCE(*pin))
except TypeError as err: except:
raise CashDrawerError(err) 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
@@ -745,11 +621,11 @@ class Escpos(object):
* SELECT * SELECT
* RESET * RESET
""" """
if hw.upper() == 'INIT': if hw.upper() == "INIT":
self._raw(HW_INIT) self._raw(HW_INIT)
elif hw.upper() == 'SELECT': elif hw.upper() == "SELECT":
self._raw(HW_SELECT) self._raw(HW_SELECT)
elif hw.upper() == 'RESET': elif hw.upper() == "RESET":
self._raw(HW_RESET) self._raw(HW_RESET)
else: # DEFAULT: DOES NOTHING else: # DEFAULT: DOES NOTHING
pass pass
@@ -764,11 +640,11 @@ class Escpos(object):
""" """
if 0 <= n <= 255: if 0 <= n <= 255:
# ESC d n # ESC d n
self._raw(ESC + b'd' + six.int2byte(n)) self._raw(ESC + b"d" + six.int2byte(n))
else: else:
raise ValueError('n must be betwen 0 and 255') raise ValueError("n must be betwen 0 and 255")
def control(self, ctl, count=5, tab_size=8): def control(self, ctl, pos=4):
""" Feed control sequences """ Feed control sequences
:param ctl: string for the following control sequences: :param ctl: string for the following control sequences:
@@ -779,29 +655,25 @@ class Escpos(object):
* HT *for Horizontal Tab* * HT *for Horizontal Tab*
* VT *for Vertical Tab* * VT *for Vertical Tab*
:param count: integer between 1 and 32, controls the horizontal tab count. Defaults to 5. :param pos: integer between 1 and 16, controls the horizontal tab position
: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
if ctl.upper() == 'LF': if ctl.upper() == "LF":
self._raw(CTL_LF) self._raw(CTL_LF)
elif ctl.upper() == 'FF': elif ctl.upper() == "FF":
self._raw(CTL_FF) self._raw(CTL_FF)
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 (0 <= count <= 32 and if not (1 <= pos <= 16):
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) self._raw(CTL_SET_HT + six.int2byte(pos))
for iterator in range(1, count):
self._raw(six.int2byte(iterator * tab_size)) self._raw(CTL_HT)
self._raw(NUL) elif ctl.upper() == "VT":
elif ctl.upper() == 'VT':
self._raw(CTL_VT) self._raw(CTL_VT)
def panel_buttons(self, enable=True): def panel_buttons(self, enable=True):
@@ -827,58 +699,12 @@ 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 online status of the printer.
:returns: When online, returns ``True``; ``False`` otherwise.
:rtype: bool
"""
status = self.query_status(RT_STATUS_ONLINE)
if len(status) == 0:
return False
return not (status[0] & RT_MASK_ONLINE)
def paper_status(self):
"""
Queries the paper status of the printer.
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.
:returns: 2: Paper is adequate. 1: Paper ending. 0: No paper.
:rtype: int
"""
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
Allows the class to be used together with the `with`-statement. You have to define a printer instance Allows the class to be used together with the `with`-statement. You have to define a printer instance
and assign it to the EscposIO class. and assign it to the EsposIO-class.
This example explains the usage: This example explains the usage:
.. code-block:: Python .. code-block:: Python
@@ -927,16 +753,16 @@ class EscposIO(object):
elif isinstance(text, list) or isinstance(text, tuple): elif isinstance(text, list) or isinstance(text, tuple):
lines = text lines = text
else: else:
lines = ['{0}'.format(text), ] lines = ["{0}".format(text), ]
# TODO check unicode handling # TODO check unicode handling
# TODO flush? or on print? (this should prob rather be handled by the _raw-method) # TODO flush? or on print? (this should prob rather be handled by the _raw-method)
for line in lines: for line in lines:
self.printer.set(**params) self.printer.set(**params)
if isinstance(text, six.text_type): if isinstance(text, six.text_type):
self.printer.text(u'{0}\n'.format(line)) self.printer.text(u"{0}\n".format(line))
else: else:
self.printer.text('{0}\n'.format(line)) self.printer.text("{0}\n".format(line))
def close(self): def close(self):
""" called upon closing the `with`-statement """ called upon closing the `with`-statement

View File

@@ -8,7 +8,6 @@ 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`
@@ -51,13 +50,13 @@ class BarcodeTypeError(Error):
one of those specified in :py:meth:`escpos.escpos.Escpos.barcode`. one of those specified in :py:meth:`escpos.escpos.Escpos.barcode`.
The returned error code is `10`. The returned error code is `10`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 10 self.resultcode = 10
def __str__(self): def __str__(self):
return 'No Barcode type is defined ({msg})'.format(msg=self.msg) return "No Barcode type is defined ({msg})".format(msg=self.msg)
class BarcodeSizeError(Error): class BarcodeSizeError(Error):
@@ -67,29 +66,28 @@ class BarcodeSizeError(Error):
The size of the barcode has to be in the range that is specified in :py:meth:`escpos.escpos.Escpos.barcode`. 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`. The resulting returncode is `20`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 20 self.resultcode = 20
def __str__(self): def __str__(self):
return 'Barcode size is out of range ({msg})'.format(msg=self.msg) return "Barcode size is out of range ({msg})".format(msg=self.msg)
class BarcodeCodeError(Error): class BarcodeCodeError(Error):
""" No Barcode code was supplied, or it is incorrect. """ No Barcode code was supplied.
No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode` or the the `check` parameter No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode`.
was True and the check failed.
The returncode for this exception is `30`. The returncode for this exception is `30`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 30 self.resultcode = 30
def __str__(self): def __str__(self):
return 'No Barcode code was supplied ({msg})'.format(msg=self.msg) return "No Barcode code was supplied ({msg})".format(msg=self.msg)
class ImageSizeError(Error): class ImageSizeError(Error):
@@ -97,7 +95,7 @@ class ImageSizeError(Error):
The returncode for this exception is `40`. The returncode for this exception is `40`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 40 self.resultcode = 40
@@ -106,33 +104,19 @@ 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.
This exception is raised when an empty string is passed to :py:meth:`escpos.escpos.Escpos.text`. This exception is raised when an empty string is passed to :py:meth:`escpos.escpos.Escpos.text`.
The returncode for this exception is `50`. The returncode for this exception is `50`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 50 self.resultcode = 50
def __str__(self): def __str__(self):
return 'Text string must be supplied to the text() method ({msg})'.format(msg=self.msg) return "Text string must be supplied to the text() method ({msg})".format(msg=self.msg)
class CashDrawerError(Error): class CashDrawerError(Error):
@@ -141,29 +125,28 @@ class CashDrawerError(Error):
A valid pin number has to be passed onto the method :py:meth:`escpos.escpos.Escpos.cashdraw`. A valid pin number has to be passed onto the method :py:meth:`escpos.escpos.Escpos.cashdraw`.
The returncode for this exception is `60`. The returncode for this exception is `60`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 60 self.resultcode = 60
def __str__(self): def __str__(self):
return 'Valid pin must be set to send pulse ({msg})'.format(msg=self.msg) return "Valid pin must be set to send pulse ({msg})".format(msg=self.msg)
class TabPosError(Error): class TabPosError(Error):
""" Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values. """ Valid tab positions must be in the range 0 to 16.
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`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 70 self.resultcode = 70
def __str__(self): def __str__(self):
return 'Valid tab positions must be in the range 0 to 16 ({msg})'.format(msg=self.msg) return "Valid tab positions must be in the range 0 to 16 ({msg})".format(msg=self.msg)
class CharCodeError(Error): class CharCodeError(Error):
@@ -172,13 +155,13 @@ class CharCodeError(Error):
The supplied charcode-name in :py:meth:`escpos.escpos.Escpos.charcode` is unknown. The supplied charcode-name in :py:meth:`escpos.escpos.Escpos.charcode` is unknown.
Ths returncode for this exception is `80`. Ths returncode for this exception is `80`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 80 self.resultcode = 80
def __str__(self): def __str__(self):
return 'Valid char code must be set ({msg})'.format(msg=self.msg) return "Valid char code must be set ({msg})".format(msg=self.msg)
class USBNotFoundError(Error): class USBNotFoundError(Error):
@@ -187,13 +170,13 @@ class USBNotFoundError(Error):
The USB device seems to be not plugged in. The USB device seems to be not plugged in.
Ths returncode for this exception is `90`. Ths returncode for this exception is `90`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 90 self.resultcode = 90
def __str__(self): def __str__(self):
return 'USB device not found ({msg})'.format(msg=self.msg) return "USB device not found ({msg})".format(msg=self.msg)
class SetVariableError(Error): class SetVariableError(Error):
@@ -202,13 +185,13 @@ class SetVariableError(Error):
Check set variables against minimum and maximum values Check set variables against minimum and maximum values
Ths returncode for this exception is `100`. Ths returncode for this exception is `100`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 100 self.resultcode = 100
def __str__(self): def __str__(self):
return 'Set variable out of range ({msg})'.format(msg=self.msg) return "Set variable out of range ({msg})".format(msg=self.msg)
# Configuration errors # Configuration errors
@@ -219,13 +202,13 @@ class ConfigNotFoundError(Error):
The default or passed configuration file could not be read The default or passed configuration file could not be read
Ths returncode for this exception is `200`. Ths returncode for this exception is `200`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 200 self.resultcode = 200
def __str__(self): def __str__(self):
return 'Configuration not found ({msg})'.format(msg=self.msg) return "Configuration not found ({msg})".format(msg=self.msg)
class ConfigSyntaxError(Error): class ConfigSyntaxError(Error):
@@ -234,13 +217,13 @@ class ConfigSyntaxError(Error):
The syntax is incorrect The syntax is incorrect
Ths returncode for this exception is `210`. Ths returncode for this exception is `210`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 210 self.resultcode = 210
def __str__(self): def __str__(self):
return 'Configuration syntax is invalid ({msg})'.format(msg=self.msg) return "Configuration syntax is invalid ({msg})".format(msg=self.msg)
class ConfigSectionMissingError(Error): class ConfigSectionMissingError(Error):
@@ -249,10 +232,10 @@ class ConfigSectionMissingError(Error):
The part of the config asked for doesn't exist in the loaded configuration The part of the config asked for doesn't exist in the loaded configuration
Ths returncode for this exception is `220`. Ths returncode for this exception is `220`.
""" """
def __init__(self, msg=''): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 220 self.resultcode = 220
def __str__(self): def __str__(self):
return 'Configuration section is missing ({msg})'.format(msg=self.msg) return "Configuration section is missing ({msg})".format(msg=self.msg)

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" Image format handling class """ Image format handling class
This module contains the image format handler :py:class:`EscposImage`. This module contains the image format handler :py:class:`EscposImage`.
@@ -15,7 +14,6 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import math import math
from PIL import Image, ImageOps from PIL import Image, ImageOps
@@ -44,14 +42,14 @@ class EscposImage(object):
# Convert to white RGB background, paste over white background # Convert to white RGB background, paste over white background
# to strip alpha. # to strip alpha.
img_original = img_original.convert('RGBA') img_original = img_original.convert('RGBA')
im = Image.new('RGB', img_original.size, (255, 255, 255)) im = Image.new("RGB", img_original.size, (255, 255, 255))
im.paste(img_original, mask=img_original.split()[3]) im.paste(img_original, mask=img_original.split()[3])
# Convert down to greyscale # Convert down to greyscale
im = im.convert('L') im = im.convert("L")
# Invert: Only works on 'L' images # Invert: Only works on 'L' images
im = ImageOps.invert(im) im = ImageOps.invert(im)
# Pure black and white # Pure black and white
self._im = im.convert('1') self._im = im.convert("1")
@property @property
def width(self): def width(self):
@@ -107,7 +105,7 @@ class EscposImage(object):
:param fragment_height: height of fragment :param fragment_height: height of fragment
:return: list of PIL objects :return: list of PIL objects
""" """
passes = int(math.ceil(self.height // fragment_height)) passes = int(math.ceil(self.height/fragment_height))
fragments = [] fragments = []
for n in range(0, passes): for n in range(0, passes):
left = 0 left = 0
@@ -117,19 +115,3 @@ class EscposImage(object):
box = (left, upper, right, lower) box = (left, upper, right, lower)
fragments.append(self.img_original.crop(box)) fragments.append(self.img_original.crop(box))
return fragments return fragments
def center(self, max_width):
"""In-place image centering
:param: Maximum width in order to deduce x offset for centering
:return: None
"""
old_width, height = self._im.size
new_size = (max_width, height)
new_im = Image.new('1', new_size)
paste_x = int((max_width - old_width) / 2)
new_im.paste(self._im, (paste_x, 0))
self._im = new_im

View File

@@ -33,7 +33,7 @@ def encode_katakana(text):
# TODO doesn't this discard all that is not in the map? Can we be sure that the input does contain only # TODO doesn't this discard all that is not in the map? Can we be sure that the input does contain only
# encodable characters? We could at least throw an exception if encoding is not possible. # encodable characters? We could at least throw an exception if encoding is not possible.
pass pass
return b''.join(encoded) return b"".join(encoded)
TXT_ENC_KATAKANA_MAP = { TXT_ENC_KATAKANA_MAP = {

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" Magic Encode """ Magic Encode
@@ -18,12 +18,10 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
from builtins import bytes from builtins import bytes
import six
from .codepages import CodePages
from .constants import CODEPAGE_CHANGE from .constants import CODEPAGE_CHANGE
from .exceptions import Error from .exceptions import Error
from .codepages import CodePages
import six
class Encoder(object): class Encoder(object):
@@ -64,7 +62,7 @@ class Encoder(object):
raise ValueError(( raise ValueError((
'Encoding "{}" cannot be used for the current profile. ' 'Encoding "{}" cannot be used for the current profile. '
'Valid encodings are: {}' 'Valid encodings are: {}'
).format(encoding, ','.join(self.codepages.keys()))) ).format(encoding, ','.join(self.codepages.keys())))
return encoding return encoding
@staticmethod @staticmethod
@@ -77,11 +75,11 @@ class Encoder(object):
""" """
codepage = CodePages.get_encoding(encoding) codepage = CodePages.get_encoding(encoding)
if 'data' in codepage: if 'data' in codepage:
encodable_chars = list(''.join(codepage['data'])) encodable_chars = list("".join(codepage['data']))
assert(len(encodable_chars) == 128) assert(len(encodable_chars) == 128)
return encodable_chars return encodable_chars
elif 'python_encode' in codepage: elif 'python_encode' in codepage:
encodable_chars = [u' '] * 128 encodable_chars = [u" "] * 128
for i in range(0, 128): for i in range(0, 128):
codepoint = i + 128 codepoint = i + 128
try: try:
@@ -282,7 +280,7 @@ class MagicEncode(object):
def write_with_encoding(self, encoding, text): def write_with_encoding(self, encoding, text):
if text is not None and type(text) is not six.text_type: if text is not None and type(text) is not six.text_type:
raise Error('The supplied text has to be unicode, but is of type {type}.'.format( raise Error("The supplied text has to be unicode, but is of type {type}.".format(
type=type(text) type=type(text)
)) ))

View File

@@ -13,12 +13,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
import socket
import serial
import usb.core import usb.core
import usb.util import usb.util
import serial
import socket
from .escpos import Escpos from .escpos import Escpos
from .exceptions import USBNotFoundError from .exceptions import USBNotFoundError
@@ -56,7 +54,7 @@ class Usb(Escpos):
""" Search device on USB tree and set it as escpos device """ """ Search device on USB tree and set it as escpos device """
self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct) self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct)
if self.device is None: if self.device is None:
raise USBNotFoundError('Device not found or cable not plugged in.') raise USBNotFoundError("Device not found or cable not plugged in.")
check_driver = None check_driver = None
@@ -70,13 +68,13 @@ class Usb(Escpos):
self.device.detach_kernel_driver(0) self.device.detach_kernel_driver(0)
except usb.core.USBError as e: except usb.core.USBError as e:
if check_driver is not None: if check_driver is not None:
print('Could not detatch kernel driver: {0}'.format(str(e))) print("Could not detatch kernel driver: {0}".format(str(e)))
try: try:
self.device.set_configuration() self.device.set_configuration()
self.device.reset() self.device.reset()
except usb.core.USBError as e: except usb.core.USBError as e:
print('Could not set configuration: {0}'.format(str(e))) print("Could not set configuration: {0}".format(str(e)))
def _raw(self, msg): def _raw(self, msg):
""" Print any command sent in raw format """ Print any command sent in raw format
@@ -86,10 +84,6 @@ 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:
@@ -109,7 +103,7 @@ class Serial(Escpos):
""" """
def __init__(self, devfile='/dev/ttyS0', baudrate=9600, bytesize=8, timeout=1, def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1,
parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE,
xonxoff=False, dsrdtr=True, *args, **kwargs): xonxoff=False, dsrdtr=True, *args, **kwargs):
""" """
@@ -137,17 +131,15 @@ 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,
xonxoff=self.xonxoff, dsrdtr=self.dsrdtr) xonxoff=self.xonxoff, dsrdtr=self.dsrdtr)
if self.device is not None: if self.device is not None:
print('Serial printer enabled') print("Serial printer enabled")
else: else:
print('Unable to open serial printer on: {0}'.format(str(self.devfile))) print("Unable to open serial printer on: {0}".format(str(self.devfile)))
def _raw(self, msg): def _raw(self, msg):
""" Print any command sent in raw format """ Print any command sent in raw format
@@ -157,13 +149,9 @@ 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 and self.device.is_open: if self.device is not None:
self.device.flush() self.device.flush()
self.device.close() self.device.close()
@@ -211,7 +199,7 @@ class Network(Escpos):
self.device.connect((self.host, self.port)) self.device.connect((self.host, self.port))
if self.device is None: if self.device is None:
print('Could not open socket for {0}'.format(self.host)) print("Could not open socket for {0}".format(self.host))
def _raw(self, msg): def _raw(self, msg):
""" Print any command sent in raw format """ Print any command sent in raw format
@@ -242,10 +230,10 @@ class File(Escpos):
""" """
def __init__(self, devfile='/dev/usb/lp0', auto_flush=True, *args, **kwargs): def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs):
""" """
:param devfile: Device file under dev filesystem :param devfile : Device file under dev filesystem
:param auto_flush: automatically call flush after every call of _raw() :param auto_flush: automatically call flush after every call of _raw()
""" """
Escpos.__init__(self, *args, **kwargs) Escpos.__init__(self, *args, **kwargs)
@@ -255,10 +243,10 @@ class File(Escpos):
def open(self): def open(self):
""" Open system file """ """ Open system file """
self.device = open(self.devfile, 'wb') self.device = open(self.devfile, "wb")
if self.device is None: if self.device is None:
print('Could not open the specified file {0}'.format(self.devfile)) print("Could not open the specified file {0}".format(self.devfile))
def flush(self): def flush(self):
""" Flush printing content """ """ Flush printing content """
@@ -314,13 +302,5 @@ class Dummy(Escpos):
""" Get the data that was sent to this printer """ """ Get the data that was sent to this printer """
return b''.join(self._output_list) return b''.join(self._output_list)
def clear(self):
""" Clear the buffer of the printer
This method can be called if you send the contents to a physical printer
and want to use the Dummy printer for new output.
"""
del self._output_list[:]
def close(self): def close(self):
pass pass

View File

@@ -7,7 +7,7 @@ from __future__ import unicode_literals
import escpos.printer as printer import escpos.printer as printer
from escpos.constants import BARCODE_TYPE_A, BARCODE_TYPE_B from escpos.constants import BARCODE_TYPE_A, BARCODE_TYPE_B
from escpos.capabilities import Profile, BARCODE_B from escpos.capabilities import Profile, BARCODE_B
from escpos.exceptions import BarcodeTypeError, BarcodeCodeError from escpos.exceptions import BarcodeTypeError
import pytest import pytest
@@ -36,17 +36,3 @@ def test_lacks_support(bctype, supports_b):
instance.barcode('test', bctype) instance.barcode('test', bctype)
assert instance.output == b'' assert instance.output == b''
@pytest.mark.parametrize("bctype,data", [
('EAN13', 'AA'),
('CODE128', '{D2354AA'),
])
def test_code_check(bctype, data):
"""should raise an error if the barcode code is invalid.
"""
instance = printer.Dummy()
with pytest.raises(BarcodeCodeError):
instance.barcode(data, bctype)
assert instance.output == b''

View File

@@ -1,19 +0,0 @@
#!/usr/bin/python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer
from escpos.exceptions import CashDrawerError
import pytest
def test_raise_CashDrawerError():
"""should raise an error if the sequence is invalid.
"""
instance = printer.Dummy()
with pytest.raises(CashDrawerError):
# call with sequence that is too long
instance.cashdraw([1,1,1,1,1,1])

View File

@@ -1,104 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer
import pytest
@pytest.mark.parametrize("bctype,data", [
('UPC-A', '01234567890'),
('UPC-A', '012345678905'),
('UPC-E', '01234567'),
('UPC-E', '0123456'),
('UPC-E', '012345678905'),
('EAN13', '0123456789012'),
('EAN13', '012345678901'),
('EAN8', '01234567'),
('EAN8', '0123456'),
('CODE39', 'ABC-1234'),
('CODE39', 'ABC-1234-$$-+A'),
('CODE39', '*WIKIPEDIA*'),
('ITF', '010203040506070809'),
('ITF', '11221133113344556677889900'),
('CODABAR', 'A2030405060B'),
('CODABAR', 'C11221133113344556677889900D'),
('CODABAR', 'D0D'),
('NW7', 'A2030405060B'),
('NW7', 'C11221133113344556677889900D'),
('NW7', 'D0D'),
('CODE93', 'A2030405060B'),
('CODE93', '+:$&23-7@$'),
('CODE93', 'D0D'),
('CODE128', '{A2030405060B'),
('CODE128', '{C+:$&23-7@$'),
('CODE128', '{B0D'),
('GS1-128', '{A2030405060B'),
('GS1-128', '{C+:$&23-7@$'),
('GS1-128', '{B0D'),
('GS1 DATABAR OMNIDIRECTIONAL', '0123456789123'),
('GS1 DATABAR TRUNCATED', '0123456789123'),
('GS1 DATABAR LIMITED', '0123456789123'),
('GS1 DATABAR EXPANDED', '(9A{A20304+-%&06a0B'),
('GS1 DATABAR EXPANDED', '(1 {C+:&23-7%'),
('GS1 DATABAR EXPANDED', '(00000001234567678'),
])
def test_check_valid_barcode(bctype, data):
assert (printer.Escpos.check_barcode(bctype, data))
@pytest.mark.parametrize("bctype,data", [
('UPC-A', '01234567890123'), # too long
('UPC-A', '0123456789'), # too short
('UPC-A', '72527273-711'), # invalid '-'
('UPC-A', 'A12345678901'), # invalid 'A'
('UPC-E', '01234567890123'), # too long
('UPC-E', '012345'), # too short
('UPC-E', '72527-2'), # invalid '-'
('UPC-E', 'A123456'), # invalid 'A'
('EAN13', '0123456789'), # too short
('EAN13', 'A123456789012'), # invalid 'A'
('EAN13', '012345678901234'), # too long
('EAN8', '012345'), # too short
('EAN8', 'A123456789012'), # invalid 'A'
('EAN8', '012345678901234'), # too long
('CODE39', 'ALKJ_34'), # invalid '_'
('CODE39', 'A' * 256), # too long
('ITF', '010203040'), # odd length
('ITF', '0' * 256), # too long
('ITF', 'AB01'), # invalid 'A'
('CODABAR', '010203040'), # no start/stop
('CODABAR', '0' * 256), # too long
('CODABAR', 'AB-01F'), # invalid 'B'
('NW7', '010203040'), # no start/stop
('NW7', '0' * 256), # too long
('NW7', 'AB-01F'), # invalid 'B'
('CODE93', 'é010203040'), # invalid 'é'
('CODE93', '0' * 256), # too long
('CODE128', '010203040'), # missing leading {
('CODE128', '{D2354AA'), # second char not between A-C
('CODE128', '0' * 256), # too long
('GS1-128', '010203040'), # missing leading {
('GS1-128', '{D2354AA'), # second char not between A-C
('GS1-128', '0' * 256), # too long
('GS1 DATABAR OMNIDIRECTIONAL', '01234567891234'), # too long
('GS1 DATABAR OMNIDIRECTIONAL', '012345678912'), # too short
('GS1 DATABAR OMNIDIRECTIONAL', '012345678A1234'), # invalid 'A'
('GS1 DATABAR TRUNCATED', '01234567891234'), # too long
('GS1 DATABAR TRUNCATED', '012345678912'), # too short
('GS1 DATABAR TRUNCATED', '012345678A1234'), # invalid 'A'
('GS1 DATABAR LIMITED', '01234567891234'), # too long
('GS1 DATABAR LIMITED', '012345678912'), # too short
('GS1 DATABAR LIMITED', '012345678A1234'), # invalid 'A'
('GS1 DATABAR LIMITED', '02345678912341'), # invalid start (should be 01)
('GS1 DATABAR EXPANDED', '010203040'), # missing leading (
('GS1-128', '(' + ('0' * 256)), # too long
('GS1 DATABAR EXPANDED', '(a{D2354AA'), # second char not between 0-9
('GS1 DATABAR EXPANDED', 'IT will fail'), # first char not '('
])
def test_check_invalid_barcode(bctype, data):
assert (not printer.Escpos.check_barcode(bctype, data))

View File

@@ -1,17 +0,0 @@
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

@@ -1,8 +0,0 @@
from nose.tools import assert_raises
from escpos.printer import Dummy
def test_printer_dummy_clear():
printer = Dummy()
printer.text("Hello")
printer.clear()
assert(printer.output == b'')

View File

@@ -12,12 +12,8 @@ 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 pytest
from PIL import Image
import escpos.printer as printer import escpos.printer as printer
from escpos.exceptions import ImageWidthError from PIL import Image
# Raster format print # Raster format print
@@ -143,37 +139,3 @@ 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')
@pytest.fixture
def dummy_with_width():
instance = printer.Dummy()
instance.profile.profile_data = {
'media': {
'width': {
'pixels': 384
}
}
}
return instance
def test_width_too_large(dummy_with_width):
"""
Test printing an image that is too large in width.
"""
instance = dummy_with_width
with pytest.raises(ImageWidthError):
instance.image(Image.new("RGB", (385, 200)))
instance.image(Image.new("RGB", (384, 200)))
def test_center_image(dummy_with_width):
instance = dummy_with_width
with pytest.raises(ImageWidthError):
instance.image(Image.new("RGB", (385, 200)), center=True)
instance.image(Image.new("RGB", (384, 200)), center=True)

View File

@@ -1,35 +0,0 @@
#!/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,18 +12,43 @@ 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.Dummy() instance = printer.File(devfile=devfile)
instance.panel_buttons() instance.panel_buttons()
assert(instance.output == b'\x1B\x63\x35\x00') instance.flush()
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.Dummy() instance = printer.File(devfile=devfile)
instance.panel_buttons(False) instance.panel_buttons(False)
assert(instance.output == b'\x1B\x63\x35\x01') instance.flush()
with open(devfile, "rb") as f:
assert(f.read() == b'\x1B\x63\x35\x01')

View File

@@ -13,8 +13,6 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
from nose.tools import raises from nose.tools import raises
import pytest
import escpos.printer as printer import escpos.printer as printer
from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1 from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1
@@ -27,6 +25,7 @@ def test_defaults():
b'(k\x07\x001P01234\x1d(k\x03\x001Q0' b'(k\x07\x001P01234\x1d(k\x03\x001Q0'
assert(instance.output == expected) assert(instance.output == expected)
def test_empty(): def test_empty():
"""Test QR printing blank code""" """Test QR printing blank code"""
instance = printer.Dummy() instance = printer.Dummy()
@@ -82,17 +81,14 @@ def test_invalid_model():
instance.qr("1234", native=True, model="Hello") instance.qr("1234", native=True, model="Hello")
@pytest.mark.skip("this test has to be debugged")
def test_image(): def test_image():
"""Test QR as image""" """Test QR as 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'\x1bt\x00\n' \ expected = b'\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et' \
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)
@@ -101,13 +97,3 @@ def test_image_invalid_model():
"""Test unsupported QR model as image""" """Test unsupported QR model as image"""
instance = printer.Dummy() instance = printer.Dummy()
instance.qr("1234", native=False, model=QR_MODEL_1) instance.qr("1234", native=False, model=QR_MODEL_1)
@pytest.fixture
def instance():
return printer.Dummy()
def test_center_not_implementer(instance):
with pytest.raises(NotImplementedError):
instance.qr("test", center=True, native=True)

View File

@@ -13,7 +13,6 @@ 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 pytest
import mock import mock
from escpos.printer import Dummy from escpos.printer import Dummy
@@ -31,12 +30,3 @@ def test_type_of_object_passed_to_image_function(img_function):
d.qr("LoremIpsum") d.qr("LoremIpsum")
args, kwargs = img_function.call_args args, kwargs = img_function.call_args
assert isinstance(args[0], Image.Image) assert isinstance(args[0], Image.Image)
@pytest.fixture
def instance():
return Dummy()
def test_center(instance):
instance.qr("LoremIpsum", center=True)

View File

@@ -1,280 +0,0 @@
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

@@ -1,16 +0,0 @@
#!/usr/bin/python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer
import pytest
def test_soft_barcode():
"""just execute soft_barcode
"""
instance = printer.Dummy()
instance.soft_barcode("ean8", "1234")

View File

@@ -39,27 +39,3 @@ 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,10 +12,30 @@ 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.Dummy() instance = printer.File(devfile=devfile)
instance.text('This is a test\n') instance.text('This is a test\n')

View File

@@ -16,7 +16,7 @@ from __future__ import unicode_literals
import six import six
import pytest import pytest
from hypothesis import given, settings from hypothesis import given
from hypothesis.strategies import text from hypothesis.strategies import text
import escpos.printer as printer import escpos.printer as printer
@@ -27,8 +27,6 @@ else:
mock_open_call = '__builtin__.open' mock_open_call = '__builtin__.open'
@pytest.mark.skip("this test is broken and has to be fixed or discarded")
@settings(use_coverage=False)
@given(path=text()) @given(path=text())
def test_load_file_printer(mocker, path): def test_load_file_printer(mocker, path):
"""test the loading of the file-printer""" """test the loading of the file-printer"""
@@ -39,8 +37,6 @@ def test_load_file_printer(mocker, path):
mock_open.assert_called_with(path, "wb") mock_open.assert_called_with(path, "wb")
@pytest.mark.skip("this test is broken and has to be fixed or discarded")
@settings(deadline=None, use_coverage=False)
@given(txt=text()) @given(txt=text())
def test_auto_flush(mocker, txt): def test_auto_flush(mocker, txt):
"""test auto_flush in file-printer""" """test auto_flush in file-printer"""
@@ -61,8 +57,6 @@ def test_auto_flush(mocker, txt):
assert mock_device.flush.called assert mock_device.flush.called
@pytest.mark.skip("this test is broken and has to be fixed or discarded")
@settings(deadline=None, use_coverage=False)
@given(txt=text()) @given(txt=text())
def test_flush_on_close(mocker, txt): def test_flush_on_close(mocker, txt):
"""test flush on close in file-printer""" """test flush on close in file-printer"""

View File

@@ -1,32 +0,0 @@
#!/usr/bin/python
"""test the raising of errors with the error module
: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 pytest
import escpos
import escpos.exceptions
def test_raise_error_wrongly():
"""raise error the wrong way
should reproduce https://github.com/python-escpos/python-escpos/issues/257
"""
with pytest.raises(AttributeError):
raise escpos.Error("This should raise an AttributeError.")
def tests_raise_error():
"""raise error the right way"""
with pytest.raises(escpos.exceptions.Error):
raise escpos.exceptions.Error("This should raise an error.")

14
tox.ini
View File

@@ -7,30 +7,22 @@ deps = nose
coverage coverage
scripttest scripttest
mock mock
pytest!=3.2.0,!=3.3.0 pytest
pytest-cov pytest-cov
pytest-mock pytest-mock
hypothesis!=3.56.9 hypothesis
viivakoodi
commands = py.test --cov escpos commands = py.test --cov escpos
passenv = ESCPOS_CAPABILITIES_PICKLE_DIR ESCPOS_CAPABILITIES_FILE CI TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* CODECOV_*
[testenv:docs] [testenv:docs]
basepython = python 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]
basepython = python basepython = python
# TODO add flake8-print after adding logging # TODO add flake8-future
# TODO add flake8-docstrings # TODO add flake8-docstrings
deps = flake8 deps = flake8
flake8-coding
flake8-copyright
flake8-future-import
flake8-quotes
flake8-import-order
commands = flake8 commands = flake8