1
0
mirror of https://github.com/python-escpos/python-escpos synced 2025-09-13 09:09:58 +00:00

53 Commits

Author SHA1 Message Date
Patrick Kanzler
cbe38648f5 Merge pull request #169 from python-escpos/development
v2.2.0
2016-08-26 15:53:50 +02:00
Patrick Kanzler
d43bcd187e Merge branch 'master' into development 2016-08-26 14:46:23 +02:00
Patrick Kanzler
6b069a4529 update CHANGELOG 2016-08-26 14:27:17 +02:00
Patrick Kanzler
cf41069829 Merge branch 'linespacing' of git://github.com/miracle2k/python-escpos into miracle2k-linespacing
Conflicts:
	src/escpos/constants.py
2016-08-26 14:16:06 +02:00
Patrick Kanzler
bde6eaa336 Merge pull request #164 from miracle2k/constants
Constants
2016-08-26 12:25:24 +02:00
Michael Elsdörfer
632a104219 Fix docstring warning. 2016-08-26 11:59:40 +02:00
Michael Elsdörfer
ae9b3785c2 Fix broken tests. 2016-08-26 11:48:58 +02:00
Michael Elsdörfer
07d47765aa Allow linespacing reset. Make this the default. 2016-08-26 10:38:36 +02:00
Michael Elsdörfer
854b75be30 Support changing the line spacing. 2016-08-26 10:34:52 +02:00
Michael Elsdörfer
df0c874f6e Add constants for sheet mode, colors. 2016-08-26 10:30:29 +02:00
Michael Elsdörfer
5c3d7dab72 Change setup.py shebang.
This supports using the current virtualenv.
2016-08-26 10:30:29 +02:00
Patrick Kanzler
798893caee refactor access of private member _img of qrcode
Since version 2.5 python-qrcode allows the direct access of the
PIL-functions. (We require version 4 and above).
Thus, we can simply call qr_img.convert() without accessing the private
member.
This refactoring is identical in functionality.
2016-08-15 23:24:06 +02:00
Patrick Kanzler
e8d91a6735 test add type-check for the qr-printing 2016-08-15 23:23:07 +02:00
Patrick Kanzler
996b3fd332 DOC fix demo-code in README.rst
fixes #159
2016-08-11 11:25:40 +02:00
Patrick Kanzler
a38c124bb1 Merge branch 'master' of github.com:python-escpos/python-escpos 2016-08-10 01:31:56 +02:00
Patrick Kanzler
e44d89bd33 DOC update changelog for v2.1.3 2016-08-10 01:30:19 +02:00
Patrick Kanzler
7312db4adb Merge pull request #158 from python-escpos/development
v2.1.3
2016-08-10 01:23:47 +02:00
Patrick Kanzler
59dccd79da test add test for image-splitting-method 2016-08-07 14:39:58 +02:00
Patrick Kanzler
603b34cadb test add test for the fragment-splitting 2016-08-07 13:49:46 +02:00
Patrick Kanzler
340a47d2f6 Merge pull request #152 from python-escpos/fix/large-image-printing
Fix/large image printing
2016-08-07 12:22:25 +02:00
Patrick Kanzler
dfe2cdbff8 configure readthedocs with yml 2016-08-02 18:39:56 +02:00
Patrick Kanzler
eea2a6f9c0 travis: configure email-notifications 2016-08-02 16:41:47 +02:00
Patrick Kanzler
11452034a3 Merge pull request #157 from python-escpos/development
v2.1.2
2016-08-02 16:32:19 +02:00
Patrick Kanzler
d2e2ea88a6 doc write changelog for version v2.1.2 2016-08-02 16:07:05 +02:00
Patrick Kanzler
2416303805 Merge pull request #156 from python-escpos/fix/code128-printing
fix printing of CODE128
2016-08-02 15:57:52 +02:00
Patrick Kanzler
38f9835931 fix printing of CODE128
The control sequence {A or {B or {C can't be part of the qr code.
For this the user has to supply this sequence.
2016-08-02 15:39:31 +02:00
Patrick Kanzler
7c732ee615 doc fix lists 2016-08-02 04:38:04 +02:00
Patrick Kanzler
3d98eb8b9c fix file-printer did not flush
The file-printer did not automatically flush and thus behaved
differently to the other printer-classes.
Now the default behaviour is to flush after every call of _raw(). This
can be disabled by calling the file-printer with auto_flush=False.

fixes #106
2016-08-02 04:25:54 +02:00
Patrick Kanzler
619d80a867 doc add changelog-stump for post-2.1.1 release 2016-08-02 00:45:36 +02:00
Patrick Kanzler
d3f74ced5d doc update changelog for release v2.1.1 2016-08-02 00:41:10 +02:00
Patrick Kanzler
0524b0576e Merge pull request #154 from python-escpos/development
release v2.1.1
2016-08-02 00:39:06 +02:00
Patrick Kanzler
c92d4463ae update changelog 2016-08-02 00:27:08 +02:00
Patrick Kanzler
6e74748773 Merge pull request #153 from python-escpos/improve/add-hypothesis-support
Improve/add hypothesis support
2016-08-02 00:25:00 +02:00
Patrick Kanzler
a2e188cecf move pypy3 to the allowed failures and add hypothesis
pypy3 is not compatible with hypothesis, which is needed for testing
2016-08-02 00:10:39 +02:00
Patrick Kanzler
c1a6da9aaa configure codecov.io
For $reasons codecov.io stopped working. Now with my account explicitly
set as "bot" it works again.
2016-08-02 00:10:39 +02:00
Patrick Kanzler
2ecf73074c improve large image printing
images longer than 1024 pixels will be split into multiple fragments.
2016-08-02 00:04:43 +02:00
Patrick Kanzler
37baf5cd34 update changelog 2016-08-01 23:59:46 +02:00
Patrick Kanzler
5c209dd557 Merge pull request #151 from Renato-Lorenzi/improve/rename-interface-to-timeout
Change the interface param to timeout
2016-08-01 23:54:35 +02:00
Renato.Lorenzi
bef1a9cccf Change the interface param to timeout 2016-08-01 09:36:48 -03:00
Patrick Kanzler
10977b06e7 doc add hint on image preprocessing 2016-08-01 14:02:49 +02:00
Patrick Kanzler
042f945a09 doc added stump for next release 2016-07-23 14:57:24 +02:00
Patrick Kanzler
2cf30c7f05 Merge pull request #147 from python-escpos/development
v2.1.0
2016-07-23 14:42:33 +02:00
Patrick Kanzler
34d929806c doc update changelog 2016-07-23 11:44:34 +02:00
Patrick Kanzler
c5d34cc268 Merge pull request #148 from python-escpos/feature/tabcompletion
Feature/tabcompletion
2016-07-23 11:43:07 +02:00
Patrick Kanzler
fd3f1067fe DOC update documentation on installation and CLI 2016-07-23 11:34:18 +02:00
Patrick Kanzler
80b714fdae add basic argcomplete support 2016-07-23 10:39:40 +02:00
Patrick Kanzler
457c62cc7f Merge pull request #146 from python-escpos/add-version-to-module
add version-strings into the module
2016-07-23 08:54:03 +02:00
Patrick Kanzler
a5cae3adb7 fix inconsistent behaviour of argparse-code
this affected certain versions of argparse in python3
2016-07-17 19:39:51 +02:00
Patrick Kanzler
9f5eed0020 add version-strings into the module
The version string is in the module as __version__ available.
In the doc the version will be automatically parsed. The version comes
from the installed module if on read the docs or directly from
setuptools_scm if you are working locally.
The CLI will issue the version string if you call it with the option
'version'. The CLI does not accept commands like '--version', since this
would not be conform with the rest of the interface (and argparse).

The configuration for loading the version-string is adapted from
pimutils/vdirsyncer. It autogenerates a version string setuptools_scm at
install-time and then adds it to the __version__ member in __init__.py

I adapted the GitHub-template with a fitting comment and bumped the
changelog.

closes #141
2016-07-17 19:39:42 +02:00
Patrick Kanzler
57dd60c13f doc: add troubleshooting hint to network-interface
closes #142
2016-07-15 13:18:33 +02:00
Patrick Kanzler
0ec83387d5 improve github issues template 2016-07-15 13:05:59 +02:00
Patrick Kanzler
36e0a52e2d DOC added stump for new changelog-entry 2016-06-26 20:52:31 +02:00
Patrick Kanzler
214b4def14 configure codecov.io
fixes #134
2016-06-26 20:39:06 +02:00
29 changed files with 556 additions and 104 deletions

View File

@@ -17,6 +17,10 @@ I have:
<!-- Replace examples with your info --> <!-- Replace examples with your info -->
**Printer:** Manufacturer Model XVI **Printer:** Manufacturer Model XVI
<!-- since version 2.0.1 you can type 'python-escpos version' in your shell.
Alternatively you could use '__version__' in module escpos. -->
**python-escpos version:** 0.0.0 **python-escpos version:** 0.0.0
**python version:** 0.0
**operating system:** **operating system:**

2
.gitignore vendored
View File

@@ -17,6 +17,8 @@ temp
build/ build/
dist/ dist/
.coverage .coverage
src/escpos/version.py
.hypothesis
# testing temporary directories # testing temporary directories
test/test-cli-output/ test/test-cli-output/

View File

@@ -28,8 +28,13 @@ matrix:
allow_failures: allow_failures:
- python: 3.5-dev - python: 3.5-dev
- python: nightly - python: nightly
- python: pypy3
before_install: before_install:
- pip install tox codecov - pip install tox codecov
script: script:
- tox - tox
- codecov - codecov
notifications:
email:
on_success: never
on_failure: change

View File

@@ -1,6 +1,74 @@
********* *********
Changelog Changelog
********* *********
2016-08-26 - Version 2.2.0 - "Fate Amenable To Change"
------------------------------------------------------
changes
^^^^^^^
- fix improper API-use in qrcode()
- change setup.py shebang to make it compatible with virtualenvs.
- add constants for sheet mode and colors
- support changing the linespacing
contributors
^^^^^^^^^^^^
- Michael Elsdörfer
- Patrick Kanzler
2016-08-10 - Version 2.1.3 - "Ethics Gradient"
----------------------------------------------
changes
^^^^^^^
- configure readthedocs and travis
- update doc with hint on image preprocessing
- add fix for printing large images (by splitting them into multiple images)
contributors
^^^^^^^^^^^^
- Patrick Kanzler
2016-08-02 - Version 2.1.2 - "Death and Gravity"
------------------------------------------------
changes
^^^^^^^
- fix File-printer: flush after every call of _raw()
- fix lists in documentation
- fix CODE128: by adding the control character to the barcode-selection-sequence the barcode became unusable
contributors
^^^^^^^^^^^^
- Patrick Kanzler
2016-08-02 - Version 2.1.1 - "Contents May Differ"
--------------------------------------------------
changes
^^^^^^^
- rename variable interface in USB-class to timeout
- add support for hypothesis and move pypy3 to the allowed failures (pypy3 is not supported by hypothesis)
contributors
^^^^^^^^^^^^
- Patrick Kanzler
- Renato Lorenzi
2016-07-23 - Version 2.1.0 - "But Who's Counting?"
--------------------------------------------------
changes
^^^^^^^
- packaging: configured the coverage-analysis codecov.io
- GitHub: improved issues-template
- documentation: add troubleshooting tip to network-interface
- the module, cli and documentation is now aware of the version of python-escpos
- the cli does now support basic tabcompletion
contributors
^^^^^^^^^^^^
- Patrick Kanzler
2016-06-24 - Version 2.0.0 - "Attitude Adjuster" 2016-06-24 - Version 2.0.0 - "Attitude Adjuster"
------------------------------------------------ ------------------------------------------------

View File

@@ -54,14 +54,14 @@ The basic usage is:
.. code:: python .. code:: python
from escpos import * from escpos.printer import Usb
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """ """ Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
Epson = escpos.Escpos(0x04b8,0x0202,0) p = Usb(0x04b8,0x0202,0)
Epson.text("Hello World\n") p.text("Hello World\n")
Epson.image("logo.gif") p.image("logo.gif")
Epson.barcode('1324354657687','EAN13',64,2,'','') p.barcode('1324354657687','EAN13',64,2,'','')
Epson.cut() p.cut()
The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_. The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_.

17
codecov.yml Normal file
View File

@@ -0,0 +1,17 @@
codecov:
bot: patkan
coverage:
status:
project:
default: # status context
target: auto
threshold: "1%"
patch:
default:
target: auto
threshold: "1%"
range: "60...100"
comment: off

View File

@@ -15,7 +15,10 @@
import sys import sys
import os import os
on_rtd = os.getenv('READTHEDOCS') == 'True' on_rtd = os.getenv('READTHEDOCS') == 'True'
from setuptools_scm import get_version if on_rtd:
import escpos
else:
from setuptools_scm import get_version
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
@@ -70,10 +73,14 @@ copyright = u'2016, Manuel F Martinez and others'
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
if on_rtd:
# The full version, including alpha/beta/rc tags.
release = escpos.__version__
else:
# locally setuptools_scm should work
release = get_version(root=root)
# The short X.Y version. # The short X.Y version.
version = get_version(root=root) version = '.'.join(release.split('.')[:2]) # The short X.Y version.
# The full version, including alpha/beta/rc tags.
release = version
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

View File

@@ -12,13 +12,13 @@ Content
:maxdepth: 1 :maxdepth: 1
:caption: User Documentation :caption: User Documentation
user/dependencies
user/installation user/installation
user/methods user/methods
user/printers user/printers
user/raspi user/raspi
user/todo user/todo
user/usage user/usage
user/barcode
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1

34
doc/user/barcode.rst Normal file
View File

@@ -0,0 +1,34 @@
Printing Barcodes
-----------------
:Last Reviewed: 2016-07-31
Most ESC/POS-printers implement barcode-printing.
The barcode-commandset is implemented in the barcode-method.
For a list of compatible barcodes you should check the manual of your printer.
As a rule of thumb: even older Epson-models support most 1D-barcodes.
To be sure just try some implementations and have a look at the notices below.
barcode-method
~~~~~~~~~~~~~~
The barcode-method is rather low-level and orients itself on the implementation of ESC/POS.
In the future this class could be supplemented by a high-level class that helps the user generating the payload.
.. py:currentmodule:: escpos.escpos
.. automethod:: Escpos.barcode
:noindex:
CODE128
~~~~~~~
Code128 barcodes need a certain format.
For now the user has to make sure that the payload is correct.
For alphanumeric CODE128 you have to preface your payload with `{B`.
.. code-block:: Python
from escpos.printer import Dummy, Serial
p = Serial()
# print CODE128 012ABCDabcd
p.barcode("{B012ABCDabcd", "CODE128", function_type="B")
A very good description on CODE128 is also on `Wikipedia <https://en.wikipedia.org/wiki/Code_128>`_.

View File

@@ -1,57 +0,0 @@
************
Dependencies
************
Fedora
------
Fortunately everything is on Fedora repositories.
::
# yum install python-imaging pyserial pyusb python-qrcode
Ubuntu
------
Ultimately, this instructions also apply to Raspbian, in case you are
interested to install python-escpos on your Raspberry with Raspbian.
Install the packages available on distro repositories.
::
# apt-get install python-imaging pyserial
The packages which are not available at Ubuntu repositories need to be
installed manually.
pyusb
^^^^^
This is the python binding to libusb-1.0
* Get the latest tarball from `sourceforge <http://sourceforge.net/projects/pyusb/files/PyUSB%201.0/>`__
* Build and install it
::
# tar zxvf pyusb-1.*.tar.gz
# cd pyusb-1.*
# python setup.py build
# sudo python setup.py install
python-qrcode
^^^^^^^^^^^^^
This is the python module to generate QR Codes
* Checkout the latest code from `github <https://github.com/lincolnloop/python-qrcode>`__
* Build and install it
::
# git clone https://github.com/lincolnloop/python-qrcode
# cd python-qrcode
# python setup.py build
# sudo python setup.py install

View File

@@ -2,16 +2,26 @@
Installation Installation
************ ************
System preparation :Last Reviewed: 2016-07-23
------------------
1. Install the required Installation with PIP
`dependencies <https://github.com/manpaz/python-escpos/wiki/Dependencies>`__ ---------------------
Installation should be rather straight-forward. python-escpos is on PyPi, so you can simply enter:
2. Get the *Product ID* and *Vendor ID* from the lsusb command ::
pip install python-escpos
This should install all necessary dependencies. Apart from that python-escpos should also be
available as a Debian package. If you want to always benefit from the newest stable releases you should probably
install from PyPi.
Setup udev for USB-Printers
---------------------------
1. Get the *Product ID* and *Vendor ID* from the lsusb command
``# lsusb Bus 002 Device 001: ID 1a2b:1a2b Device name`` ``# lsusb Bus 002 Device 001: ID 1a2b:1a2b Device name``
3. Create a udev rule to let users belonging to *dialout* group use the 2. Create a udev rule to let users belonging to *dialout* group use the
printer. You can create the file printer. You can create the file
``/etc/udev/rules.d/99-escpos.rules`` and add the following: ``/etc/udev/rules.d/99-escpos.rules`` and add the following:
``SUBSYSTEM=="usb", ATTRS{idVendor}=="1a2b", ATTRS{idProduct}=="1a2b", MODE="0664", GROUP="dialout"`` ``SUBSYSTEM=="usb", ATTRS{idVendor}=="1a2b", ATTRS{idProduct}=="1a2b", MODE="0664", GROUP="dialout"``
@@ -20,21 +30,19 @@ System preparation
"dialout" group, or use another group you already belongs instead "dialout" group, or use another group you already belongs instead
"dialout" and set it in the ``GROUP`` parameter in the above rule. "dialout" and set it in the ``GROUP`` parameter in the above rule.
4. Restart udev ``# sudo service udev restart`` In some new systems it 3. Restart udev ``# sudo service udev restart`` In some new systems it
is done with ``# sudo udevadm control --reload`` is done with ``# sudo udevadm control --reload``
Install Enabling tab-completion in CLI
------- ------------------------------
python-escpos has a CLI with tab-completion. This is realised with ``argcomplete``.
In order for this to work you have to enable tab-completion, which is described in
the `manual of argcomplete <https://argcomplete.readthedocs.io>`__.
* Clone python-escpos from github If you only want to enable it for python-escpos, or global activation does not work, try this:
* Change directory to python-escpos and install the package
:: ::
# cd python-escpos eval "$(register-python-argcomplete python-escpos)"
# python setup.py build
# sudo python setup.py install
* Enjoy !!!

View File

@@ -88,6 +88,7 @@ set("align", "font", "type", width, height, invert, smooth, flip)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Set text properties. Set text properties.
* ``align`` set horizontal position for text, the possible values are: * ``align`` set horizontal position for text, the possible values are:
* CENTER * CENTER
@@ -106,6 +107,7 @@ cut("mode")
^^^^^^^^^^^ ^^^^^^^^^^^
Cut paper. Cut paper.
* ``mode`` set a full or partial cut. *Default:* full * ``mode`` set a full or partial cut. *Default:* full
**Partial cut is not implemented in all printers.** **Partial cut is not implemented in all printers.**
@@ -132,6 +134,7 @@ control("align")
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
Carrier feed and tabs. Carrier feed and tabs.
* ``align`` is a string which takes any of the following values: * ``align`` is a string which takes any of the following values:
* LF *for Line Feed* * LF *for Line Feed*

View File

@@ -32,11 +32,25 @@ Network("host", port)
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
Based on socket Based on socket
* ``host`` is an alphanumeric host name, could be either DNS host name or IP address. * ``host`` is an alphanumeric host name, could be either DNS host name or IP address.
* ``port`` to write to (default = 9100) * ``port`` to write to (default = 9100)
Troubleshooting:
Problems with a network-attached printer can have numerous causes. Make sure that your device has a proper IP address.
Often you can check the IP address by triggering the self-test of the device. As a next step try to send text
manually to the device. You could use for example:
.. ::
echo "OK\n" | nc IPADDRESS 9100
# the port number is often 9100
As a last resort try to reset the interface of the printer. This should be described in its manual.
File("file\_name") File("file\_name")
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
Printcap printers Printcap printers
* ``file_name`` is the full path to the device file name * ``file_name`` is the full path to the device file name

View File

@@ -204,6 +204,37 @@ Here you can download an example, that will print a set of common barcodes:
* :download:`barcode.bin </download/barcode.bin>` by `@mike42 <https://github.com/mike42>`_ * :download:`barcode.bin </download/barcode.bin>` by `@mike42 <https://github.com/mike42>`_
Hint: preprocess printing
-------------------------
Printing images directly to the printer is rather slow.
One factor that slows down the process is the transmission over e.g. serial port.
Apart from configuring your printer to use the maximum baudrate (in the case of serial-printers), there is not much
that you can do.
However you could use the :py:class:`escpos.printer.Dummy`-printer to preprocess your image.
This is probably best explained by an example:
.. code-block:: Python
from escpos.printer import Serial, Dummy
p = Serial()
d = Dummy()
# create ESC/POS for the print job, this should go really fast
d.text("This is my image:\n")
d.image("funny_cat.png")
d.cut()
# send code to printer
p._raw(d.output)
This way you could also store the code in a file and print later.
You could then for example print the code from another process than your main-program and thus reduce the waiting time.
(Of course this will not make the printer print faster.)
How to update your code for USB printers How to update your code for USB printers
---------------------------------------- ----------------------------------------

7
readthedocs.yml Normal file
View File

@@ -0,0 +1,7 @@
formats:
- pdf
- epub
requirements_file: doc/requirements.txt
python:
version: 2
setup_py_install: true

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/env python
import os import os
import sys import sys
@@ -45,9 +45,24 @@ class Tox(test_command):
errno = tox.cmdline(args=args) errno = tox.cmdline(args=args)
sys.exit(errno) sys.exit(errno)
setuptools_scm_template = """\
# coding: utf-8
# file generated by setuptools_scm
# don't change, don't track in version control
from __future__ import unicode_literals
version = '{version}'
"""
setup( setup(
name='python-escpos', name='python-escpos',
use_scm_version=True, use_scm_version={
'write_to': 'src/escpos/version.py',
'write_to_template': setuptools_scm_template,
},
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',
@@ -95,11 +110,13 @@ setup(
'six', 'six',
'appdirs', 'appdirs',
'pyyaml', 'pyyaml',
'argparse',
'argcomplete',
], ],
setup_requires=[ setup_requires=[
'setuptools_scm', 'setuptools_scm',
], ],
tests_require=['tox', 'nose', 'scripttest'], tests_require=['tox', 'nose', 'scripttest', 'mock', 'hypothesis'],
cmdclass={'test': Tox}, cmdclass={'test': Tox},
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [

View File

@@ -1 +1,19 @@
# -*- coding: utf-8 -*-
"""
python-escpos enables you to manipulate escpos-printers
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
__all__ = ["constants", "escpos", "exceptions", "printer"] __all__ = ["constants", "escpos", "exceptions", "printer"]
try:
from .version import version as __version__ # noqa
except ImportError: # pragma: no cover
raise ImportError(
'Failed to find (autogenerated) version.py. '
'This might be because you are installing from GitHub\'s tarballs, '
'use the PyPI ones.'
)

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
""" CLI """ CLI
This module acts as a command line interface for python-escpos. It mirrors This module acts as a command line interface for python-escpos. It mirrors
@@ -14,9 +15,15 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import argparse import argparse
try:
import argcomplete
except ImportError:
# this CLI works nevertheless without argcomplete
pass # noqa
import sys import sys
import six import six
from . import config from . import config
from . import version
# Must be defined before it's used in DEMO_FUNCTIONS # Must be defined before it's used in DEMO_FUNCTIONS
@@ -448,14 +455,17 @@ def main():
# Allow config file location to be passed # Allow config file location to be passed
parser.add_argument( parser.add_argument(
'-c', '--config', '-c', '--config',
help='Altnerate path to the configuration file', help='Alternate path to the configuration file',
) )
# Everything interesting runs off of a subparser so we can use the format # Everything interesting runs off of a subparser so we can use the format
# cli [subparser] -args # cli [subparser] -args
command_subparsers = parser.add_subparsers( command_subparsers = parser.add_subparsers(
title='ESCPOS Command', title='ESCPOS Command',
dest='parser',
) )
# fix inconsistencies in the behaviour of some versions of argparse
command_subparsers.required = False # force 'required' testing
# Build the ESCPOS command arguments # Build the ESCPOS command arguments
for command in ESCPOS_COMMANDS: for command in ESCPOS_COMMANDS:
@@ -491,6 +501,14 @@ def main():
action='store_true', action='store_true',
) )
parser_command_version = command_subparsers.add_parser('version',
help='Print the version of python-escpos')
parser_command_version.set_defaults(version=True)
# hook in argcomplete
if 'argcomplete' in globals():
argcomplete.autocomplete(parser)
# Get only arguments actually passed # Get only arguments actually passed
args_dict = vars(parser.parse_args()) args_dict = vars(parser.parse_args())
if not args_dict: if not args_dict:
@@ -498,6 +516,12 @@ def main():
sys.exit() sys.exit()
command_arguments = dict([k, v] for k, v in six.iteritems(args_dict) if v is not None) command_arguments = dict([k, v] for k, v in six.iteritems(args_dict) if v is not None)
# If version should be printed, do this, then exit
print_version = command_arguments.pop('version', None)
if print_version:
print(version.version)
sys.exit()
# If there was a config path passed, grab it # If there was a config path passed, grab it
config_path = command_arguments.pop('config', None) config_path = command_arguments.pop('config', None)
@@ -511,6 +535,9 @@ def main():
target_command = command_arguments.pop('func') target_command = command_arguments.pop('func')
# remove helper-argument 'parser' from dict
command_arguments.pop('parser', None)
if hasattr(printer, target_command): if hasattr(printer, target_command):
# print command with args # print command with args
getattr(printer, target_command)(**command_arguments) getattr(printer, target_command)(**command_arguments)

View File

@@ -55,11 +55,18 @@ _CUT_PAPER = lambda m: GS + b'V' + m
PAPER_FULL_CUT = _CUT_PAPER(b'\x00') # Full cut paper PAPER_FULL_CUT = _CUT_PAPER(b'\x00') # Full cut paper
PAPER_PART_CUT = _CUT_PAPER(b'\x01') # Partial cut paper PAPER_PART_CUT = _CUT_PAPER(b'\x01') # Partial cut paper
# Beep
BEEP = b'\x07'
# Panel buttons (e.g. the FEED button) # Panel buttons (e.g. the FEED button)
_PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n) _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
# Sheet modes
SHEET_SLIP_MODE = ESC + b'\x63\x30\x04' # slip paper
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.
@@ -101,6 +108,18 @@ TXT_ALIGN_RT = ESC + b'\x61\x02' # Right justification
TXT_INVERT_ON = GS + b'\x42\x01' # Inverse Printing ON TXT_INVERT_ON = GS + b'\x42\x01' # Inverse Printing ON
TXT_INVERT_OFF = GS + b'\x42\x00' # Inverse Printing OFF TXT_INVERT_OFF = GS + b'\x42\x00' # Inverse Printing OFF
# Text colors
TXT_COLOR_BLACK = ESC + b'\x72\x00' # Default Color
TXT_COLOR_RED = ESC + b'\x72\x01' # Alternative Color (Usually Red)
# Spacing
LINESPACING_RESET = ESC + b'2'
LINESPACING_FUNCS = {
60: ESC + b'A', # line_spacing/60 of an inch, 0 <= line_spacing <= 85
360: ESC + b'+', # line_spacing/360 of an inch, 0 <= line_spacing <= 255
180: ESC + b'3', # line_spacing/180 of an inch, 0 <= line_spacing <= 255
}
# Char code table # Char code table
CHARCODE_PC437 = ESC + b'\x74\x00' # USA: Standard Europe CHARCODE_PC437 = ESC + b'\x74\x00' # USA: Standard Europe
CHARCODE_JIS = ESC + b'\x74\x01' # Japanese Katakana CHARCODE_JIS = ESC + b'\x74\x01' # Japanese Katakana
@@ -168,10 +187,7 @@ BARCODE_TYPE_B = {
'NW7': _SET_BARCODE_TYPE(71), 'NW7': _SET_BARCODE_TYPE(71),
'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7 'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7
'CODE93': _SET_BARCODE_TYPE(72), 'CODE93': _SET_BARCODE_TYPE(72),
# These are all the same barcode, but using different charcter sets 'CODE128': _SET_BARCODE_TYPE(73),
'CODE128A': _SET_BARCODE_TYPE(73) + b'{A', # CODE128 character set A
'CODE128B': _SET_BARCODE_TYPE(73) + b'{B', # CODE128 character set B
'CODE128C': _SET_BARCODE_TYPE(73) + b'{C', # CODE128 character set C
'GS1-128': _SET_BARCODE_TYPE(74), 'GS1-128': _SET_BARCODE_TYPE(74),
'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75), 'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75),
'GS1 DATABAR TRUNCATED': _SET_BARCODE_TYPE(76), 'GS1 DATABAR TRUNCATED': _SET_BARCODE_TYPE(76),
@@ -189,7 +205,7 @@ QR_ECLEVEL_L = 0
QR_ECLEVEL_M = 1 QR_ECLEVEL_M = 1
QR_ECLEVEL_Q = 2 QR_ECLEVEL_Q = 2
QR_ECLEVEL_H = 3 QR_ECLEVEL_H = 3
# QRcode models # QRcode models
QR_MODEL_1 = 1 QR_MODEL_1 = 1
QR_MODEL_2 = 2 QR_MODEL_2 = 2

View File

@@ -56,7 +56,8 @@ class Escpos(object):
""" """
pass pass
def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster"): def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster",
fragment_height=1024):
""" 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.
@@ -76,9 +77,20 @@ class Escpos(object):
:param high_density_vertical: print in high density in vertical direction *default:* True :param high_density_vertical: print in high density in vertical direction *default:* True
: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:* 1024
""" """
im = EscposImage(img_source) im = EscposImage(img_source)
if im.height > fragment_height:
fragments = im.split(fragment_height)
for fragment in fragments:
self.image(fragment,
high_density_vertical=high_density_vertical,
high_density_horizontal=high_density_horizontal,
impl=impl,
fragment_height=fragment_height)
return
if impl == "bitImageRaster": if impl == "bitImageRaster":
# GS v 0, raster format bit image # GS v 0, raster format bit image
@@ -553,6 +565,35 @@ class Escpos(object):
else: else:
self._raw(TXT_INVERT_OFF) self._raw(TXT_INVERT_OFF)
def line_spacing(self, spacing=None, divisor=180):
""" Set line character spacing.
If no spacing is given, we reset it to the default.
There are different commands for setting the line spacing, using
a different denominator:
'+'' line_spacing/360 of an inch, 0 <= line_spacing <= 255
'3' line_spacing/180 of an inch, 0 <= line_spacing <= 255
'A' line_spacing/60 of an inch, 0 <= line_spacing <= 85
Some printers may not support all of them. The most commonly
available command (using a divisor of 180) is chosen.
"""
if spacing is None:
self._raw(LINESPACING_RESET)
return
if divisor not in LINESPACING_FUNCS:
raise ValueError("divisor must be either 360, 180 or 60")
if (divisor in [360, 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)):
raise ValueError("spacing must be a int between 0 and 85 when divisor is 60")
self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing))
def cut(self, mode=''): def cut(self, mode=''):
""" Cut paper. """ Cut paper.

View File

@@ -8,6 +8,12 @@ This module contains the image format handler :py:class:`EscposImage`.
:license: GNU GPL v3 :license: GNU GPL v3
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import math
from PIL import Image, ImageOps from PIL import Image, ImageOps
@@ -30,6 +36,9 @@ class EscposImage(object):
else: else:
img_original = Image.open(img_source) img_original = Image.open(img_source)
# store image for eventual further processing (splitting)
self.img_original = img_original
# 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')
@@ -88,3 +97,21 @@ class EscposImage(object):
Convert image to raster-format binary Convert image to raster-format binary
""" """
return self._im.tobytes() return self._im.tobytes()
def split(self, fragment_height):
"""
Split an image into multiple fragments after fragment_height pixels
:param fragment_height: height of fragment
:return: list of PIL objects
"""
passes = int(math.ceil(self.height/fragment_height))
fragments = []
for n in range(0, passes):
left = 0
right = self.width
upper = n * fragment_height
lower = min((n + 1) * fragment_height, self.height)
box = (left, upper, right, lower)
fragments.append(self.img_original.crop(box))
return fragments

View File

@@ -34,18 +34,18 @@ class Usb(Escpos):
""" """
def __init__(self, idVendor, idProduct, interface=0, in_ep=0x82, out_ep=0x01, *args, **kwargs): def __init__(self, idVendor, idProduct, timeout=0, in_ep=0x82, out_ep=0x01, *args, **kwargs):
""" """
:param idVendor: Vendor ID :param idVendor: Vendor ID
:param idProduct: Product ID :param idProduct: Product ID
:param interface: USB device interface :param timeout: Is the time limit of the USB operation. Default without timeout.
:param in_ep: Input end point :param in_ep: Input end point
:param out_ep: Output end point :param out_ep: Output end point
""" """
Escpos.__init__(self, *args, **kwargs) Escpos.__init__(self, *args, **kwargs)
self.idVendor = idVendor self.idVendor = idVendor
self.idProduct = idProduct self.idProduct = idProduct
self.interface = interface self.timeout = timeout
self.in_ep = in_ep self.in_ep = in_ep
self.out_ep = out_ep self.out_ep = out_ep
self.open() self.open()
@@ -82,7 +82,7 @@ class Usb(Escpos):
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes :type msg: bytes
""" """
self.device.write(self.out_ep, msg, self.interface) self.device.write(self.out_ep, msg, self.timeout)
def close(self): def close(self):
""" Release USB interface """ """ Release USB interface """
@@ -229,13 +229,15 @@ class File(Escpos):
""" """
def __init__(self, devfile="/dev/usb/lp0", *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()
""" """
Escpos.__init__(self, *args, **kwargs) Escpos.__init__(self, *args, **kwargs)
self.devfile = devfile self.devfile = devfile
self.auto_flush = auto_flush
self.open() self.open()
def open(self): def open(self):
@@ -256,6 +258,8 @@ class File(Escpos):
:type msg: bytes :type msg: bytes
""" """
self.device.write(msg) self.device.write(msg)
if self.auto_flush:
self.flush()
def close(self): def close(self):
""" Close system file """ """ Close system file """

View File

@@ -11,6 +11,7 @@ import os
import sys import sys
from scripttest import TestFileEnvironment from scripttest import TestFileEnvironment
from nose.tools import assert_equals from nose.tools import assert_equals
import escpos
TEST_DIR = os.path.abspath('test/test-cli-output') TEST_DIR = os.path.abspath('test/test-cli-output')
@@ -29,7 +30,7 @@ printer:
) )
class TestCLI: class TestCLI():
""" Contains setups, teardowns, and tests for CLI """ Contains setups, teardowns, and tests for CLI
""" """
@@ -60,8 +61,7 @@ class TestCLI:
) )
self.default_args = ( self.default_args = (
sys.executable, 'python-escpos',
'-mescpos.cli',
'-c', '-c',
CONFIGFILE, CONFIGFILE,
) )
@@ -79,10 +79,16 @@ class TestCLI:
def test_cli_help(self): def test_cli_help(self):
""" Test getting help from cli """ """ Test getting help from cli """
result = self.env.run(sys.executable, '-mescpos.cli', '-h') result = self.env.run('python-escpos', '-h')
assert not result.stderr assert not result.stderr
assert 'usage' in result.stdout assert 'usage' in result.stdout
def test_cli_version(self):
""" Test the version string """
result = self.env.run('python-escpos', 'version')
assert not result.stderr
assert_equals(escpos.__version__, result.stdout.strip())
def test_cli_text(self): def test_cli_text(self):
""" Make sure text returns what we sent it """ """ Make sure text returns what we sent it """
test_text = 'this is some text' test_text = 'this is some text'

