mirror of
https://github.com/python-escpos/python-escpos
synced 2025-09-13 09:09:58 +00:00
Compare commits
53 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cbe38648f5 | ||
![]() |
d43bcd187e | ||
![]() |
6b069a4529 | ||
![]() |
cf41069829 | ||
![]() |
bde6eaa336 | ||
![]() |
632a104219 | ||
![]() |
ae9b3785c2 | ||
![]() |
07d47765aa | ||
![]() |
854b75be30 | ||
![]() |
df0c874f6e | ||
![]() |
5c3d7dab72 | ||
![]() |
798893caee | ||
![]() |
e8d91a6735 | ||
![]() |
996b3fd332 | ||
![]() |
a38c124bb1 | ||
![]() |
e44d89bd33 | ||
![]() |
7312db4adb | ||
![]() |
59dccd79da | ||
![]() |
603b34cadb | ||
![]() |
340a47d2f6 | ||
![]() |
dfe2cdbff8 | ||
![]() |
eea2a6f9c0 | ||
![]() |
11452034a3 | ||
![]() |
d2e2ea88a6 | ||
![]() |
2416303805 | ||
![]() |
38f9835931 | ||
![]() |
7c732ee615 | ||
![]() |
3d98eb8b9c | ||
![]() |
619d80a867 | ||
![]() |
d3f74ced5d | ||
![]() |
0524b0576e | ||
![]() |
c92d4463ae | ||
![]() |
6e74748773 | ||
![]() |
a2e188cecf | ||
![]() |
c1a6da9aaa | ||
![]() |
2ecf73074c | ||
![]() |
37baf5cd34 | ||
![]() |
5c209dd557 | ||
![]() |
bef1a9cccf | ||
![]() |
10977b06e7 | ||
![]() |
042f945a09 | ||
![]() |
2cf30c7f05 | ||
![]() |
34d929806c | ||
![]() |
c5d34cc268 | ||
![]() |
fd3f1067fe | ||
![]() |
80b714fdae | ||
![]() |
457c62cc7f | ||
![]() |
a5cae3adb7 | ||
![]() |
9f5eed0020 | ||
![]() |
57dd60c13f | ||
![]() |
0ec83387d5 | ||
![]() |
36e0a52e2d | ||
![]() |
214b4def14 |
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@@ -17,6 +17,10 @@ I have:
|
||||
<!-- Replace examples with your info -->
|
||||
**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 version:** 0.0
|
||||
|
||||
**operating system:**
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -17,6 +17,8 @@ temp
|
||||
build/
|
||||
dist/
|
||||
.coverage
|
||||
src/escpos/version.py
|
||||
.hypothesis
|
||||
|
||||
# testing temporary directories
|
||||
test/test-cli-output/
|
||||
|
@@ -28,8 +28,13 @@ matrix:
|
||||
allow_failures:
|
||||
- python: 3.5-dev
|
||||
- python: nightly
|
||||
- python: pypy3
|
||||
before_install:
|
||||
- pip install tox codecov
|
||||
script:
|
||||
- tox
|
||||
- codecov
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: change
|
||||
|
@@ -1,6 +1,74 @@
|
||||
*********
|
||||
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"
|
||||
------------------------------------------------
|
||||
|
12
README.rst
12
README.rst
@@ -54,14 +54,14 @@ The basic usage is:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from escpos import *
|
||||
from escpos.printer import Usb
|
||||
|
||||
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
|
||||
Epson = escpos.Escpos(0x04b8,0x0202,0)
|
||||
Epson.text("Hello World\n")
|
||||
Epson.image("logo.gif")
|
||||
Epson.barcode('1324354657687','EAN13',64,2,'','')
|
||||
Epson.cut()
|
||||
p = Usb(0x04b8,0x0202,0)
|
||||
p.text("Hello World\n")
|
||||
p.image("logo.gif")
|
||||
p.barcode('1324354657687','EAN13',64,2,'','')
|
||||
p.cut()
|
||||
|
||||
The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_.
|
||||
|
||||
|
17
codecov.yml
Normal file
17
codecov.yml
Normal 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
|
||||
|
15
doc/conf.py
15
doc/conf.py
@@ -15,7 +15,10 @@
|
||||
import sys
|
||||
import os
|
||||
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,
|
||||
# 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
|
||||
# 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.
|
||||
version = get_version(root=root)
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
version = '.'.join(release.split('.')[:2]) # The short X.Y version.
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@@ -12,13 +12,13 @@ Content
|
||||
:maxdepth: 1
|
||||
:caption: User Documentation
|
||||
|
||||
user/dependencies
|
||||
user/installation
|
||||
user/methods
|
||||
user/printers
|
||||
user/raspi
|
||||
user/todo
|
||||
user/usage
|
||||
user/barcode
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
34
doc/user/barcode.rst
Normal file
34
doc/user/barcode.rst
Normal 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>`_.
|
@@ -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
|
||||
|
@@ -2,16 +2,26 @@
|
||||
Installation
|
||||
************
|
||||
|
||||
System preparation
|
||||
------------------
|
||||
:Last Reviewed: 2016-07-23
|
||||
|
||||
1. Install the required
|
||||
`dependencies <https://github.com/manpaz/python-escpos/wiki/Dependencies>`__
|
||||
Installation with PIP
|
||||
---------------------
|
||||
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``
|
||||
|
||||
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
|
||||
``/etc/udev/rules.d/99-escpos.rules`` and add the following:
|
||||
``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" 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``
|
||||
|
||||
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
|
||||
* Change directory to python-escpos and install the package
|
||||
If you only want to enable it for python-escpos, or global activation does not work, try this:
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
# cd python-escpos
|
||||
# python setup.py build
|
||||
# sudo python setup.py install
|
||||
|
||||
* Enjoy !!!
|
||||
eval "$(register-python-argcomplete python-escpos)"
|
||||
|
||||
|
||||
|
@@ -88,6 +88,7 @@ set("align", "font", "type", width, height, invert, smooth, flip)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Set text properties.
|
||||
|
||||
* ``align`` set horizontal position for text, the possible values are:
|
||||
|
||||
* CENTER
|
||||
@@ -106,6 +107,7 @@ cut("mode")
|
||||
^^^^^^^^^^^
|
||||
|
||||
Cut paper.
|
||||
|
||||
* ``mode`` set a full or partial cut. *Default:* full
|
||||
|
||||
**Partial cut is not implemented in all printers.**
|
||||
@@ -132,6 +134,7 @@ control("align")
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
Carrier feed and tabs.
|
||||
|
||||
* ``align`` is a string which takes any of the following values:
|
||||
|
||||
* LF *for Line Feed*
|
||||
|
@@ -32,11 +32,25 @@ Network("host", port)
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Based on socket
|
||||
|
||||
* ``host`` is an alphanumeric host name, could be either DNS host name or IP address.
|
||||
* ``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")
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Printcap printers
|
||||
|
||||
* ``file_name`` is the full path to the device file name
|
||||
|
@@ -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>`_
|
||||
|
||||
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
|
||||
----------------------------------------
|
||||
|
||||
|
7
readthedocs.yml
Normal file
7
readthedocs.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
formats:
|
||||
- pdf
|
||||
- epub
|
||||
requirements_file: doc/requirements.txt
|
||||
python:
|
||||
version: 2
|
||||
setup_py_install: true
|
23
setup.py
23
setup.py
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -45,9 +45,24 @@ class Tox(test_command):
|
||||
errno = tox.cmdline(args=args)
|
||||
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(
|
||||
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',
|
||||
download_url='https://github.com/python-escpos/python-escpos/archive/master.zip',
|
||||
description='Python library to manipulate ESC/POS Printers',
|
||||
@@ -95,11 +110,13 @@ setup(
|
||||
'six',
|
||||
'appdirs',
|
||||
'pyyaml',
|
||||
'argparse',
|
||||
'argcomplete',
|
||||
],
|
||||
setup_requires=[
|
||||
'setuptools_scm',
|
||||
],
|
||||
tests_require=['tox', 'nose', 'scripttest'],
|
||||
tests_require=['tox', 'nose', 'scripttest', 'mock', 'hypothesis'],
|
||||
cmdclass={'test': Tox},
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
@@ -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"]
|
||||
|
||||
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.'
|
||||
)
|
||||
|
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
""" CLI
|
||||
|
||||
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
|
||||
|
||||
import argparse
|
||||
try:
|
||||
import argcomplete
|
||||
except ImportError:
|
||||
# this CLI works nevertheless without argcomplete
|
||||
pass # noqa
|
||||
import sys
|
||||
import six
|
||||
from . import config
|
||||
from . import version
|
||||
|
||||
|
||||
# Must be defined before it's used in DEMO_FUNCTIONS
|
||||
@@ -448,14 +455,17 @@ def main():
|
||||
# Allow config file location to be passed
|
||||
parser.add_argument(
|
||||
'-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
|
||||
# cli [subparser] -args
|
||||
command_subparsers = parser.add_subparsers(
|
||||
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
|
||||
for command in ESCPOS_COMMANDS:
|
||||
@@ -491,6 +501,14 @@ def main():
|
||||
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
|
||||
args_dict = vars(parser.parse_args())
|
||||
if not args_dict:
|
||||
@@ -498,6 +516,12 @@ def main():
|
||||
sys.exit()
|
||||
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
|
||||
config_path = command_arguments.pop('config', None)
|
||||
|
||||
@@ -511,6 +535,9 @@ def main():
|
||||
|
||||
target_command = command_arguments.pop('func')
|
||||
|
||||
# remove helper-argument 'parser' from dict
|
||||
command_arguments.pop('parser', None)
|
||||
|
||||
if hasattr(printer, target_command):
|
||||
# print command with args
|
||||
getattr(printer, target_command)(**command_arguments)
|
||||
|
@@ -55,11 +55,18 @@ _CUT_PAPER = lambda m: GS + b'V' + m
|
||||
PAPER_FULL_CUT = _CUT_PAPER(b'\x00') # Full cut paper
|
||||
PAPER_PART_CUT = _CUT_PAPER(b'\x01') # Partial cut paper
|
||||
|
||||
# Beep
|
||||
BEEP = b'\x07'
|
||||
|
||||
# Panel buttons (e.g. the FEED button)
|
||||
_PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n)
|
||||
PANEL_BUTTON_ON = _PANEL_BUTTON(0) # enable 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
|
||||
# TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll
|
||||
# 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_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
|
||||
CHARCODE_PC437 = ESC + b'\x74\x00' # USA: Standard Europe
|
||||
CHARCODE_JIS = ESC + b'\x74\x01' # Japanese Katakana
|
||||
@@ -168,10 +187,7 @@ BARCODE_TYPE_B = {
|
||||
'NW7': _SET_BARCODE_TYPE(71),
|
||||
'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7
|
||||
'CODE93': _SET_BARCODE_TYPE(72),
|
||||
# These are all the same barcode, but using different charcter sets
|
||||
'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
|
||||
'CODE128': _SET_BARCODE_TYPE(73),
|
||||
'GS1-128': _SET_BARCODE_TYPE(74),
|
||||
'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75),
|
||||
'GS1 DATABAR TRUNCATED': _SET_BARCODE_TYPE(76),
|
||||
|
@@ -56,7 +56,8 @@ class Escpos(object):
|
||||
"""
|
||||
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
|
||||
|
||||
You can select whether the printer should print in high density or not. The default value is high density.
|
||||
@@ -76,10 +77,21 @@ class Escpos(object):
|
||||
: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 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)
|
||||
|
||||
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":
|
||||
# GS v 0, raster format bit image
|
||||
density_byte = (0 if high_density_horizontal else 1) + (0 if high_density_vertical else 2)
|
||||
@@ -553,6 +565,35 @@ class Escpos(object):
|
||||
else:
|
||||
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=''):
|
||||
""" Cut paper.
|
||||
|
||||
|
@@ -8,6 +8,12 @@ This module contains the image format handler :py:class:`EscposImage`.
|
||||
: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
|
||||
|
||||
|
||||
@@ -30,6 +36,9 @@ class EscposImage(object):
|
||||
else:
|
||||
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
|
||||
# to strip alpha.
|
||||
img_original = img_original.convert('RGBA')
|
||||
@@ -88,3 +97,21 @@ class EscposImage(object):
|
||||
Convert image to raster-format binary
|
||||
"""
|
||||
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
|
||||
|
@@ -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 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 out_ep: Output end point
|
||||
"""
|
||||
Escpos.__init__(self, *args, **kwargs)
|
||||
self.idVendor = idVendor
|
||||
self.idProduct = idProduct
|
||||
self.interface = interface
|
||||
self.timeout = timeout
|
||||
self.in_ep = in_ep
|
||||
self.out_ep = out_ep
|
||||
self.open()
|
||||
@@ -82,7 +82,7 @@ class Usb(Escpos):
|
||||
:param msg: arbitrary code to be printed
|
||||
:type msg: bytes
|
||||
"""
|
||||
self.device.write(self.out_ep, msg, self.interface)
|
||||
self.device.write(self.out_ep, msg, self.timeout)
|
||||
|
||||
def close(self):
|
||||
""" 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 auto_flush: automatically call flush after every call of _raw()
|
||||
"""
|
||||
Escpos.__init__(self, *args, **kwargs)
|
||||
self.devfile = devfile
|
||||
self.auto_flush = auto_flush
|
||||
self.open()
|
||||
|
||||
def open(self):
|
||||
@@ -256,6 +258,8 @@ class File(Escpos):
|
||||
:type msg: bytes
|
||||
"""
|
||||
self.device.write(msg)
|
||||
if self.auto_flush:
|
||||
self.flush()
|
||||
|
||||
def close(self):
|
||||
""" Close system file """
|
||||
|
@@ -11,6 +11,7 @@ import os
|
||||
import sys
|
||||
from scripttest import TestFileEnvironment
|
||||
from nose.tools import assert_equals
|
||||
import escpos
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
@@ -60,8 +61,7 @@ class TestCLI:
|
||||
)
|
||||
|
||||
self.default_args = (
|
||||
sys.executable,
|
||||
'-mescpos.cli',
|
||||
'python-escpos',
|
||||
'-c',
|
||||
CONFIGFILE,
|
||||
)
|
||||
@@ -79,10 +79,16 @@ class TestCLI:
|
||||
|
||||
def test_cli_help(self):
|
||||
""" 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 '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):
|
||||
""" Make sure text returns what we sent it """
|
||||
test_text = 'this is some text'
|
||||
|
@@ -130,3 +130,12 @@ def test_graphics_transparent():
|
||||
instance = printer.Dummy()
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
32
test/test_function_qr_non-native.py
Normal file
32
test/test_function_qr_non-native.py
Normal 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
26
test/test_functions.py
Normal 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)
|
@@ -43,6 +43,20 @@ def test_image_white():
|
||||
_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):
|
||||
"""
|
||||
Load an image, and test whether raster & column formatted output, sizes, etc match expectations.
|
||||
|
70
test/test_printer_file.py
Normal file
70
test/test_printer_file.py
Normal 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
|
Reference in New Issue
Block a user