View File

@@ -130,3 +130,12 @@ def test_graphics_transparent():
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/black_transparent.png', impl="graphics") instance.image('test/resources/black_transparent.png', impl="graphics")
assert(instance.output == b'\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002') assert(instance.output == b'\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002')
def test_large_graphics():
"""
Test whether 'large' graphics that induce a fragmentation are handled correctly.
"""
instance = printer.Dummy()
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')

View File

@@ -0,0 +1,32 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""tests for the non-native part of qr()
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: GNU GPL v3
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import mock
from escpos.printer import Dummy
from PIL import Image
@mock.patch('escpos.printer.Dummy.image', spec=Dummy)
def test_type_of_object_passed_to_image_function(img_function):
"""
Test the type of object that is passed to the image function during non-native qr-printing.
The type should be PIL.Image
"""
d = Dummy()
d.qr("LoremIpsum")
args, kwargs = img_function.call_args
assert isinstance(args[0], Image.Image)

26
test/test_functions.py Normal file
View File

@@ -0,0 +1,26 @@
from nose.tools import assert_raises
from escpos.printer import Dummy
def test_line_spacing_code_gen():
printer = Dummy()
printer.line_spacing(10)
assert printer.output == b'\x1b3\n'
def test_line_spacing_rest():
printer = Dummy()
printer.line_spacing()
assert printer.output == b'\x1b2'
def test_line_spacing_error_handling():
printer = Dummy()
with assert_raises(ValueError):
printer.line_spacing(99, divisor=44)
with assert_raises(ValueError):
printer.line_spacing(divisor=80, spacing=86)
with assert_raises(ValueError):
printer.line_spacing(divisor=360, spacing=256)
with assert_raises(ValueError):
printer.line_spacing(divisor=180, spacing=256)

View File

@@ -43,6 +43,20 @@ def test_image_white():
_load_and_check_img('canvas_white.' + img_format, 1, 1, b'\x00', [b'\x00']) _load_and_check_img('canvas_white.' + img_format, 1, 1, b'\x00', [b'\x00'])
def test_split():
"""
test whether the split-function works as expected
"""
im = EscposImage('test/resources/black_white.png')
(upper_part, lower_part) = im.split(1)
upper_part = EscposImage(upper_part)
lower_part = EscposImage(lower_part)
assert(upper_part.width == lower_part.width == 2)
assert(upper_part.height == lower_part.height == 1)
assert(upper_part.to_raster_format() == b'\xc0')
assert(lower_part.to_raster_format() == b'\x00')
def _load_and_check_img(filename, width_expected, height_expected, raster_format_expected, column_format_expected): def _load_and_check_img(filename, width_expected, height_expected, raster_format_expected, column_format_expected):
""" """
Load an image, and test whether raster & column formatted output, sizes, etc match expectations. Load an image, and test whether raster & column formatted output, sizes, etc match expectations.

70
test/test_printer_file.py Normal file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""tests for the File printer
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: GNU GPL v3
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import six
import mock
from hypothesis import given
from hypothesis.strategies import text
import escpos.printer as printer
if six.PY3:
mock_open_call = 'builtins.open'
else:
mock_open_call = '__builtin__.open'
@given(path=text())
@mock.patch(mock_open_call)
@mock.patch('escpos.escpos.Escpos.__init__')
def test_load_file_printer(mock_escpos, mock_open, path):
"""test the loading of the file-printer"""
printer.File(devfile=path)
assert mock_escpos.called
mock_open.assert_called_with(path, "wb")
@given(txt=text())
@mock.patch.object(printer.File, 'device')
@mock.patch(mock_open_call)
@mock.patch('escpos.escpos.Escpos.__init__')
def test_auto_flush(mock_escpos, mock_open, mock_device, txt):
"""test auto_flush in file-printer"""
p = printer.File(auto_flush=False)
# inject the mocked device-object
p.device = mock_device
p._raw(txt)
assert not mock_device.flush.called
mock_device.reset_mock()
p = printer.File(auto_flush=True)
# inject the mocked device-object
p.device = mock_device
p._raw(txt)
assert mock_device.flush.called
@given(txt=text())
@mock.patch.object(printer.File, 'device')
@mock.patch(mock_open_call)
def test_flush_on_close(mock_open, mock_device, txt):
"""test flush on close in file-printer"""
p = printer.File(auto_flush=False)
# inject the mocked device-object
p.device = mock_device
p._raw(txt)
assert not mock_device.flush.called
p.close()
assert mock_device.flush.called
assert mock_device.close.called

View File

@@ -5,6 +5,8 @@ envlist = py27, py34, py35, docs
deps = nose deps = nose
coverage coverage
scripttest scripttest
mock
hypothesis
commands = nosetests --with-coverage --cover-erase --cover-branches commands = nosetests --with-coverage --cover-erase --cover-branches
[testenv:docs] [testenv:docs]