Compare commits
292 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
11452034a3 | ||
![]() |
d2e2ea88a6 | ||
![]() |
2416303805 | ||
![]() |
38f9835931 | ||
![]() |
7c732ee615 | ||
![]() |
3d98eb8b9c | ||
![]() |
619d80a867 | ||
![]() |
d3f74ced5d | ||
![]() |
0524b0576e | ||
![]() |
c92d4463ae | ||
![]() |
6e74748773 | ||
![]() |
a2e188cecf | ||
![]() |
c1a6da9aaa | ||
![]() |
37baf5cd34 | ||
![]() |
5c209dd557 | ||
![]() |
bef1a9cccf | ||
![]() |
042f945a09 | ||
![]() |
2cf30c7f05 | ||
![]() |
34d929806c | ||
![]() |
c5d34cc268 | ||
![]() |
fd3f1067fe | ||
![]() |
80b714fdae | ||
![]() |
457c62cc7f | ||
![]() |
a5cae3adb7 | ||
![]() |
9f5eed0020 | ||
![]() |
57dd60c13f | ||
![]() |
0ec83387d5 | ||
![]() |
36e0a52e2d | ||
![]() |
214b4def14 | ||
![]() |
c13a0715e4 | ||
![]() |
925d19447d | ||
![]() |
5be81e4703 | ||
![]() |
ab39fd2b99 | ||
![]() |
685a1f504c | ||
![]() |
7529642788 | ||
![]() |
0907b6aa8b | ||
![]() |
87438f9efa | ||
![]() |
e8e91eba80 | ||
![]() |
3e5509238e | ||
![]() |
08051d48d0 | ||
![]() |
2676a802bd | ||
![]() |
6697922b74 | ||
![]() |
e814396bd8 | ||
![]() |
adf73f3790 | ||
![]() |
1e490b6de8 | ||
![]() |
910f2fbf2f | ||
![]() |
16569067c2 | ||
![]() |
a0dc993f2f | ||
![]() |
1b00477144 | ||
![]() |
ddaf126c20 | ||
![]() |
6a2673d01d | ||
![]() |
f6ae109bb9 | ||
![]() |
0a8057414c | ||
![]() |
a34e306ea8 | ||
![]() |
2588406831 | ||
![]() |
b6ada13f9b | ||
![]() |
eadd9f7583 | ||
![]() |
471222eda9 | ||
![]() |
c6cc28254e | ||
![]() |
f903af6730 | ||
![]() |
a0d8689141 | ||
![]() |
44c79eaf11 | ||
![]() |
4584e3138a | ||
![]() |
6b445b3fb1 | ||
![]() |
ba03538c50 | ||
![]() |
b45afbb297 | ||
![]() |
59afcf778f | ||
![]() |
36debff72c | ||
![]() |
2aa0878f54 | ||
![]() |
7b18afec75 | ||
![]() |
f39c4227ec | ||
![]() |
481285625b | ||
![]() |
1ee657a750 | ||
![]() |
0121aa0bfb | ||
![]() |
7547bfddd2 | ||
![]() |
f6e0edc7c7 | ||
![]() |
df77b7dec3 | ||
![]() |
bf3012b882 | ||
![]() |
68b3ec4d89 | ||
![]() |
a236ccebe9 | ||
![]() |
1a1c032d6a | ||
![]() |
f5a7d681eb | ||
![]() |
abebf7eb99 | ||
![]() |
fca363119c | ||
![]() |
8ed1441c4c | ||
![]() |
062282bf47 | ||
![]() |
3fe4589b8b | ||
![]() |
b9c9189ca7 | ||
![]() |
5ecae9d585 | ||
![]() |
39da32ca85 | ||
![]() |
e545999aa2 | ||
![]() |
3017c14df2 | ||
![]() |
3831665da4 | ||
![]() |
b6b30d7c82 | ||
![]() |
39165fcb41 | ||
![]() |
fd6a0e4bda | ||
![]() |
38b58eb39a | ||
![]() |
cabb2c930a | ||
![]() |
0c3f273fa1 | ||
![]() |
76f300ea18 | ||
![]() |
f504d2dc15 | ||
![]() |
5a2ca10874 | ||
![]() |
8101e1ec9f | ||
![]() |
1a000d29fd | ||
![]() |
28b82fb54f | ||
![]() |
ee223670bf | ||
![]() |
a445c4205a | ||
![]() |
00ef7f129b | ||
![]() |
8c186d912d | ||
![]() |
14ae1a7d89 | ||
![]() |
ca3b4665a2 | ||
![]() |
4f92247ed6 | ||
![]() |
5ba751d89b | ||
![]() |
8e44c5126e | ||
![]() |
7a58109928 | ||
![]() |
9b40c0860f | ||
![]() |
3ec00ae16e | ||
![]() |
7afd5e75d4 | ||
![]() |
7d74dcac00 | ||
![]() |
cdf8f6be09 | ||
![]() |
d5e3d85c4b | ||
![]() |
2bb9756d28 | ||
![]() |
5dc676bea7 | ||
![]() |
c26c875b61 | ||
![]() |
39e912bef4 | ||
![]() |
c7b36916e7 | ||
![]() |
4548dd3830 | ||
![]() |
d5073626ae | ||
![]() |
1a866f4d1f | ||
![]() |
ef31e58d26 | ||
![]() |
01328db808 | ||
![]() |
1adc66992d | ||
![]() |
73be1f2c48 | ||
![]() |
27352b071c | ||
![]() |
87a73beb31 | ||
![]() |
6096c15b80 | ||
![]() |
133241e7e9 | ||
![]() |
eea3e76eed | ||
![]() |
28be6a2041 | ||
![]() |
d523b4d342 | ||
![]() |
99291abd10 | ||
![]() |
81b3c1a63a | ||
![]() |
323b205f70 | ||
![]() |
2dc70677d7 | ||
![]() |
e355d2cbdf | ||
![]() |
126e0e269a | ||
![]() |
309866f8c9 | ||
![]() |
8596148271 | ||
![]() |
8b454c4765 | ||
![]() |
f76db4e0d4 | ||
![]() |
e43e95bcff | ||
![]() |
b2ff39b6b1 | ||
![]() |
64e63b0180 | ||
![]() |
e49c35abc1 | ||
![]() |
f676782130 | ||
![]() |
086b407b62 | ||
![]() |
1a2b8f1df2 | ||
![]() |
68641572f9 | ||
![]() |
7fdccb7245 | ||
![]() |
fee650faba | ||
![]() |
1009cf8988 | ||
![]() |
916b09a84c | ||
![]() |
80f6200915 | ||
![]() |
f354791285 | ||
![]() |
205728f5be | ||
![]() |
046cf1ff5b | ||
![]() |
75252a3797 | ||
![]() |
21992d7017 | ||
![]() |
4c406c1775 | ||
![]() |
5b5c2c0dba | ||
![]() |
6b56de67a7 | ||
![]() |
c9ea90cd82 | ||
![]() |
5d5412ba73 | ||
![]() |
7005ba5899 | ||
![]() |
18518fa901 | ||
![]() |
bda7f85346 | ||
![]() |
70307d0f24 | ||
![]() |
5a03f0fc7c | ||
![]() |
4cfc4fd564 | ||
![]() |
d96dcd8e7e | ||
![]() |
9d12c7faab | ||
![]() |
39d1c1d587 | ||
![]() |
dbb0081287 | ||
![]() |
68a9dcc47b | ||
![]() |
7f921c667b | ||
![]() |
d888a39b6f | ||
![]() |
250455ae23 | ||
![]() |
d9ffb03089 | ||
![]() |
5c49e0103c | ||
![]() |
60bc6b7d5c | ||
![]() |
cdf997aff5 | ||
![]() |
8b8162c1a9 | ||
![]() |
870144aac6 | ||
![]() |
e48755f7d0 | ||
![]() |
8d00e63b87 | ||
![]() |
7c98de6727 | ||
![]() |
8000cf258b | ||
![]() |
b7c6edc9e1 | ||
![]() |
f303a38a8d | ||
![]() |
d672ca1268 | ||
![]() |
3e200a86b9 | ||
![]() |
95f5b5ed48 | ||
![]() |
331fe6a93a | ||
![]() |
ae37de2577 | ||
![]() |
720545979c | ||
![]() |
0c56f5c831 | ||
![]() |
248ddf8456 | ||
![]() |
713380baf9 | ||
![]() |
cd2da59fc2 | ||
![]() |
d00fc5016b | ||
![]() |
939e66834e | ||
![]() |
198b50ac5e | ||
![]() |
41c6afd3b8 | ||
![]() |
f25521f22f | ||
![]() |
0e907644d9 | ||
![]() |
a6ec674828 | ||
![]() |
598c893943 | ||
![]() |
1614298863 | ||
![]() |
a921061b40 | ||
![]() |
050419f117 | ||
![]() |
3e89a10bed | ||
![]() |
e988873999 | ||
![]() |
f7a2caee72 | ||
![]() |
4e1f9db5c7 | ||
![]() |
9159aafd18 | ||
![]() |
020ba4145c | ||
![]() |
592dbe15f6 | ||
![]() |
69680b04e9 | ||
![]() |
e88a19ef2d | ||
![]() |
c2b45748e1 | ||
![]() |
8b8ca76af6 | ||
![]() |
e5cd37bfbc | ||
![]() |
3d61445e2c | ||
![]() |
8b8ab80e5f | ||
![]() |
029549aaae | ||
![]() |
6cc325b395 | ||
![]() |
a9844b1a1b | ||
![]() |
e16e666dce | ||
![]() |
50fbf2873f | ||
![]() |
ce0b0d5ba3 | ||
![]() |
2aafc34ae1 | ||
![]() |
c3fbdf28d4 | ||
![]() |
8a77c963c3 | ||
![]() |
b68a5782fe | ||
![]() |
06a68d1c97 | ||
![]() |
aa4ffdd21b | ||
![]() |
05ea0e929e | ||
![]() |
50b0691e68 | ||
![]() |
06f2e1e731 | ||
![]() |
577de10cb4 | ||
![]() |
242399ebe2 | ||
![]() |
8fd05eb02b | ||
![]() |
c1d985eeaf | ||
![]() |
0dacc35d94 | ||
![]() |
8b5798eedf | ||
![]() |
ef8035527c | ||
![]() |
07d8e073ae | ||
![]() |
096445631f | ||
![]() |
3ea52e52fd | ||
![]() |
371d5e78bd | ||
![]() |
7e3b6ce586 | ||
![]() |
4bb94c2662 | ||
![]() |
ef84a5150f | ||
![]() |
cd5969e843 | ||
![]() |
1a1ed5e7fc | ||
![]() |
e23ce9bfc2 | ||
![]() |
546f47edcc | ||
![]() |
3f6528da07 | ||
![]() |
5eaa6f26d0 | ||
![]() |
37d7f34241 | ||
![]() |
280000d6ed | ||
![]() |
95082067e4 | ||
![]() |
f0f84e1215 | ||
![]() |
cae02976a3 | ||
![]() |
25b650c935 | ||
![]() |
6734864a5b | ||
![]() |
9550ad1068 | ||
![]() |
0ef2951c7e | ||
![]() |
e623799fd7 | ||
![]() |
47aa4a96c9 | ||
![]() |
bb329b093b | ||
![]() |
dd228c9fda | ||
![]() |
7da2e32e3c | ||
![]() |
b99c076bae | ||
![]() |
afc6834082 | ||
![]() |
d93e76e904 | ||
![]() |
f3933d5d20 | ||
![]() |
517435efad | ||
![]() |
122ff9a363 | ||
![]() |
708f7e97d1 | ||
![]() |
8a3850ea64 | ||
![]() |
34f562d64a |
26
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!--
|
||||||
|
Please feel free to delete any sections that aren't relevant.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- mark with x between the [ ] -->
|
||||||
|
I have:
|
||||||
|
- [ ] searched open and closed issues for duplicates
|
||||||
|
|
||||||
|
### Bug description
|
||||||
|
|
||||||
|
### Steps to reproduce
|
||||||
|
- add your steps here
|
||||||
|
- as a list
|
||||||
|
- using hyphens
|
||||||
|
|
||||||
|
### Device info
|
||||||
|
<!-- 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:**
|
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
### Contributor checklist
|
||||||
|
<!-- mark with x between the brackets -->
|
||||||
|
- [ ] I have read the CONTRIBUTING.rst
|
||||||
|
- [ ] I have tested my contribution on these devices:
|
||||||
|
* e.g. Epson TM-T88II
|
||||||
|
- [ ] My contribution is ready to be merged as is
|
||||||
|
|
||||||
|
----------
|
||||||
|
|
||||||
|
### Description
|
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# python temporary files
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# editor autosaves, data and file browser files
|
||||||
|
$~
|
||||||
|
.idea/
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# temporary data
|
||||||
|
temp
|
||||||
|
|
||||||
|
# packaging and testing
|
||||||
|
.tox/
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
*.egg
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
.coverage
|
||||||
|
src/escpos/version.py
|
||||||
|
.hypothesis
|
||||||
|
|
||||||
|
# testing temporary directories
|
||||||
|
test/test-cli-output/
|
10
.hgignore
@@ -1,10 +0,0 @@
|
|||||||
# python temporary files
|
|
||||||
syntax: glob
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# editor autosaves
|
|
||||||
$~
|
|
||||||
|
|
||||||
# temporary data
|
|
||||||
syntax: regexp
|
|
||||||
temp
|
|
36
.travis.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
language: python
|
||||||
|
sudo: false
|
||||||
|
cache: pip
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- graphviz
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- python: 2.7
|
||||||
|
env: TOXENV=py27
|
||||||
|
- python: 3.3
|
||||||
|
env: TOXENV=py33
|
||||||
|
- python: 3.4
|
||||||
|
env: TOXENV=py34
|
||||||
|
- python: 3.5
|
||||||
|
env: TOXENV=py35
|
||||||
|
- python: 3.5-dev
|
||||||
|
env: TOXENV=py35
|
||||||
|
- python: nightly
|
||||||
|
env: TOXENV=py36
|
||||||
|
- python: pypy
|
||||||
|
env: TOXENV=pypy
|
||||||
|
- python: pypy3
|
||||||
|
env: TOXENV=pypy3
|
||||||
|
- python: 2.7
|
||||||
|
env: TOXENV=docs
|
||||||
|
allow_failures:
|
||||||
|
- python: 3.5-dev
|
||||||
|
- python: nightly
|
||||||
|
- python: pypy3
|
||||||
|
before_install:
|
||||||
|
- pip install tox codecov
|
||||||
|
script:
|
||||||
|
- tox
|
||||||
|
- codecov
|
22
CHANGELOG
@@ -1,22 +0,0 @@
|
|||||||
CHANGELOG
|
|
||||||
|
|
||||||
* 2012-11-15 - Version 1.0
|
|
||||||
- Issue #2: Added ethernet support
|
|
||||||
- Issue #3: Added compatibility with libusb-1.0.1
|
|
||||||
- Issue #4: Fixed typo in escpos.py
|
|
||||||
|
|
||||||
* 2013-03-14 - Version 1.0.1
|
|
||||||
- Issue #8: Fixed set font
|
|
||||||
- Added QR support
|
|
||||||
|
|
||||||
* 2013-12-30 - Version 1.0.2
|
|
||||||
- Issue #5: Fixed vertical tab
|
|
||||||
- Issue #9: Fixed identation inconsistence
|
|
||||||
|
|
||||||
* 2014-02-23 - Version 1.0.3
|
|
||||||
- Issue #18: Added quad-area characters (Sent by syncman1x@gmail.com)
|
|
||||||
- Added exception for PIL import
|
|
||||||
|
|
||||||
* 2014-05-20 - Version 1.0.4
|
|
||||||
- Added charcode tables
|
|
||||||
- Fixed Horizontal Tab
|
|
141
CHANGELOG.rst
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
*********
|
||||||
|
Changelog
|
||||||
|
*********
|
||||||
|
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"
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
This version is based on the original version of python-escpos by Manuel F Martinez. However, many contributions have
|
||||||
|
greatly improved the old codebase. Since this version does not completely match the interface of the version published
|
||||||
|
on PyPi and has many improvements, it will be released as version 2.0.0.
|
||||||
|
|
||||||
|
changes
|
||||||
|
^^^^^^^
|
||||||
|
- refactor complete code in order to be compatible with Python 2 and 3
|
||||||
|
- modernize packaging
|
||||||
|
- add testing and CI
|
||||||
|
- merge various forks into codebase, fixing multiple issues with barcode-, QR-printing, cashdraw and structure
|
||||||
|
- improve the documentation
|
||||||
|
- extend support of barcode-codes to type B
|
||||||
|
- add function to disable panel-buttons
|
||||||
|
- the text-functions are now intended for unicode, the driver will automatically encode the string based on the selected
|
||||||
|
codepage
|
||||||
|
- the image-functions are now much more flexible
|
||||||
|
- added a CLI
|
||||||
|
- restructured the constants
|
||||||
|
|
||||||
|
contributors
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
- Thomas van den Berg
|
||||||
|
- Michael Billington
|
||||||
|
- Nate Bookham
|
||||||
|
- Davis Goglin
|
||||||
|
- Christoph Heuel
|
||||||
|
- Patrick Kanzler
|
||||||
|
- Qian LinFeng
|
||||||
|
|
||||||
|
2016-01-24 - Version 1.0.9
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
- fix constant definition for PC1252
|
||||||
|
- move documentation to Sphinx
|
||||||
|
|
||||||
|
2015-10-27 - Version 1.0.8
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
- Merge pull request #59 from zouppen/master
|
||||||
|
- Support for images vertically longer than 256 pixels
|
||||||
|
- Sent by Joel Lehtonen <joel.lehtonen@koodilehto.fi>
|
||||||
|
- Updated README
|
||||||
|
|
||||||
|
2015-08-22 - Version 1.0.7
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
- Issue #57: Fixed transparent images
|
||||||
|
|
||||||
|
2015-07-06 - Version 1.0.6
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
- Merge pull request #53 from ldos/master
|
||||||
|
- Extended params for serial printers
|
||||||
|
- Sent by ldos <cafeteria.ldosalzira@gmail.com>
|
||||||
|
|
||||||
|
2015-04-21 - Version 1.0.5
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
- Merge pull request #45 from Krispy2009/master
|
||||||
|
- Raising the right error when wrong charcode is used
|
||||||
|
- Sent by Kristi <Krispy2009@gmail.com>
|
||||||
|
|
||||||
|
2014-05-20 - Version 1.0.4
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
- Issue #20: Added Density support (Sent by thomas.erbacher@ragapack.de)
|
||||||
|
- Added charcode tables
|
||||||
|
- Fixed Horizontal Tab
|
||||||
|
- Fixed code tabulators
|
||||||
|
|
||||||
|
2014-02-23 - Version 1.0.3
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
- Issue #18: Added quad-area characters (Sent by syncman1x@gmail.com)
|
||||||
|
- Added exception for PIL import
|
||||||
|
|
||||||
|
2013-12-30 - Version 1.0.2
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
- Issue #5: Fixed vertical tab
|
||||||
|
- Issue #9: Fixed identation inconsistence
|
||||||
|
|
||||||
|
2013-03-14 - Version 1.0.1
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
- Issue #8: Fixed set font
|
||||||
|
- Added QR support
|
||||||
|
|
||||||
|
2012-11-15 - Version 1.0
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
- Issue #2: Added ethernet support
|
||||||
|
- Issue #3: Added compatibility with libusb-1.0.1
|
||||||
|
- Issue #4: Fixed typo in escpos.py
|
80
CONTRIBUTING.rst
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
************
|
||||||
|
Contributing
|
||||||
|
************
|
||||||
|
|
||||||
|
This project is open to any kind of contribution. You can help with improving the documentation, adding fixes to the
|
||||||
|
code, providing test cases in code or as a description or just spreading the word. Please feel free to create an
|
||||||
|
issue or pull request.
|
||||||
|
In order to reduce the amount of work for everyone please try to adhere to good practice.
|
||||||
|
|
||||||
|
The pull requests and issues will be prefilled with templates. Please fill in your information where applicable.
|
||||||
|
|
||||||
|
This project uses `semantic versioning <http://semver.org/>`_ and tries to adhere to the proposed rules as
|
||||||
|
well as possible.
|
||||||
|
|
||||||
|
Style-Guide
|
||||||
|
-----------
|
||||||
|
|
||||||
|
When writing code please try to stick to these rules.
|
||||||
|
|
||||||
|
Python 2 and 3
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
We have rewritten the code in order to maintain compatibility with both Python 2 and Python 3.
|
||||||
|
In order to ensure that we do not miss any accidental degradation, please add these imports to the top
|
||||||
|
of every file of code:
|
||||||
|
|
||||||
|
.. code-block:: Python
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
Furthermore please be aware of the differences between Python 2 and 3. For
|
||||||
|
example `this guide <https://docs.python.org/3/howto/pyporting.html>`_ is helpful.
|
||||||
|
Special care has to be taken when dealing with strings and byte-strings. Please note
|
||||||
|
that the :py:meth:`~escpos.escpos.Escpos._raw`-method only accepts byte-strings.
|
||||||
|
Often you can achieve compatibility quite easily with a tool from the `six`-package.
|
||||||
|
|
||||||
|
PEP8
|
||||||
|
^^^^
|
||||||
|
This is not yet consequently done in every piece of code, but please try to ensure
|
||||||
|
that your code honors PEP8.
|
||||||
|
The checks by Landscape and QuantifiedCode that run on every PR will provide you with hints.
|
||||||
|
|
||||||
|
GIT
|
||||||
|
^^^
|
||||||
|
The master-branch contains code that has been released to PyPi. A release is marked with a tag
|
||||||
|
corresponding to the version. Issues are closed when they have been resolved in a released version
|
||||||
|
of the package.
|
||||||
|
|
||||||
|
When you have a change to make, begin by creating a new branch from the HEAD of `python-escpos/development`.
|
||||||
|
Name your branch to indicate what you are trying to achieve. Good branch names might
|
||||||
|
be `improve/text-handling`, `feature/enable-color-printing`.
|
||||||
|
|
||||||
|
Please try to group your commits into logical units. If you need to tidy up your branch, you can make use of a
|
||||||
|
git feature called an 'interactive rebase' before making a pull request. A small, self-contained change-set is
|
||||||
|
easier to review, and improves the chance of your code being merged.
|
||||||
|
Please also make sure that before creating your PR, your branch is rebased on a recent commit or you merged a recent
|
||||||
|
commit into your branch. This way you can ensure that your PR is without merge conflicts.
|
||||||
|
|
||||||
|
Docstrings
|
||||||
|
^^^^^^^^^^
|
||||||
|
This project tries to have a good documentation.
|
||||||
|
Please add a docstring to every method and class. Have a look at existing methods and classes for the style.
|
||||||
|
We use basically standard rst-docstrings for Sphinx.
|
||||||
|
|
||||||
|
Test
|
||||||
|
^^^^
|
||||||
|
Try to write tests whenever possible. Our goal for the future is 100% coverage.
|
||||||
|
We are currently using `nose` but might change in the future.
|
||||||
|
You can copy the structure from other testcases. Please remember to adapt the docstrings.
|
||||||
|
|
||||||
|
Further reading
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
For further best practices and hints on contributing please see the
|
||||||
|
`contribution-guide <http://www.contribution-guide.org/>`_. Should there be any contradictions between this guide
|
||||||
|
and the linked one, please stick to this text.
|
||||||
|
Aside from that feel free to create an issue or write an email if anything is unclear.
|
||||||
|
|
||||||
|
Thank you for your contribution!
|
8
MANIFEST
@@ -1,8 +0,0 @@
|
|||||||
# file GENERATED by distutils, do NOT edit
|
|
||||||
README
|
|
||||||
setup.py
|
|
||||||
escpos/__init__.py
|
|
||||||
escpos/constants.py
|
|
||||||
escpos/escpos.py
|
|
||||||
escpos/exceptions.py
|
|
||||||
escpos/printer.py
|
|
11
MANIFEST.in
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
include *.rst
|
||||||
|
include *.txt
|
||||||
|
include COPYING
|
||||||
|
include INSTALL
|
||||||
|
include tox.ini
|
||||||
|
recursive-include doc *.bat
|
||||||
|
recursive-include doc *.ico
|
||||||
|
recursive-include doc *.py
|
||||||
|
recursive-include doc *.rst
|
||||||
|
recursive-include doc *.txt
|
||||||
|
recursive-include doc Makefile
|
89
README
@@ -1,89 +0,0 @@
|
|||||||
ESCPOS
|
|
||||||
======
|
|
||||||
|
|
||||||
Python library to manipulate ESC/POS Printers.
|
|
||||||
|
|
||||||
------------------------------------------------------------------
|
|
||||||
1. Dependencies
|
|
||||||
|
|
||||||
In order to start getting access to your printer, you must ensure
|
|
||||||
you have previously installed the following python modules:
|
|
||||||
|
|
||||||
* pyusb (python-usb)
|
|
||||||
* PIL (Python Image Library)
|
|
||||||
|
|
||||||
------------------------------------------------------------------
|
|
||||||
2. Description
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
------------------------------------------------------------------
|
|
||||||
3. Define your printer
|
|
||||||
|
|
||||||
Before start create your Python ESC/POS printer instance, you must
|
|
||||||
see at your system for the printer parameters. This is done with
|
|
||||||
the 'lsusb' command.
|
|
||||||
|
|
||||||
First run the command to look for the "Vendor ID" and "Product ID",
|
|
||||||
then write down the values, these values are displayed just before
|
|
||||||
the name of the device with the following format:
|
|
||||||
|
|
||||||
xxxx:xxxx
|
|
||||||
|
|
||||||
Example:
|
|
||||||
Bus 002 Device 001: ID 1a2b:1a2b Device name
|
|
||||||
|
|
||||||
Write down the the values in question, then issue the following
|
|
||||||
command so you can get the "Interface" number and "End Point"
|
|
||||||
|
|
||||||
lsusb -vvv -d xxxx:xxxx | grep iInterface
|
|
||||||
lsusb -vvv -d xxxx:xxxx | grep bEndpointAddress | grep OUT
|
|
||||||
|
|
||||||
The first command will yields the "Interface" number that must
|
|
||||||
be handy to have and the second yields the "Output Endpoint"
|
|
||||||
address.
|
|
||||||
|
|
||||||
By default the "Interface" number is "0" and the "Output Endpoint"
|
|
||||||
address is "0x82", if you have other values then you can define
|
|
||||||
with your instance.
|
|
||||||
|
|
||||||
------------------------------------------------------------------
|
|
||||||
4. Define your instance
|
|
||||||
|
|
||||||
The following example shows how to initialize the Epson TM-TI88IV
|
|
||||||
*** NOTE: Always finish the sequence with Epson.cut() otherwise
|
|
||||||
you will endup with weird chars being printed.
|
|
||||||
|
|
||||||
from escpos import *
|
|
||||||
|
|
||||||
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
|
|
||||||
Epson = escpos.Escpos(0x04b8,0x0202,0)
|
|
||||||
Epson.text("Hello World")
|
|
||||||
Epson.image("logo.gif")
|
|
||||||
Epson.barcode
|
|
||||||
Epson.barcode('1324354657687','EAN13',64,2,'','')
|
|
||||||
Epson.cut()
|
|
||||||
|
|
||||||
------------------------------------------------------------------
|
|
||||||
5. Links
|
|
||||||
|
|
||||||
Please visit project homepage at:
|
|
||||||
http://repo.bashlinux.com/projects/escpos.html
|
|
||||||
|
|
||||||
Manuel F Martinez <manpaz@bashlinux.com>
|
|
||||||
|
|
71
README.rst
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#############################################################
|
||||||
|
python-escpos - Python library to manipulate ESC/POS Printers
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
.. image:: https://travis-ci.org/python-escpos/python-escpos.svg?branch=master
|
||||||
|
:target: https://travis-ci.org/python-escpos/python-escpos
|
||||||
|
: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
|
||||||
|
:target: https://landscape.io/github/python-escpos/python-escpos/master
|
||||||
|
:alt: Code Health
|
||||||
|
|
||||||
|
.. image:: https://codecov.io/github/python-escpos/python-escpos/coverage.svg?branch=master
|
||||||
|
:target: https://codecov.io/github/python-escpos/python-escpos?branch=master
|
||||||
|
:alt: Code Coverage
|
||||||
|
|
||||||
|
.. image:: https://readthedocs.org/projects/python-escpos/badge/?version=stable
|
||||||
|
:target: http://python-escpos.readthedocs.io/en/latest/?badge=stable
|
||||||
|
:alt: Documentation Status
|
||||||
|
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
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 library tries to implement the functions provided by the ESC/POS-commandset and supports sending text, images,
|
||||||
|
barcodes and qr-codes to the printer.
|
||||||
|
|
||||||
|
Text can be aligned/justified and fonts can be changed by size, type and weight.
|
||||||
|
|
||||||
|
Also, this module handles some hardware functionalities like cutting paper, control characters, printer reset
|
||||||
|
and similar functions.
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
------------
|
||||||
|
|
||||||
|
This library makes use of:
|
||||||
|
|
||||||
|
* pyusb for USB-printers
|
||||||
|
* Pillow for image printing
|
||||||
|
* qrcode for the generation of QR-codes
|
||||||
|
* pyserial for serial printers
|
||||||
|
|
||||||
|
Documentation and Usage
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
The basic usage is:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from escpos import *
|
||||||
|
|
||||||
|
""" 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()
|
||||||
|
|
||||||
|
The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_.
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
------------
|
||||||
|
|
||||||
|
This project is open for any contribution! Please see CONTRIBUTING.rst for more information.
|
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
|
||||||
|
|
1
doc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
_build/
|
177
doc/Makefile
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
PAPER =
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# User-friendly check for sphinx-build
|
||||||
|
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||||
|
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Internal variables.
|
||||||
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
|
PAPEROPT_letter = -D latex_paper_size=letter
|
||||||
|
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
|
# the i18n builder cannot share the environment and doctrees with the others
|
||||||
|
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
|
|
||||||
|
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " dirhtml to make HTML files named index.html in directories"
|
||||||
|
@echo " singlehtml to make a single large HTML file"
|
||||||
|
@echo " pickle to make pickle files"
|
||||||
|
@echo " json to make JSON files"
|
||||||
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
|
@echo " qthelp to make HTML files and a qthelp project"
|
||||||
|
@echo " devhelp to make HTML files and a Devhelp project"
|
||||||
|
@echo " epub to make an epub"
|
||||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||||
|
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||||
|
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||||
|
@echo " text to make text files"
|
||||||
|
@echo " man to make manual pages"
|
||||||
|
@echo " texinfo to make Texinfo files"
|
||||||
|
@echo " info to make Texinfo files and run them through makeinfo"
|
||||||
|
@echo " gettext to make PO message catalogs"
|
||||||
|
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||||
|
@echo " xml to make Docutils-native XML files"
|
||||||
|
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||||
|
@echo " linkcheck to check all external links for integrity"
|
||||||
|
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
|
html:
|
||||||
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
|
dirhtml:
|
||||||
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
|
singlehtml:
|
||||||
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||||
|
|
||||||
|
pickle:
|
||||||
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
|
json:
|
||||||
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
|
htmlhelp:
|
||||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
|
qthelp:
|
||||||
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||||
|
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||||
|
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-escpos.qhcp"
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-escpos.qhc"
|
||||||
|
|
||||||
|
devhelp:
|
||||||
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished."
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# mkdir -p $$HOME/.local/share/devhelp/python-escpos"
|
||||||
|
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-escpos"
|
||||||
|
@echo "# devhelp"
|
||||||
|
|
||||||
|
epub:
|
||||||
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||||
|
|
||||||
|
latex:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||||
|
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||||
|
"(use \`make latexpdf' here to do that automatically)."
|
||||||
|
|
||||||
|
latexpdf:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo "Running LaTeX files through pdflatex..."
|
||||||
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||||
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
|
latexpdfja:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||||
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||||
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
|
text:
|
||||||
|
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||||
|
|
||||||
|
man:
|
||||||
|
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||||
|
|
||||||
|
texinfo:
|
||||||
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||||
|
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||||
|
"(use \`make info' here to do that automatically)."
|
||||||
|
|
||||||
|
info:
|
||||||
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
|
@echo "Running Texinfo files through makeinfo..."
|
||||||
|
make -C $(BUILDDIR)/texinfo info
|
||||||
|
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||||
|
|
||||||
|
gettext:
|
||||||
|
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||||
|
|
||||||
|
changes:
|
||||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
|
@echo
|
||||||
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||||
|
@echo
|
||||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
|
doctest:
|
||||||
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||||
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
|
"results in $(BUILDDIR)/doctest/output.txt."
|
||||||
|
|
||||||
|
xml:
|
||||||
|
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||||
|
|
||||||
|
pseudoxml:
|
||||||
|
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
0
doc/_static/.gitignore
vendored
Normal file
10
doc/api/config.rst
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Config
|
||||||
|
------
|
||||||
|
Module :py:mod:`escpos.config`
|
||||||
|
|
||||||
|
.. automodule:: escpos.config
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
:member-order: bysource
|
10
doc/api/constants.rst
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Constants
|
||||||
|
---------
|
||||||
|
Module :py:mod:`escpos.constants`
|
||||||
|
|
||||||
|
.. automodule:: escpos.constants
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
:member-order: bysource
|
10
doc/api/escpos.rst
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Esc/Pos
|
||||||
|
-------
|
||||||
|
Module :py:mod:`escpos.escpos`
|
||||||
|
|
||||||
|
.. automodule:: escpos.escpos
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
:member-order: bysource
|
9
doc/api/exceptions.rst
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
Exceptions
|
||||||
|
----------
|
||||||
|
Module :py:mod:`escpos.exceptions`
|
||||||
|
|
||||||
|
.. automodule:: escpos.exceptions
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
:show-inheritance:
|
||||||
|
:member-order: bysource
|
10
doc/api/image.rst
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Image helper
|
||||||
|
------------
|
||||||
|
Module :py:mod:`escpos.image`
|
||||||
|
|
||||||
|
.. automodule:: escpos.image
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
:member-order: bysource
|
9
doc/api/printer.rst
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
Printer implementations
|
||||||
|
-----------------------
|
||||||
|
Module :py:mod:`escpos.printer`
|
||||||
|
|
||||||
|
.. automodule:: escpos.printer
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
:member-order: bysource
|
294
doc/conf.py
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# python-escpos documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Sat Dec 26 14:28:42 2015.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its
|
||||||
|
# containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
on_rtd = os.getenv('READTHEDOCS') == 'True'
|
||||||
|
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
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
sys.path.insert(0, os.path.abspath('../src'))
|
||||||
|
root = os.path.relpath(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
|
# -- General configuration ------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
|
extensions = [
|
||||||
|
'sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.doctest',
|
||||||
|
'sphinx.ext.todo',
|
||||||
|
'sphinx.ext.coverage',
|
||||||
|
'sphinx.ext.viewcode',
|
||||||
|
'sphinx.ext.todo',
|
||||||
|
'sphinx.ext.graphviz',
|
||||||
|
'sphinx.ext.inheritance_diagram',
|
||||||
|
]
|
||||||
|
|
||||||
|
# supress warnings for external images
|
||||||
|
suppress_warnings = [
|
||||||
|
'image.nonlocal_uri',
|
||||||
|
]
|
||||||
|
|
||||||
|
# enable todos
|
||||||
|
todo_include_todos = True
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix of source filenames.
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The encoding of source files.
|
||||||
|
#source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'python-escpos'
|
||||||
|
copyright = u'2016, Manuel F Martinez and others'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |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 = '.'.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.
|
||||||
|
#language = None
|
||||||
|
|
||||||
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
|
# non-false value, then it is used:
|
||||||
|
#today = ''
|
||||||
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
|
#today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
exclude_patterns = ['_build']
|
||||||
|
|
||||||
|
# The reST default role (used for this markup: `text`) to use for all
|
||||||
|
# documents.
|
||||||
|
#default_role = None
|
||||||
|
|
||||||
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
#add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
#add_module_names = True
|
||||||
|
|
||||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
|
# output. They are ignored by default.
|
||||||
|
#show_authors = False
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# A list of ignored prefixes for module index sorting.
|
||||||
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||||
|
#keep_warnings = False
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output ----------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
if on_rtd:
|
||||||
|
html_theme = 'default'
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
import sphinx_rtd_theme
|
||||||
|
html_theme = 'sphinx_rtd_theme'
|
||||||
|
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||||
|
except ImportError:
|
||||||
|
print("no sphinx_rtd_theme found, switching to nature")
|
||||||
|
html_theme = 'default'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
|
#html_theme_path = []
|
||||||
|
|
||||||
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
|
# "<project> v<release> documentation".
|
||||||
|
#html_title = None
|
||||||
|
|
||||||
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
|
#html_short_title = None
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
|
# of the sidebar.
|
||||||
|
#html_logo = None
|
||||||
|
|
||||||
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
|
# pixels large.
|
||||||
|
html_favicon = 'pyescpos.ico'
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
# Add any extra paths that contain custom files (such as robots.txt or
|
||||||
|
# .htaccess) here, relative to this directory. These files are copied
|
||||||
|
# directly to the root of the documentation.
|
||||||
|
#html_extra_path = []
|
||||||
|
|
||||||
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
|
# using the given strftime format.
|
||||||
|
#html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
|
# typographically correct entities.
|
||||||
|
#html_use_smartypants = True
|
||||||
|
|
||||||
|
# Custom sidebar templates, maps document names to template names.
|
||||||
|
#html_sidebars = {}
|
||||||
|
|
||||||
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
|
# template names.
|
||||||
|
#html_additional_pages = {}
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#html_domain_indices = True
|
||||||
|
|
||||||
|
# If false, no index is generated.
|
||||||
|
#html_use_index = True
|
||||||
|
|
||||||
|
# If true, the index is split into individual pages for each letter.
|
||||||
|
#html_split_index = False
|
||||||
|
|
||||||
|
# If true, links to the reST sources are added to the pages.
|
||||||
|
#html_show_sourcelink = True
|
||||||
|
|
||||||
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_sphinx = True
|
||||||
|
|
||||||
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_copyright = True
|
||||||
|
|
||||||
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
|
# base URL from which the finished HTML is served.
|
||||||
|
#html_use_opensearch = ''
|
||||||
|
|
||||||
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
|
#html_file_suffix = None
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'python-escposdoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#'papersize': 'letterpaper',
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#'preamble': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title,
|
||||||
|
# author, documentclass [howto, manual, or own class]).
|
||||||
|
latex_documents = [
|
||||||
|
('index', 'python-escpos.tex', u'python-escpos Documentation',
|
||||||
|
u'Manuel F Martinez and others', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
# the title page.
|
||||||
|
#latex_logo = None
|
||||||
|
|
||||||
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
|
# not chapters.
|
||||||
|
#latex_use_parts = False
|
||||||
|
|
||||||
|
# If true, show page references after internal links.
|
||||||
|
#latex_show_pagerefs = False
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#latex_show_urls = False
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output ---------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
('index', 'python-escpos', u'python-escpos Documentation',
|
||||||
|
[u'Manuel F Martinez and others'], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output -------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
texinfo_documents = [
|
||||||
|
('index', 'python-escpos', u'python-escpos Documentation',
|
||||||
|
u'Manuel F Martinez and others', 'python-escpos', 'One line description of project.',
|
||||||
|
'Miscellaneous'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#texinfo_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#texinfo_domain_indices = True
|
||||||
|
|
||||||
|
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||||
|
#texinfo_show_urls = 'footnote'
|
||||||
|
|
||||||
|
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||||
|
#texinfo_no_detailmenu = False
|
1
doc/dev/changelog.rst
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.. include:: ../../CHANGELOG.rst
|
1
doc/dev/contributing.rst
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.. include:: ../../CONTRIBUTING.rst
|
BIN
doc/download/barcode.bin
Normal file
47
doc/index.rst
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
.. python-escpos documentation master file, created by
|
||||||
|
sphinx-quickstart on Sat Dec 26 14:28:42 2015.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
.. include:: ../README.rst
|
||||||
|
|
||||||
|
Content
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
:caption: User Documentation
|
||||||
|
|
||||||
|
user/installation
|
||||||
|
user/methods
|
||||||
|
user/printers
|
||||||
|
user/raspi
|
||||||
|
user/todo
|
||||||
|
user/usage
|
||||||
|
user/barcode
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
:caption: Developer Documentation
|
||||||
|
|
||||||
|
dev/contributing
|
||||||
|
dev/changelog
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
:caption: API Documentation
|
||||||
|
|
||||||
|
api/escpos
|
||||||
|
api/printer
|
||||||
|
api/constants
|
||||||
|
api/exceptions
|
||||||
|
api/config
|
||||||
|
api/image
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
||||||
|
|
242
doc/make.bat
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
|
||||||
|
REM Command file for Sphinx documentation
|
||||||
|
|
||||||
|
if "%SPHINXBUILD%" == "" (
|
||||||
|
set SPHINXBUILD=sphinx-build
|
||||||
|
)
|
||||||
|
set BUILDDIR=_build
|
||||||
|
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||||
|
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||||
|
if NOT "%PAPER%" == "" (
|
||||||
|
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||||
|
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "" goto help
|
||||||
|
|
||||||
|
if "%1" == "help" (
|
||||||
|
:help
|
||||||
|
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||||
|
echo. html to make standalone HTML files
|
||||||
|
echo. dirhtml to make HTML files named index.html in directories
|
||||||
|
echo. singlehtml to make a single large HTML file
|
||||||
|
echo. pickle to make pickle files
|
||||||
|
echo. json to make JSON files
|
||||||
|
echo. htmlhelp to make HTML files and a HTML help project
|
||||||
|
echo. qthelp to make HTML files and a qthelp project
|
||||||
|
echo. devhelp to make HTML files and a Devhelp project
|
||||||
|
echo. epub to make an epub
|
||||||
|
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||||
|
echo. text to make text files
|
||||||
|
echo. man to make manual pages
|
||||||
|
echo. texinfo to make Texinfo files
|
||||||
|
echo. gettext to make PO message catalogs
|
||||||
|
echo. changes to make an overview over all changed/added/deprecated items
|
||||||
|
echo. xml to make Docutils-native XML files
|
||||||
|
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||||
|
echo. linkcheck to check all external links for integrity
|
||||||
|
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "clean" (
|
||||||
|
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||||
|
del /q /s %BUILDDIR%\*
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
%SPHINXBUILD% 2> nul
|
||||||
|
if errorlevel 9009 (
|
||||||
|
echo.
|
||||||
|
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||||
|
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||||
|
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||||
|
echo.may add the Sphinx directory to PATH.
|
||||||
|
echo.
|
||||||
|
echo.If you don't have Sphinx installed, grab it from
|
||||||
|
echo.http://sphinx-doc.org/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "html" (
|
||||||
|
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "dirhtml" (
|
||||||
|
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "singlehtml" (
|
||||||
|
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "pickle" (
|
||||||
|
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished; now you can process the pickle files.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "json" (
|
||||||
|
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished; now you can process the JSON files.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "htmlhelp" (
|
||||||
|
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||||
|
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "qthelp" (
|
||||||
|
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||||
|
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||||
|
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-escpos.qhcp
|
||||||
|
echo.To view the help file:
|
||||||
|
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-escpos.ghc
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "devhelp" (
|
||||||
|
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "epub" (
|
||||||
|
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "latex" (
|
||||||
|
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "latexpdf" (
|
||||||
|
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||||
|
cd %BUILDDIR%/latex
|
||||||
|
make all-pdf
|
||||||
|
cd %BUILDDIR%/..
|
||||||
|
echo.
|
||||||
|
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "latexpdfja" (
|
||||||
|
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||||
|
cd %BUILDDIR%/latex
|
||||||
|
make all-pdf-ja
|
||||||
|
cd %BUILDDIR%/..
|
||||||
|
echo.
|
||||||
|
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "text" (
|
||||||
|
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "man" (
|
||||||
|
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "texinfo" (
|
||||||
|
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "gettext" (
|
||||||
|
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "changes" (
|
||||||
|
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.The overview file is in %BUILDDIR%/changes.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "linkcheck" (
|
||||||
|
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Link check complete; look for any errors in the above output ^
|
||||||
|
or in %BUILDDIR%/linkcheck/output.txt.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "doctest" (
|
||||||
|
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Testing of doctests in the sources finished, look at the ^
|
||||||
|
results in %BUILDDIR%/doctest/output.txt.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "xml" (
|
||||||
|
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "pseudoxml" (
|
||||||
|
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
:end
|
BIN
doc/pyescpos.ico
Normal file
After Width: | Height: | Size: 162 KiB |
6
doc/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
pyusb
|
||||||
|
Pillow>=2.0
|
||||||
|
qrcode>=4.0
|
||||||
|
pyserial
|
||||||
|
sphinx-rtd-theme
|
||||||
|
setuptools-scm
|
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>`_.
|
48
doc/user/installation.rst
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
************
|
||||||
|
Installation
|
||||||
|
************
|
||||||
|
|
||||||
|
:Last Reviewed: 2016-07-23
|
||||||
|
|
||||||
|
Installation with PIP
|
||||||
|
---------------------
|
||||||
|
Installation should be rather straight-forward. python-escpos is on PyPi, so you can simply enter:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
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``
|
||||||
|
|
||||||
|
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"``
|
||||||
|
Replace *idVendor* and *idProduct* hex numbers with the ones that you
|
||||||
|
got from the previous step. Note that you can either, add yourself to
|
||||||
|
"dialout" group, or use another group you already belongs instead
|
||||||
|
"dialout" and set it in the ``GROUP`` parameter in the above rule.
|
||||||
|
|
||||||
|
3. Restart udev ``# sudo service udev restart`` In some new systems it
|
||||||
|
is done with ``# sudo udevadm control --reload``
|
||||||
|
|
||||||
|
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>`__.
|
||||||
|
|
||||||
|
If you only want to enable it for python-escpos, or global activation does not work, try this:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
eval "$(register-python-argcomplete python-escpos)"
|
||||||
|
|
||||||
|
|
144
doc/user/methods.rst
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
*******
|
||||||
|
Methods
|
||||||
|
*******
|
||||||
|
|
||||||
|
.. note:: **TODO** Merge this page with the API-description. (Make the API-description more pretty and then
|
||||||
|
replace this with the API-description.)
|
||||||
|
|
||||||
|
Escpos class
|
||||||
|
------------
|
||||||
|
|
||||||
|
Escpos inherits its methods to the printers. the following methods are
|
||||||
|
defined:
|
||||||
|
|
||||||
|
image("image\_name.ext")
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Prints an image. Its adjust the size in order to print it.
|
||||||
|
|
||||||
|
* ``image_name.ext`` is the complete file name and location of any image type (jpg, gif, png, bmp)
|
||||||
|
|
||||||
|
Raises ``ImageSizeError`` exception.
|
||||||
|
|
||||||
|
qr("text")
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
Prints a QR code. The size has been adjusted to Version 4, so it can be
|
||||||
|
enough small to be printed but also enough big to be read by a smart
|
||||||
|
phone.
|
||||||
|
|
||||||
|
* ``text`` Any text that needs to be QR encoded. It could be a slogan,
|
||||||
|
salutation, url, etc.
|
||||||
|
|
||||||
|
barcode("code", "barcode\_type", width, height, "position", "font")
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Prints a barcode.
|
||||||
|
|
||||||
|
* ``code`` is an alphanumeric code to be printed as bar code
|
||||||
|
* ``barcode_type`` must be one of the following type of codes for function type A:
|
||||||
|
|
||||||
|
* UPC-A
|
||||||
|
* UPC-E
|
||||||
|
* EAN13
|
||||||
|
* EAN8
|
||||||
|
* CODE39
|
||||||
|
* ITF
|
||||||
|
* NW7
|
||||||
|
|
||||||
|
And for function type B:
|
||||||
|
|
||||||
|
* Any type above
|
||||||
|
* CODE93
|
||||||
|
* CODE128
|
||||||
|
* GS1-128
|
||||||
|
* GS1 DataBar Omnidirectional
|
||||||
|
* GS1 DataBar Truncated
|
||||||
|
* GS1 DataBar Limited
|
||||||
|
* GS1 DataBar Expanded
|
||||||
|
|
||||||
|
|
||||||
|
* ``width`` is a numeric value in the range between (1,255) *Default:* 64
|
||||||
|
* ``height`` is a numeric value in the range between (2,6) *Default:* 3
|
||||||
|
* ``position`` is where to place the code around the bars, could be one of the following values:
|
||||||
|
|
||||||
|
* ABOVE
|
||||||
|
* BELOW
|
||||||
|
* BOTH
|
||||||
|
* OFF > *Default:* BELOW
|
||||||
|
|
||||||
|
* ``font`` is one of the 2 type of fonts, values could be:
|
||||||
|
|
||||||
|
* A
|
||||||
|
* B > *Default:* A
|
||||||
|
|
||||||
|
* ``fuction_type`` chooses between ESCPOS function type A or B. A is default, B has more barcode options. Choose which one based upon your printer support and require barcode.
|
||||||
|
|
||||||
|
* A
|
||||||
|
* B > *Default* A
|
||||||
|
|
||||||
|
* Raises ``BarcodeTypeError``, ``BarcodeSizeError``, ``BarcodeCodeError`` exceptions.
|
||||||
|
|
||||||
|
text("text")
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Prints raw text. Raises ``TextError`` exception.
|
||||||
|
|
||||||
|
set("align", "font", "type", width, height, invert, smooth, flip)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Set text properties.
|
||||||
|
|
||||||
|
* ``align`` set horizontal position for text, the possible values are:
|
||||||
|
|
||||||
|
* CENTER
|
||||||
|
* LEFT
|
||||||
|
* RIGHT > > *Default:* left
|
||||||
|
|
||||||
|
* ``font`` type could be ``A`` or ``B``. *Default:* A
|
||||||
|
* ``type`` type could be ``B`` (Bold), ``U`` (Underline) or ``normal``. *Default:* normal
|
||||||
|
* ``width`` is a numeric value, 1 is for regular size, and 2 is twice the standard size. *Default*: 1
|
||||||
|
* ``height`` is a numeric value, 1 is for regular size and 2 is twice the standard size. *Default*: 1
|
||||||
|
* ``invert`` is a boolean value, True enables white on black printing. *Default*: False
|
||||||
|
* ``smooth`` is a boolean value, True enables text smoothing. *Default*: False
|
||||||
|
* ``flip`` is a boolean value, True enables upside-down text. *Default*: False
|
||||||
|
|
||||||
|
cut("mode")
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
Cut paper.
|
||||||
|
|
||||||
|
* ``mode`` set a full or partial cut. *Default:* full
|
||||||
|
|
||||||
|
**Partial cut is not implemented in all printers.**
|
||||||
|
|
||||||
|
cashdraw(pin)
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Sends a pulse to the cash drawer in the specified pin.
|
||||||
|
|
||||||
|
* ``pin`` is a numeric value which defines the pin to be used to send the pulse, it could be 2 or 5. Raises ``CashDrawerError()``
|
||||||
|
|
||||||
|
hw("operation")
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Hardware operations.
|
||||||
|
|
||||||
|
* ``operation`` is any of the following options:
|
||||||
|
|
||||||
|
* INIT
|
||||||
|
* SELECT
|
||||||
|
* RESET
|
||||||
|
|
||||||
|
control("align")
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Carrier feed and tabs.
|
||||||
|
|
||||||
|
* ``align`` is a string which takes any of the following values:
|
||||||
|
|
||||||
|
* LF *for Line Feed*
|
||||||
|
* FF *for Form Feed*
|
||||||
|
* CR *for Carriage Return*
|
||||||
|
* HT *for Horizontal Tab*
|
||||||
|
* VT *for Vertical Tab*
|
56
doc/user/printers.rst
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
********
|
||||||
|
Printers
|
||||||
|
********
|
||||||
|
|
||||||
|
.. note:: **TODO** Merge this page into the API-description.
|
||||||
|
|
||||||
|
There 3 different type of printers:
|
||||||
|
|
||||||
|
USB(idVendor, idProduct, interface, in\_ep, out\_ep)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Based on pyusb and libusb-1.0
|
||||||
|
|
||||||
|
* ``idVendor`` is the Vendor ID
|
||||||
|
* ``idProduct`` is the Product ID
|
||||||
|
* ``interface`` is the USB device interface (default = 0)
|
||||||
|
* ``in_ep`` is the input end point (default = 0x82)
|
||||||
|
* ``out_ep`` is the output end point (default = 0x01)
|
||||||
|
|
||||||
|
Serial("devfile", baudrate, bytesize, timeout)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Based on pyserial, default values are based on the defaults set by
|
||||||
|
DIP\_SWITCH\_1 on the documentation(hardware side).
|
||||||
|
|
||||||
|
* ``devfile`` is an alphanumeric device file name under /dev filesystem (default = /ev/ttyS0)
|
||||||
|
* ``baudrate`` is the Baud rate for serial transmission (default = 9600)
|
||||||
|
* ``bytesize`` sets the serial buffer size (default = 8)
|
||||||
|
* ``timeout`` defines Read/Write timeout (default = 1)
|
||||||
|
|
||||||
|
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
|
73
doc/user/raspi.rst
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
************
|
||||||
|
Raspberry Pi
|
||||||
|
************
|
||||||
|
|
||||||
|
This instructions were tested on Raspbian.
|
||||||
|
|
||||||
|
Unless you have done any distro with libusb-1.0 on the Raspberry Pi, the
|
||||||
|
following instructions should works fine on your raspberry distro.
|
||||||
|
|
||||||
|
.. warning:: You should **never** directly connect an printer with RS232-interface (serial port) directly to
|
||||||
|
a Raspberry PI or similar interface (e.g. those simple USB-sticks without encasing). Those interfaces are
|
||||||
|
based on 5V- or 3,3V-logic (the latter in the case of Raspberry PI). Classical RS232 uses 12V-logic and would
|
||||||
|
**thus destroy your interface**. Connect both systems with an appropriate *level shifter*.
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
------------
|
||||||
|
|
||||||
|
First, install the packages available on Raspbian.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# apt-get install python-imaging python-serial python-setuptools
|
||||||
|
|
||||||
|
PyUSB
|
||||||
|
^^^^^
|
||||||
|
|
||||||
|
PyUSB 1.0 is not available on Ubuntu, so you have to download and
|
||||||
|
install it manually
|
||||||
|
|
||||||
|
1. Download the latest tarball from
|
||||||
|
`Sourceforge <http://sourceforge.net/projects/pyusb/files/>`__
|
||||||
|
2. Decompress the zip file
|
||||||
|
3. Install the library
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# wget ...
|
||||||
|
# unzip pyusb*.zip
|
||||||
|
# cd pyusb*
|
||||||
|
# python setup.py build
|
||||||
|
# sudo python setup.py install
|
||||||
|
|
||||||
|
python-qrcode
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
1. Checkout the code from github
|
||||||
|
2. Install the library
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# git clone https://github.com/lincolnloop/python-qrcode
|
||||||
|
# cd python-qrcode
|
||||||
|
# python setup.py build
|
||||||
|
# sudo python setup.py install
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
If you have installed pyusb for libusb-1.0 then you need to:
|
||||||
|
|
||||||
|
1. Download the latest file
|
||||||
|
2. Decompress the file
|
||||||
|
3. Install the library
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# git clone https://github.com/manpaz/python-escpos.git
|
||||||
|
# cd python-escpos
|
||||||
|
# python setup.py build
|
||||||
|
# sudo python setup.py install
|
||||||
|
|
||||||
|
Now you can attach your printer and and test it with the example code in
|
||||||
|
the project's `home <https://github.com/manpaz/python-escpos>`__
|
43
doc/user/todo.rst
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
****
|
||||||
|
TODO
|
||||||
|
****
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
|
python-escpos is the initial idea, from here we can start to build a
|
||||||
|
robust library to get most of the ESC/POS printers working with this
|
||||||
|
library.
|
||||||
|
|
||||||
|
Eventually, this library must be able to cover almost all the defined
|
||||||
|
models detailed in the ESC/POS Command Specification Manual.
|
||||||
|
|
||||||
|
Details
|
||||||
|
-------
|
||||||
|
|
||||||
|
What things are planned to work on?
|
||||||
|
|
||||||
|
Testing
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
* Test on many printers as possible (USB, Serial, Network)
|
||||||
|
* automate testing
|
||||||
|
|
||||||
|
Design
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
* Add all those sequences which are not common, but part of the ESC/POS
|
||||||
|
Command Specifications.
|
||||||
|
|
||||||
|
* Port to Python 3
|
||||||
|
* Windows compatibility (hidapi instead libusb?)
|
||||||
|
* PDF417 support
|
||||||
|
|
||||||
|
* use something similar to the `capabilities` in escpos-php
|
||||||
|
|
||||||
|
Todos in the codebase
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. todolist::
|
||||||
|
|
||||||
|
|
222
doc/user/usage.rst
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
*****
|
||||||
|
Usage
|
||||||
|
*****
|
||||||
|
|
||||||
|
Define your printer
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
USB printer
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
Before start creating your Python ESC/POS printer instance, you must see
|
||||||
|
at your system for the printer parameters. This is done with the 'lsusb'
|
||||||
|
command.
|
||||||
|
|
||||||
|
First run the command to look for the "Vendor ID" and "Product ID", then
|
||||||
|
write down the values, these values are displayed just before the name
|
||||||
|
of the device with the following format:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
xxxx:xxxx
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# lsusb
|
||||||
|
Bus 002 Device 001: ID 04b8:0202 Epson ...
|
||||||
|
|
||||||
|
Write down the the values in question, then issue the following command
|
||||||
|
so you can get the "Interface" number and "End Point"
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# lsusb -vvv -d xxxx:xxxx | grep iInterface
|
||||||
|
iInterface 0
|
||||||
|
# lsusb -vvv -d xxxx:xxxx | grep bEndpointAddress | grep OUT
|
||||||
|
bEndpointAddress 0x01 EP 1 OUT
|
||||||
|
|
||||||
|
The first command will yields the "Interface" number that must be handy
|
||||||
|
to have and the second yields the "Output Endpoint" address.
|
||||||
|
|
||||||
|
**USB Printer initialization**
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Epson = printer.Usb(0x04b8,0x0202)
|
||||||
|
|
||||||
|
By default the "Interface" number is "0" and the "Output Endpoint"
|
||||||
|
address is "0x01", if you have other values then you can define with
|
||||||
|
your instance. So, assuming that we have another printer where in\_ep is
|
||||||
|
on 0x81 and out\_ep=0x02, then the printer definition should looks like:
|
||||||
|
|
||||||
|
**Generic USB Printer initialization**
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Generic = printer.Usb(0x1a2b,0x1a2b,0,0x81,0x02)
|
||||||
|
|
||||||
|
Network printer
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
You only need the IP of your printer, either because it is getting its
|
||||||
|
IP by DHCP or you set it manually.
|
||||||
|
|
||||||
|
**Network Printer initialization**
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Epson = printer.Network("192.168.1.99")
|
||||||
|
|
||||||
|
Serial printer
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Must of the default values set by the DIP switches for the serial
|
||||||
|
printers, have been set as default on the serial printer class, so the
|
||||||
|
only thing you need to know is which serial port the printer is hooked
|
||||||
|
up.
|
||||||
|
|
||||||
|
**Serial printer initialization**
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Epson = printer.Serial("/dev/tty0")
|
||||||
|
|
||||||
|
Other printers
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Some printers under /dev can't be used or initialized with any of the
|
||||||
|
methods described above. Usually, those are printers used by printcap,
|
||||||
|
however, if you know the device name, you could try the initialize
|
||||||
|
passing the device node name.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Epson = printer.File("/dev/usb/lp1")
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Define your instance
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
The following example demonstrate how to initialize the Epson TM-TI88IV
|
||||||
|
on USB interface
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from escpos import *
|
||||||
|
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
|
||||||
|
Epson = printer.Usb(0x04b8,0x0202)
|
||||||
|
# Print text
|
||||||
|
Epson.text("Hello World\n")
|
||||||
|
# Print image
|
||||||
|
Epson.image("logo.gif")
|
||||||
|
# Print QR Code
|
||||||
|
Epson.qr("You can readme from your smartphone")
|
||||||
|
# Print barcode
|
||||||
|
Epson.barcode('1324354657687','EAN13',64,2,'','')
|
||||||
|
# Cut paper
|
||||||
|
Epson.cut()
|
||||||
|
|
||||||
|
Configuration File
|
||||||
|
------------------
|
||||||
|
|
||||||
|
You can create a configuration file for python-escpos. This will
|
||||||
|
allow you to use the CLI, and skip some setup when using the library
|
||||||
|
programically.
|
||||||
|
|
||||||
|
The default configuration file is named ``config.yaml``. It's in the YAML
|
||||||
|
format. For windows it is probably at::
|
||||||
|
|
||||||
|
%appdata%/python-escpos/config.yaml
|
||||||
|
|
||||||
|
And for linux::
|
||||||
|
|
||||||
|
$HOME/.config/python-escpos/config.yaml
|
||||||
|
|
||||||
|
If you aren't sure, run::
|
||||||
|
|
||||||
|
from escpos import config
|
||||||
|
c = config.Config()
|
||||||
|
c.load()
|
||||||
|
|
||||||
|
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
|
||||||
|
search to the ``load()`` method.
|
||||||
|
|
||||||
|
|
||||||
|
To load the configured pritner, run::
|
||||||
|
|
||||||
|
from escpos import config
|
||||||
|
c = config.Config()
|
||||||
|
printer = c.printer()
|
||||||
|
|
||||||
|
|
||||||
|
The printer section
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The ``printer`` configuration section defines a default printer to create.
|
||||||
|
|
||||||
|
The only required paramter is ``type``. The value of this should be one of the
|
||||||
|
printers defined in :doc:`/user/printers`.
|
||||||
|
|
||||||
|
The rest of the parameters are whatever you want to pass to the printer.
|
||||||
|
|
||||||
|
An example file printer::
|
||||||
|
|
||||||
|
printer:
|
||||||
|
type: File
|
||||||
|
devfile: /dev/someprinter
|
||||||
|
|
||||||
|
And for a network printer::
|
||||||
|
|
||||||
|
printer:
|
||||||
|
type: network
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 9000
|
||||||
|
|
||||||
|
Advanced Usage: Print from binary blob
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Imagine you have a file with ESC/POS-commands in binary form. This could be useful for testing capabilities of your
|
||||||
|
printer with a known working combination of commands.
|
||||||
|
You can print this data with the following code, using the standard methods of python-escpos. (This is an
|
||||||
|
advantage of the fact that `_raw()` accepts binary strings.)
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from escpos import printer
|
||||||
|
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
|
||||||
|
data = file.read()
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
p._raw(data)
|
||||||
|
|
||||||
|
That's all, the printer should then print your data. You can also use this technique to let others reproduce an issue
|
||||||
|
that you have found. (Just "print" your commands to a File-printer on your local filesystem.)
|
||||||
|
However, please keep in mind, that often it is easier and better to just supply the code that you are using.
|
||||||
|
|
||||||
|
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>`_
|
||||||
|
|
||||||
|
How to update your code for USB printers
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
Old code
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Epson = escpos.Escpos(0x04b8,0x0202,0)
|
||||||
|
|
||||||
|
New code
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Epson = printer.Usb(0x04b8,0x0202)
|
||||||
|
|
||||||
|
Nothe that "0" which is the interface number is no longer needed.
|
@@ -1 +0,0 @@
|
|||||||
__all__ = ["constants","escpos","exceptions","printer"]
|
|
@@ -1,75 +0,0 @@
|
|||||||
""" ESC/POS Commands (Constants) """
|
|
||||||
|
|
||||||
# Feed control sequences
|
|
||||||
CTL_LF = '\x0a' # Print and line feed
|
|
||||||
CTL_FF = '\x0c' # Form feed
|
|
||||||
CTL_CR = '\x0d' # Carriage return
|
|
||||||
CTL_HT = '\x09' # Horizontal tab
|
|
||||||
CTL_SET_HT = '\x1b\x44' # Set horizontal tab positions
|
|
||||||
CTL_VT = '\x1b\x64\x04' # Vertical tab
|
|
||||||
# Printer hardware
|
|
||||||
HW_INIT = '\x1b\x40' # Clear data in buffer and reset modes
|
|
||||||
HW_SELECT = '\x1b\x3d\x01' # Printer select
|
|
||||||
HW_RESET = '\x1b\x3f\x0a\x00' # Reset printer hardware
|
|
||||||
# Cash Drawer
|
|
||||||
CD_KICK_2 = '\x1b\x70\x00' # Sends a pulse to pin 2 []
|
|
||||||
CD_KICK_5 = '\x1b\x70\x01' # Sends a pulse to pin 5 []
|
|
||||||
# Paper
|
|
||||||
PAPER_FULL_CUT = '\x1d\x56\x00' # Full cut paper
|
|
||||||
PAPER_PART_CUT = '\x1d\x56\x01' # Partial cut paper
|
|
||||||
# Text format
|
|
||||||
TXT_NORMAL = '\x1b\x21\x00' # Normal text
|
|
||||||
TXT_2HEIGHT = '\x1b\x21\x10' # Double height text
|
|
||||||
TXT_2WIDTH = '\x1b\x21\x20' # Double width text
|
|
||||||
TXT_UNDERL_OFF = '\x1b\x2d\x00' # Underline font OFF
|
|
||||||
TXT_UNDERL_ON = '\x1b\x2d\x01' # Underline font 1-dot ON
|
|
||||||
TXT_UNDERL2_ON = '\x1b\x2d\x02' # Underline font 2-dot ON
|
|
||||||
TXT_BOLD_OFF = '\x1b\x45\x00' # Bold font OFF
|
|
||||||
TXT_BOLD_ON = '\x1b\x45\x01' # Bold font ON
|
|
||||||
TXT_FONT_A = '\x1b\x4d\x00' # Font type A
|
|
||||||
TXT_FONT_B = '\x1b\x4d\x01' # Font type B
|
|
||||||
TXT_ALIGN_LT = '\x1b\x61\x00' # Left justification
|
|
||||||
TXT_ALIGN_CT = '\x1b\x61\x01' # Centering
|
|
||||||
TXT_ALIGN_RT = '\x1b\x61\x02' # Right justification
|
|
||||||
# Char code table
|
|
||||||
CHARCODE_PC437 = '\x1b\x74\x00' # USA: Standard Europe
|
|
||||||
CHARCODE_JIS = '\x1b\x74\x01' # Japanese Katakana
|
|
||||||
CHARCODE_PC850 = '\x1b\x74\x02' # Multilingual
|
|
||||||
CHARCODE_PC860 = '\x1b\x74\x03' # Portuguese
|
|
||||||
CHARCODE_PC863 = '\x1b\x74\x04' # Canadian-French
|
|
||||||
CHARCODE_PC865 = '\x1b\x74\x05' # Nordic
|
|
||||||
CHARCODE_WEU = '\x1b\x74\x06' # Simplified Kanji, Hirakana
|
|
||||||
CHARCODE_GREEK = '\x1b\x74\x07' # Simplified Kanji
|
|
||||||
CHARCODE_HEBREW = '\x1b\x74\x08' # Simplified Kanji
|
|
||||||
CHARCODE_PC1252 = '\x1b\x74\x11' # Western European Windows Code Set
|
|
||||||
CHARCODE_PC866 = '\x1b\x74\x12' # Cirillic #2
|
|
||||||
CHARCODE_PC852 = '\x1b\x74\x13' # Latin 2
|
|
||||||
CHARCODE_PC858 = '\x1b\x74\x14' # Euro
|
|
||||||
CHARCODE_THAI42 = '\x1b\x74\x15' # Thai character code 42
|
|
||||||
CHARCODE_THAI11 = '\x1b\x74\x16' # Thai character code 11
|
|
||||||
CHARCODE_THAI13 = '\x1b\x74\x17' # Thai character code 13
|
|
||||||
CHARCODE_THAI14 = '\x1b\x74\x18' # Thai character code 14
|
|
||||||
CHARCODE_THAI16 = '\x1b\x74\x19' # Thai character code 16
|
|
||||||
CHARCODE_THAI17 = '\x1b\x74\x1a' # Thai character code 17
|
|
||||||
CHARCODE_THAI18 = '\x1b\x74\x1b' # Thai character code 18
|
|
||||||
# Barcode format
|
|
||||||
BARCODE_TXT_OFF = '\x1d\x48\x00' # HRI barcode chars OFF
|
|
||||||
BARCODE_TXT_ABV = '\x1d\x48\x01' # HRI barcode chars above
|
|
||||||
BARCODE_TXT_BLW = '\x1d\x48\x02' # HRI barcode chars below
|
|
||||||
BARCODE_TXT_BTH = '\x1d\x48\x03' # HRI barcode chars both above and below
|
|
||||||
BARCODE_FONT_A = '\x1d\x66\x00' # Font type A for HRI barcode chars
|
|
||||||
BARCODE_FONT_B = '\x1d\x66\x01' # Font type B for HRI barcode chars
|
|
||||||
BARCODE_HEIGHT = '\x1d\x68\x64' # Barcode Height [1-255]
|
|
||||||
BARCODE_WIDTH = '\x1d\x77\x03' # Barcode Width [2-6]
|
|
||||||
BARCODE_UPC_A = '\x1d\x6b\x00' # Barcode type UPC-A
|
|
||||||
BARCODE_UPC_E = '\x1d\x6b\x01' # Barcode type UPC-E
|
|
||||||
BARCODE_EAN13 = '\x1d\x6b\x02' # Barcode type EAN13
|
|
||||||
BARCODE_EAN8 = '\x1d\x6b\x03' # Barcode type EAN8
|
|
||||||
BARCODE_CODE39 = '\x1d\x6b\x04' # Barcode type CODE39
|
|
||||||
BARCODE_ITF = '\x1d\x6b\x05' # Barcode type ITF
|
|
||||||
BARCODE_NW7 = '\x1d\x6b\x06' # Barcode type NW7
|
|
||||||
# Image format
|
|
||||||
S_RASTER_N = '\x1d\x76\x30\x00' # Set raster image normal size
|
|
||||||
S_RASTER_2W = '\x1d\x76\x30\x01' # Set raster image double width
|
|
||||||
S_RASTER_2H = '\x1d\x76\x30\x02' # Set raster image double height
|
|
||||||
S_RASTER_Q = '\x1d\x76\x30\x03' # Set raster image quadruple
|
|
331
escpos/escpos.py
@@ -1,331 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
"""
|
|
||||||
@author: Manuel F Martinez <manpaz@bashlinux.com>
|
|
||||||
@organization: Bashlinux
|
|
||||||
@copyright: Copyright (c) 2012 Bashlinux
|
|
||||||
@license: GPL
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
import Image
|
|
||||||
except ImportError:
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
import qrcode
|
|
||||||
import time
|
|
||||||
|
|
||||||
from constants import *
|
|
||||||
from exceptions import *
|
|
||||||
|
|
||||||
class Escpos:
|
|
||||||
""" ESC/POS Printer object """
|
|
||||||
device = None
|
|
||||||
|
|
||||||
|
|
||||||
def _check_image_size(self, size):
|
|
||||||
""" Check and fix the size of the image to 32 bits """
|
|
||||||
if size % 32 == 0:
|
|
||||||
return (0, 0)
|
|
||||||
else:
|
|
||||||
image_border = 32 - (size % 32)
|
|
||||||
if (image_border % 2) == 0:
|
|
||||||
return (image_border / 2, image_border / 2)
|
|
||||||
else:
|
|
||||||
return (image_border / 2, (image_border / 2) + 1)
|
|
||||||
|
|
||||||
|
|
||||||
def _print_image(self, line, size):
|
|
||||||
""" Print formatted image """
|
|
||||||
i = 0
|
|
||||||
cont = 0
|
|
||||||
buffer = ""
|
|
||||||
|
|
||||||
self._raw(S_RASTER_N)
|
|
||||||
buffer = "%02X%02X%02X%02X" % (((size[0]/size[1])/8), 0, size[1], 0)
|
|
||||||
self._raw(buffer.decode('hex'))
|
|
||||||
buffer = ""
|
|
||||||
|
|
||||||
while i < len(line):
|
|
||||||
hex_string = int(line[i:i+8],2)
|
|
||||||
buffer += "%02X" % hex_string
|
|
||||||
i += 8
|
|
||||||
cont += 1
|
|
||||||
if cont % 4 == 0:
|
|
||||||
self._raw(buffer.decode("hex"))
|
|
||||||
buffer = ""
|
|
||||||
cont = 0
|
|
||||||
|
|
||||||
|
|
||||||
def _convert_image(self, im):
|
|
||||||
""" Parse image and prepare it to a printable format """
|
|
||||||
pixels = []
|
|
||||||
pix_line = ""
|
|
||||||
im_left = ""
|
|
||||||
im_right = ""
|
|
||||||
switch = 0
|
|
||||||
img_size = [ 0, 0 ]
|
|
||||||
|
|
||||||
|
|
||||||
if im.size[0] > 512:
|
|
||||||
print ("WARNING: Image is wider than 512 and could be truncated at print time ")
|
|
||||||
if im.size[1] > 255:
|
|
||||||
raise ImageSizeError()
|
|
||||||
|
|
||||||
im_border = self._check_image_size(im.size[0])
|
|
||||||
for i in range(im_border[0]):
|
|
||||||
im_left += "0"
|
|
||||||
for i in range(im_border[1]):
|
|
||||||
im_right += "0"
|
|
||||||
|
|
||||||
for y in range(im.size[1]):
|
|
||||||
img_size[1] += 1
|
|
||||||
pix_line += im_left
|
|
||||||
img_size[0] += im_border[0]
|
|
||||||
for x in range(im.size[0]):
|
|
||||||
img_size[0] += 1
|
|
||||||
RGB = im.getpixel((x, y))
|
|
||||||
im_color = (RGB[0] + RGB[1] + RGB[2])
|
|
||||||
im_pattern = "1X0"
|
|
||||||
pattern_len = len(im_pattern)
|
|
||||||
switch = (switch - 1 ) * (-1)
|
|
||||||
for x in range(pattern_len):
|
|
||||||
if im_color <= (255 * 3 / pattern_len * (x+1)):
|
|
||||||
if im_pattern[x] == "X":
|
|
||||||
pix_line += "%d" % switch
|
|
||||||
else:
|
|
||||||
pix_line += im_pattern[x]
|
|
||||||
break
|
|
||||||
elif im_color > (255 * 3 / pattern_len * pattern_len) and im_color <= (255 * 3):
|
|
||||||
pix_line += im_pattern[-1]
|
|
||||||
break
|
|
||||||
pix_line += im_right
|
|
||||||
img_size[0] += im_border[1]
|
|
||||||
|
|
||||||
self._print_image(pix_line, img_size)
|
|
||||||
|
|
||||||
|
|
||||||
def image(self,path_img):
|
|
||||||
""" Open image file """
|
|
||||||
im_open = Image.open(path_img)
|
|
||||||
im = im_open.convert("RGB")
|
|
||||||
# Convert the RGB image in printable image
|
|
||||||
self._convert_image(im)
|
|
||||||
|
|
||||||
|
|
||||||
def qr(self,text):
|
|
||||||
""" Print QR Code for the provided string """
|
|
||||||
qr_code = qrcode.QRCode(version=4, box_size=4, border=1)
|
|
||||||
qr_code.add_data(text)
|
|
||||||
qr_code.make(fit=True)
|
|
||||||
qr_img = qr_code.make_image()
|
|
||||||
im = qr_img._img.convert("RGB")
|
|
||||||
# Convert the RGB image in printable image
|
|
||||||
self._convert_image(im)
|
|
||||||
|
|
||||||
|
|
||||||
def charcode(self,code):
|
|
||||||
""" Set Character Code Table """
|
|
||||||
if code.upper() == "USA":
|
|
||||||
self._raw(CHARCODE_PC437)
|
|
||||||
elif code.upper() == "JIS":
|
|
||||||
self._raw(CHARCODE_JIS)
|
|
||||||
elif code.upper() == "MULTILINGUAL":
|
|
||||||
self._raw(CHARCODE_PC850)
|
|
||||||
elif code.upper() == "PORTUGUESE":
|
|
||||||
self._raw(CHARCODE_PC860)
|
|
||||||
elif code.upper() == "CA_FRENCH":
|
|
||||||
self._raw(CHARCODE_PC863)
|
|
||||||
elif code.upper() == "NORDIC":
|
|
||||||
self._raw(CHARCODE_PC865)
|
|
||||||
elif code.upper() == "WEST_EUROPE":
|
|
||||||
self._raw(CHARCODE_WEU)
|
|
||||||
elif code.upper() == "GREEK":
|
|
||||||
self._raw(CHARCODE_GREEK)
|
|
||||||
elif code.upper() == "HEBREW":
|
|
||||||
self._raw(CHARCODE_HEBREW)
|
|
||||||
elif code.upper() == "LATVIAN":
|
|
||||||
self._raw(CHARCODE_PC755)
|
|
||||||
elif code.upper() == "WPC1252":
|
|
||||||
self._raw(CHARCODE_PC1252)
|
|
||||||
elif code.upper() == "CIRILLIC2":
|
|
||||||
self._raw(CHARCODE_PC866)
|
|
||||||
elif code.upper() == "LATIN2":
|
|
||||||
self._raw(CHARCODE_PC852)
|
|
||||||
elif code.upper() == "EURO":
|
|
||||||
self._raw(CHARCODE_PC858)
|
|
||||||
elif code.upper() == "THAI42":
|
|
||||||
self._raw(CHARCODE_THAI42)
|
|
||||||
elif code.upper() == "THAI11":
|
|
||||||
self._raw(CHARCODE_THAI11)
|
|
||||||
elif code.upper() == "THAI13":
|
|
||||||
self._raw(CHARCODE_THAI13)
|
|
||||||
elif code.upper() == "THAI14":
|
|
||||||
self._raw(CHARCODE_THAI14)
|
|
||||||
elif code.upper() == "THAI16":
|
|
||||||
self._raw(CHARCODE_THAI16)
|
|
||||||
elif code.upper() == "THAI17":
|
|
||||||
self._raw(CHARCODE_THAI17)
|
|
||||||
elif code.upper() == "THAI18":
|
|
||||||
self._raw(CHARCODE_THAI18)
|
|
||||||
else:
|
|
||||||
raise CharCode_error()
|
|
||||||
|
|
||||||
def barcode(self, code, bc, width, height, pos, font):
|
|
||||||
""" Print Barcode """
|
|
||||||
# Align Bar Code()
|
|
||||||
self._raw(TXT_ALIGN_CT)
|
|
||||||
# Height
|
|
||||||
if height >=2 or height <=6:
|
|
||||||
self._raw(BARCODE_HEIGHT)
|
|
||||||
else:
|
|
||||||
raise BarcodeSizeError()
|
|
||||||
# Width
|
|
||||||
if width >= 1 or width <=255:
|
|
||||||
self._raw(BARCODE_WIDTH)
|
|
||||||
else:
|
|
||||||
raise BarcodeSizeError()
|
|
||||||
# Font
|
|
||||||
if font.upper() == "B":
|
|
||||||
self._raw(BARCODE_FONT_B)
|
|
||||||
else: # DEFAULT FONT: A
|
|
||||||
self._raw(BARCODE_FONT_A)
|
|
||||||
# Position
|
|
||||||
if pos.upper() == "OFF":
|
|
||||||
self._raw(BARCODE_TXT_OFF)
|
|
||||||
elif pos.upper() == "BOTH":
|
|
||||||
self._raw(BARCODE_TXT_BTH)
|
|
||||||
elif pos.upper() == "ABOVE":
|
|
||||||
self._raw(BARCODE_TXT_ABV)
|
|
||||||
else: # DEFAULT POSITION: BELOW
|
|
||||||
self._raw(BARCODE_TXT_BLW)
|
|
||||||
# Type
|
|
||||||
if bc.upper() == "UPC-A":
|
|
||||||
self._raw(BARCODE_UPC_A)
|
|
||||||
elif bc.upper() == "UPC-E":
|
|
||||||
self._raw(BARCODE_UPC_E)
|
|
||||||
elif bc.upper() == "EAN13":
|
|
||||||
self._raw(BARCODE_EAN13)
|
|
||||||
elif bc.upper() == "EAN8":
|
|
||||||
self._raw(BARCODE_EAN8)
|
|
||||||
elif bc.upper() == "CODE39":
|
|
||||||
self._raw(BARCODE_CODE39)
|
|
||||||
elif bc.upper() == "ITF":
|
|
||||||
self._raw(BARCODE_ITF)
|
|
||||||
elif bc.upper() == "NW7":
|
|
||||||
self._raw(BARCODE_NW7)
|
|
||||||
else:
|
|
||||||
raise BarcodeTypeError()
|
|
||||||
# Print Code
|
|
||||||
if code:
|
|
||||||
self._raw(code)
|
|
||||||
else:
|
|
||||||
raise exception.BarcodeCodeError()
|
|
||||||
|
|
||||||
|
|
||||||
def text(self, txt):
|
|
||||||
""" Print alpha-numeric text """
|
|
||||||
if txt:
|
|
||||||
self._raw(txt)
|
|
||||||
else:
|
|
||||||
raise TextError()
|
|
||||||
|
|
||||||
|
|
||||||
def set(self, align='left', font='a', type='normal', width=1, height=1):
|
|
||||||
""" Set text properties """
|
|
||||||
# Width
|
|
||||||
if height == 2 and width == 2:
|
|
||||||
self._raw(TXT_2WIDTH)
|
|
||||||
self._raw(TXT_2HEIGHT)
|
|
||||||
elif height == 2 and width != 2:
|
|
||||||
self._raw(TXT_NORMAL)
|
|
||||||
self._raw(TXT_2HEIGHT)
|
|
||||||
elif width == 2 and height != 2:
|
|
||||||
self._raw(TXT_NORMAL)
|
|
||||||
self._raw(TXT_2WIDTH)
|
|
||||||
else: # DEFAULT SIZE: NORMAL
|
|
||||||
self._raw(TXT_NORMAL)
|
|
||||||
# Type
|
|
||||||
if type.upper() == "B":
|
|
||||||
self._raw(TXT_BOLD_ON)
|
|
||||||
self._raw(TXT_UNDERL_OFF)
|
|
||||||
elif type.upper() == "U":
|
|
||||||
self._raw(TXT_BOLD_OFF)
|
|
||||||
self._raw(TXT_UNDERL_ON)
|
|
||||||
elif type.upper() == "U2":
|
|
||||||
self._raw(TXT_BOLD_OFF)
|
|
||||||
self._raw(TXT_UNDERL2_ON)
|
|
||||||
self._raw(TXT_ITALIC_OFF)
|
|
||||||
elif type.upper() == "BU":
|
|
||||||
self._raw(TXT_BOLD_ON)
|
|
||||||
self._raw(TXT_UNDERL_ON)
|
|
||||||
elif type.upper() == "BU2":
|
|
||||||
self._raw(TXT_BOLD_ON)
|
|
||||||
self._raw(TXT_UNDERL2_ON)
|
|
||||||
elif type.upper == "NORMAL":
|
|
||||||
self._raw(TXT_BOLD_OFF)
|
|
||||||
self._raw(TXT_UNDERL_OFF)
|
|
||||||
# Font
|
|
||||||
if font.upper() == "B":
|
|
||||||
self._raw(TXT_FONT_B)
|
|
||||||
else: # DEFAULT FONT: A
|
|
||||||
self._raw(TXT_FONT_A)
|
|
||||||
# Align
|
|
||||||
if align.upper() == "CENTER":
|
|
||||||
self._raw(TXT_ALIGN_CT)
|
|
||||||
elif align.upper() == "RIGHT":
|
|
||||||
self._raw(TXT_ALIGN_RT)
|
|
||||||
elif align.upper() == "LEFT":
|
|
||||||
self._raw(TXT_ALIGN_LT)
|
|
||||||
|
|
||||||
|
|
||||||
def cut(self, mode=''):
|
|
||||||
""" Cut paper """
|
|
||||||
# Fix the size between last line and cut
|
|
||||||
# TODO: handle this with a line feed
|
|
||||||
self._raw("\n\n\n\n\n\n")
|
|
||||||
if mode.upper() == "PART":
|
|
||||||
self._raw(PAPER_PART_CUT)
|
|
||||||
else: # DEFAULT MODE: FULL CUT
|
|
||||||
self._raw(PAPER_FULL_CUT)
|
|
||||||
|
|
||||||
|
|
||||||
def cashdraw(self, pin):
|
|
||||||
""" Send pulse to kick the cash drawer """
|
|
||||||
if pin == 2:
|
|
||||||
self._raw(CD_KICK_2)
|
|
||||||
elif pin == 5:
|
|
||||||
self._raw(CD_KICK_5)
|
|
||||||
else:
|
|
||||||
raise CashDrawerError()
|
|
||||||
|
|
||||||
|
|
||||||
def hw(self, hw):
|
|
||||||
""" Hardware operations """
|
|
||||||
if hw.upper() == "INIT":
|
|
||||||
self._raw(HW_INIT)
|
|
||||||
elif hw.upper() == "SELECT":
|
|
||||||
self._raw(HW_SELECT)
|
|
||||||
elif hw.upper() == "RESET":
|
|
||||||
self._raw(HW_RESET)
|
|
||||||
else: # DEFAULT: DOES NOTHING
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def control(self, ctl, pos=4):
|
|
||||||
""" Feed control sequences """
|
|
||||||
# Set tab positions
|
|
||||||
if pos < 1 or pos > 16:
|
|
||||||
raise TabError()
|
|
||||||
else:
|
|
||||||
self._raw("".join([CTL_SET_HT,hex(pos)]))
|
|
||||||
# Set position
|
|
||||||
if ctl.upper() == "LF":
|
|
||||||
self._raw(CTL_LF)
|
|
||||||
elif ctl.upper() == "FF":
|
|
||||||
self._raw(CTL_FF)
|
|
||||||
elif ctl.upper() == "CR":
|
|
||||||
self._raw(CTL_CR)
|
|
||||||
elif ctl.upper() == "HT":
|
|
||||||
self._raw(CTL_HT)
|
|
||||||
elif ctl.upper() == "VT":
|
|
||||||
self._raw(CTL_VT)
|
|
@@ -1,102 +0,0 @@
|
|||||||
""" ESC/POS Exceptions classes """
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
""" Base class for ESC/POS errors """
|
|
||||||
def __init__(self, msg, status=None):
|
|
||||||
Exception.__init__(self)
|
|
||||||
self.msg = msg
|
|
||||||
self.resultcode = 1
|
|
||||||
if status is not None:
|
|
||||||
self.resultcode = status
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.msg
|
|
||||||
|
|
||||||
# Result/Exit codes
|
|
||||||
# 0 = success
|
|
||||||
# 10 = No Barcode type defined
|
|
||||||
# 20 = Barcode size values are out of range
|
|
||||||
# 30 = Barcode text not supplied
|
|
||||||
# 40 = Image height is too large
|
|
||||||
# 50 = No string supplied to be printed
|
|
||||||
# 60 = Invalid pin to send Cash Drawer pulse
|
|
||||||
# 70 = Invalid number of tab positions
|
|
||||||
# 80 = Invalid char code
|
|
||||||
|
|
||||||
|
|
||||||
class BarcodeTypeError(Error):
|
|
||||||
def __init__(self, msg=""):
|
|
||||||
Error.__init__(self, msg)
|
|
||||||
self.msg = msg
|
|
||||||
self.resultcode = 10
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "No Barcode type is defined"
|
|
||||||
|
|
||||||
class BarcodeSizeError(Error):
|
|
||||||
def __init__(self, msg=""):
|
|
||||||
Error.__init__(self, msg)
|
|
||||||
self.msg = msg
|
|
||||||
self.resultcode = 20
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Barcode size is out of range"
|
|
||||||
|
|
||||||
class BarcodeCodeError(Error):
|
|
||||||
def __init__(self, msg=""):
|
|
||||||
Error.__init__(self, msg)
|
|
||||||
self.msg = msg
|
|
||||||
self.resultcode = 30
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Code was not supplied"
|
|
||||||
|
|
||||||
class ImageSizeError(Error):
|
|
||||||
def __init__(self, msg=""):
|
|
||||||
Error.__init__(self, msg)
|
|
||||||
self.msg = msg
|
|
||||||
self.resultcode = 40
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Image height is longer than 255px and can't be printed"
|
|
||||||
|
|
||||||
class TextError(Error):
|
|
||||||
def __init__(self, msg=""):
|
|
||||||
Error.__init__(self, msg)
|
|
||||||
self.msg = msg
|
|
||||||
self.resultcode = 50
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Text string must be supplied to the text() method"
|
|
||||||
|
|
||||||
|
|
||||||
class CashDrawerError(Error):
|
|
||||||
def __init__(self, msg=""):
|
|
||||||
Error.__init__(self, msg)
|
|
||||||
self.msg = msg
|
|
||||||
self.resultcode = 60
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Valid pin must be set to send pulse"
|
|
||||||
|
|
||||||
|
|
||||||
class TabError(Error):
|
|
||||||
def __init__(self, msg=""):
|
|
||||||
Error.__init__(self, msg)
|
|
||||||
self.msg = msg
|
|
||||||
self.resultcode = 70
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Valid tab positions must be in the range 0 to 16"
|
|
||||||
|
|
||||||
|
|
||||||
class CharCodeError(Error):
|
|
||||||
def __init__(self, msg=""):
|
|
||||||
Error.__init__(self, msg)
|
|
||||||
self.msg = msg
|
|
||||||
self.resultcode = 70
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Valid char code must be set"
|
|
@@ -1,167 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
"""
|
|
||||||
@author: Manuel F Martinez <manpaz@bashlinux.com>
|
|
||||||
@organization: Bashlinux
|
|
||||||
@copyright: Copyright (c) 2012 Bashlinux
|
|
||||||
@license: GPL
|
|
||||||
"""
|
|
||||||
|
|
||||||
import usb.core
|
|
||||||
import usb.util
|
|
||||||
import serial
|
|
||||||
import socket
|
|
||||||
|
|
||||||
from escpos import *
|
|
||||||
from constants import *
|
|
||||||
from exceptions import *
|
|
||||||
|
|
||||||
class Usb(Escpos):
|
|
||||||
""" Define USB printer """
|
|
||||||
|
|
||||||
def __init__(self, idVendor, idProduct, interface=0, in_ep=0x82, out_ep=0x01):
|
|
||||||
"""
|
|
||||||
@param idVendor : Vendor ID
|
|
||||||
@param idProduct : Product ID
|
|
||||||
@param interface : USB device interface
|
|
||||||
@param in_ep : Input end point
|
|
||||||
@param out_ep : Output end point
|
|
||||||
"""
|
|
||||||
self.idVendor = idVendor
|
|
||||||
self.idProduct = idProduct
|
|
||||||
self.interface = interface
|
|
||||||
self.in_ep = in_ep
|
|
||||||
self.out_ep = out_ep
|
|
||||||
self.open()
|
|
||||||
|
|
||||||
|
|
||||||
def open(self):
|
|
||||||
""" Search device on USB tree and set is as escpos device """
|
|
||||||
self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct)
|
|
||||||
if self.device is None:
|
|
||||||
print "Cable isn't plugged in"
|
|
||||||
|
|
||||||
if self.device.is_kernel_driver_active(0):
|
|
||||||
try:
|
|
||||||
self.device.detach_kernel_driver(0)
|
|
||||||
except usb.core.USBError as e:
|
|
||||||
print "Could not detatch kernel driver: %s" % str(e)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.device.set_configuration()
|
|
||||||
self.device.reset()
|
|
||||||
except usb.core.USBError as e:
|
|
||||||
print "Could not set configuration: %s" % str(e)
|
|
||||||
|
|
||||||
|
|
||||||
def _raw(self, msg):
|
|
||||||
""" Print any command sent in raw format """
|
|
||||||
self.device.write(self.out_ep, msg, self.interface)
|
|
||||||
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
""" Release USB interface """
|
|
||||||
if self.device:
|
|
||||||
usb.util.dispose_resources(self.device)
|
|
||||||
self.device = None
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Serial(Escpos):
|
|
||||||
""" Define Serial printer """
|
|
||||||
|
|
||||||
def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1):
|
|
||||||
"""
|
|
||||||
@param devfile : Device file under dev filesystem
|
|
||||||
@param baudrate : Baud rate for serial transmission
|
|
||||||
@param bytesize : Serial buffer size
|
|
||||||
@param timeout : Read/Write timeout
|
|
||||||
"""
|
|
||||||
self.devfile = devfile
|
|
||||||
self.baudrate = baudrate
|
|
||||||
self.bytesize = bytesize
|
|
||||||
self.timeout = timeout
|
|
||||||
self.open()
|
|
||||||
|
|
||||||
|
|
||||||
def open(self):
|
|
||||||
""" Setup serial port and set is as escpos device """
|
|
||||||
self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate, bytesize=self.bytesize, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=self.timeout, dsrdtr=True)
|
|
||||||
|
|
||||||
if self.device is not None:
|
|
||||||
print "Serial printer enabled"
|
|
||||||
else:
|
|
||||||
print "Unable to open serial printer on: %s" % self.devfile
|
|
||||||
|
|
||||||
|
|
||||||
def _raw(self, msg):
|
|
||||||
""" Print any command sent in raw format """
|
|
||||||
self.device.write(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
""" Close Serial interface """
|
|
||||||
if self.device is not None:
|
|
||||||
self.device.close()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Network(Escpos):
|
|
||||||
""" Define Network printer """
|
|
||||||
|
|
||||||
def __init__(self,host,port=9100):
|
|
||||||
"""
|
|
||||||
@param host : Printer's hostname or IP address
|
|
||||||
@param port : Port to write to
|
|
||||||
"""
|
|
||||||
self.host = host
|
|
||||||
self.port = port
|
|
||||||
self.open()
|
|
||||||
|
|
||||||
|
|
||||||
def open(self):
|
|
||||||
""" Open TCP socket and set it as escpos device """
|
|
||||||
self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
self.device.connect((self.host, self.port))
|
|
||||||
|
|
||||||
if self.device is None:
|
|
||||||
print "Could not open socket for %s" % self.host
|
|
||||||
|
|
||||||
|
|
||||||
def _raw(self, msg):
|
|
||||||
""" Print any command sent in raw format """
|
|
||||||
self.device.send(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
""" Close TCP connection """
|
|
||||||
self.device.close()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class File(Escpos):
|
|
||||||
""" Define Generic file printer """
|
|
||||||
|
|
||||||
def __init__(self, devfile="/dev/usb/lp0"):
|
|
||||||
"""
|
|
||||||
@param devfile : Device file under dev filesystem
|
|
||||||
"""
|
|
||||||
self.devfile = devfile
|
|
||||||
self.open()
|
|
||||||
|
|
||||||
|
|
||||||
def open(self):
|
|
||||||
""" Open system file """
|
|
||||||
self.device = open(self.devfile, "wb")
|
|
||||||
|
|
||||||
if self.device is None:
|
|
||||||
print "Could not open the specified file %s" % self.devfile
|
|
||||||
|
|
||||||
|
|
||||||
def _raw(self, msg):
|
|
||||||
""" Print any command sent in raw format """
|
|
||||||
self.device.write(msg);
|
|
||||||
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
""" Close system file """
|
|
||||||
self.device.close()
|
|
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-e .
|
7
setup.cfg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[nosetests]
|
||||||
|
verbosity=3
|
||||||
|
with-doctest=1
|
||||||
|
|
||||||
|
[bdist_wheel]
|
||||||
|
# This flag says that the code is written to work on both Python 2 and Python 3.
|
||||||
|
universal=1
|
125
setup.py
@@ -1,29 +1,126 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
from distutils.core import setup
|
import os
|
||||||
|
import sys
|
||||||
|
from setuptools import find_packages, setup
|
||||||
|
from setuptools.command.test import test as test_command
|
||||||
|
|
||||||
|
|
||||||
|
base_dir = os.path.dirname(__file__)
|
||||||
|
src_dir = os.path.join(base_dir, "src")
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
sys.path.insert(0, src_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def read(fname):
|
||||||
|
"""read file from same path as setup.py"""
|
||||||
|
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 = """\
|
||||||
|
# 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='escpos',
|
name='python-escpos',
|
||||||
version='1.0.4',
|
use_scm_version={
|
||||||
url='http://code.google.com/p/python-escpos',
|
'write_to': 'src/escpos/version.py',
|
||||||
download_url='http://python-escpos.googlecode.com/files/python-escpos-1.0.zip',
|
'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',
|
description='Python library to manipulate ESC/POS Printers',
|
||||||
|
bugtrack_url='https://github.com/python-escpos/python-escpos/issues',
|
||||||
license='GNU GPL v3',
|
license='GNU GPL v3',
|
||||||
long_description=open('README').read(),
|
long_description=read('README.rst'),
|
||||||
author='Manuel F Martinez',
|
author='Manuel F Martinez and others',
|
||||||
author_email='manpaz@bashlinux.com',
|
author_email='manpaz@bashlinux.com',
|
||||||
platforms=['linux'],
|
maintainer='Patrick Kanzler',
|
||||||
packages=[
|
maintainer_email='patrick.kanzler@fablab.fau.de',
|
||||||
'escpos',
|
keywords=[
|
||||||
|
'ESC/POS',
|
||||||
|
'thermoprinter',
|
||||||
|
'voucher printer',
|
||||||
|
'printing',
|
||||||
|
'receipt,',
|
||||||
],
|
],
|
||||||
|
platforms='any',
|
||||||
|
package_dir={"": "src"},
|
||||||
|
packages=find_packages(where="src", exclude=["tests", "tests.*"]),
|
||||||
package_data={'': ['COPYING']},
|
package_data={'': ['COPYING']},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 1 - Alpha',
|
'Development Status :: 4 - Beta',
|
||||||
'License :: OSI Approved :: GNU GPL v3',
|
'Environment :: Console',
|
||||||
'Operating System :: GNU/Linux',
|
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Topic :: System :: Pheripherals',
|
'Programming Language :: Python :: 2',
|
||||||
|
'Programming Language :: Python :: 2.7',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.3',
|
||||||
|
'Programming Language :: Python :: 3.4',
|
||||||
|
'Programming Language :: Python :: 3.5',
|
||||||
|
'Programming Language :: Python :: Implementation :: CPython',
|
||||||
|
'Programming Language :: Python :: Implementation :: PyPy',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
|
'Topic :: Office/Business :: Financial :: Point-Of-Sale',
|
||||||
],
|
],
|
||||||
|
install_requires=[
|
||||||
|
'pyusb>=1.0.0',
|
||||||
|
'Pillow>=2.0',
|
||||||
|
'qrcode>=4.0',
|
||||||
|
'pyserial',
|
||||||
|
'six',
|
||||||
|
'appdirs',
|
||||||
|
'pyyaml',
|
||||||
|
'argparse',
|
||||||
|
'argcomplete',
|
||||||
|
],
|
||||||
|
setup_requires=[
|
||||||
|
'setuptools_scm',
|
||||||
|
],
|
||||||
|
tests_require=['tox', 'nose', 'scripttest', 'mock', 'hypothesis'],
|
||||||
|
cmdclass={'test': Tox},
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'python-escpos = escpos.cli:main'
|
||||||
|
]
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
19
src/escpos/__init__.py
Normal file
@@ -0,0 +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.'
|
||||||
|
)
|
572
src/escpos/cli.py
Normal file
@@ -0,0 +1,572 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# PYTHON_ARGCOMPLETE_OK
|
||||||
|
""" CLI
|
||||||
|
|
||||||
|
This module acts as a command line interface for python-escpos. It mirrors
|
||||||
|
closely the available ESCPOS commands while adding a couple extra ones for convience.
|
||||||
|
|
||||||
|
It requires you to have a configuration file. See documentation for details.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
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
|
||||||
|
def str_to_bool(string):
|
||||||
|
""" Used as a type in argparse so that we get back a proper
|
||||||
|
bool instead of always True
|
||||||
|
"""
|
||||||
|
return string.lower() in ('y', 'yes', '1', 'true')
|
||||||
|
|
||||||
|
# A list of functions that work better with a newline to be sent after them.
|
||||||
|
REQUIRES_NEWLINE = ('qr', 'barcode', 'text', 'block_text')
|
||||||
|
|
||||||
|
# Used in demo method
|
||||||
|
# Key: The name of escpos function and the argument passed on the CLI. Some
|
||||||
|
# manual translation is done in the case of barcodes_a -> barcode.
|
||||||
|
# Value: A list of dictionaries to pass to the escpos function as arguments.
|
||||||
|
DEMO_FUNCTIONS = {
|
||||||
|
'text': [
|
||||||
|
{'txt': 'Hello, World!\n', }
|
||||||
|
],
|
||||||
|
'qr': [
|
||||||
|
{'content': 'This tests a QR code'},
|
||||||
|
{'content': 'https://en.wikipedia.org/'}
|
||||||
|
],
|
||||||
|
'barcodes_a': [
|
||||||
|
{'bc': 'UPC-A', 'code': '13243546576'},
|
||||||
|
{'bc': 'UPC-E', 'code': '132435'},
|
||||||
|
{'bc': 'EAN13', 'code': '1324354657687'},
|
||||||
|
{'bc': 'EAN8', 'code': '1324354'},
|
||||||
|
{'bc': 'CODE39', 'code': 'TEST'},
|
||||||
|
{'bc': 'ITF', 'code': '55867492279103'},
|
||||||
|
{'bc': 'NW7', 'code': 'A00000000A'},
|
||||||
|
],
|
||||||
|
'barcodes_b': [
|
||||||
|
{'bc': 'UPC-A', 'code': '13243546576', 'function_type': 'B'},
|
||||||
|
{'bc': 'UPC-E', 'code': '132435', 'function_type': 'B'},
|
||||||
|
{'bc': 'EAN13', 'code': '1324354657687', 'function_type': 'B'},
|
||||||
|
{'bc': 'EAN8', 'code': '1324354', 'function_type': 'B'},
|
||||||
|
{'bc': 'CODE39', 'code': 'TEST', 'function_type': 'B'},
|
||||||
|
{'bc': 'ITF', 'code': '55867492279103', 'function_type': 'B'},
|
||||||
|
{'bc': 'NW7', 'code': 'A00000000A', 'function_type': 'B'},
|
||||||
|
{'bc': 'CODE93', 'code': 'A00000000A', 'function_type': 'B'},
|
||||||
|
{'bc': 'CODE93', 'code': '1324354657687', 'function_type': 'B'},
|
||||||
|
{'bc': 'CODE128A', 'code': 'TEST', 'function_type': 'B'},
|
||||||
|
{'bc': 'CODE128B', 'code': 'TEST', 'function_type': 'B'},
|
||||||
|
{'bc': 'CODE128C', 'code': 'TEST', 'function_type': 'B'},
|
||||||
|
{'bc': 'GS1-128', 'code': '00123456780000000001', 'function_type': 'B'},
|
||||||
|
{'bc': 'GS1 DataBar Omnidirectional', 'code': '0000000000000', 'function_type': 'B'},
|
||||||
|
{'bc': 'GS1 DataBar Truncated', 'code': '0000000000000', 'function_type': 'B'},
|
||||||
|
{'bc': 'GS1 DataBar Limited', 'code': '0000000000000', 'function_type': 'B'},
|
||||||
|
{'bc': 'GS1 DataBar Expanded', 'code': '00AAAAAAA', 'function_type': 'B'},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Used to build the CLI
|
||||||
|
# A list of dictionaries. Each dict is a CLI argument.
|
||||||
|
# Keys:
|
||||||
|
# parser: A dict of args for command_parsers.add_parser
|
||||||
|
# defaults: A dict of args for subparser.set_defaults
|
||||||
|
# arguments: A list of dicts of args for subparser.add_argument
|
||||||
|
ESCPOS_COMMANDS = [
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'qr',
|
||||||
|
'help': 'Print a QR code',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'qr',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--content',),
|
||||||
|
'help': 'Text to print as a qr code',
|
||||||
|
'required': True,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'barcode',
|
||||||
|
'help': 'Print a barcode',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'barcode',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--code',),
|
||||||
|
'help': 'Barcode data to print',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--bc',),
|
||||||
|
'help': 'Barcode format',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--height',),
|
||||||
|
'help': 'Barcode height in px',
|
||||||
|
'type': int,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--width',),
|
||||||
|
'help': 'Barcode width',
|
||||||
|
'type': int,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--pos',),
|
||||||
|
'help': 'Label position',
|
||||||
|
'choices': ['BELOW', 'ABOVE', 'BOTH', 'OFF'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--font',),
|
||||||
|
'help': 'Label font',
|
||||||
|
'choices': ['A', 'B'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--align_ct',),
|
||||||
|
'help': 'Align barcode center',
|
||||||
|
'type': str_to_bool,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--function_type',),
|
||||||
|
'help': 'ESCPOS function type',
|
||||||
|
'choices': ['A', 'B'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'text',
|
||||||
|
'help': 'Print plain text',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'text',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--txt',),
|
||||||
|
'help': 'Plain text to print',
|
||||||
|
'required': True,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'block_text',
|
||||||
|
'help': 'Print wrapped text',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'block_text',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--txt',),
|
||||||
|
'help': 'block_text to print',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--columns',),
|
||||||
|
'help': 'Number of columns',
|
||||||
|
'type': int,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'cut',
|
||||||
|
'help': 'Cut the paper',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'cut',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--mode',),
|
||||||
|
'help': 'Type of cut',
|
||||||
|
'choices': ['FULL', 'PART'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'cashdraw',
|
||||||
|
'help': 'Kick the cash drawer',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'cashdraw',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--pin',),
|
||||||
|
'help': 'Which PIN to kick',
|
||||||
|
'choices': [2, 5],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'image',
|
||||||
|
'help': 'Print an image',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'image',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--img_source',),
|
||||||
|
'help': 'Path to image',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--impl',),
|
||||||
|
'help': 'Implementation to use',
|
||||||
|
'choices': ['bitImageRaster', 'bitImageColumn', 'graphics'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--high_density_horizontal',),
|
||||||
|
'help': 'Image density (horizontal)',
|
||||||
|
'type': str_to_bool,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--high_density_vertical',),
|
||||||
|
'help': 'Image density (vertical)',
|
||||||
|
'type': str_to_bool,
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'fullimage',
|
||||||
|
'help': 'Print a fullimage',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'fullimage',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--img',),
|
||||||
|
'help': 'Path to img',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--max_height',),
|
||||||
|
'help': 'Max height of image in px',
|
||||||
|
'type': int,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--width',),
|
||||||
|
'help': 'Max width of image in px',
|
||||||
|
'type': int,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--histeq',),
|
||||||
|
'help': 'Equalize the histrogram',
|
||||||
|
'type': str_to_bool,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--bandsize',),
|
||||||
|
'help': 'Size of bands to divide into when printing',
|
||||||
|
'type': int,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'charcode',
|
||||||
|
'help': 'Set character code table',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'charcode',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--code',),
|
||||||
|
'help': 'Character code',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'set',
|
||||||
|
'help': 'Set text properties',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'set',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--align',),
|
||||||
|
'help': 'Horizontal alignment',
|
||||||
|
'choices': ['left', 'center', 'right'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--font',),
|
||||||
|
'help': 'Font choice',
|
||||||
|
'choices': ['left', 'center', 'right'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--text_type',),
|
||||||
|
'help': 'Text properties',
|
||||||
|
'choices': ['B', 'U', 'U2', 'BU', 'BU2', 'NORMAL'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--width',),
|
||||||
|
'help': 'Width multiplier',
|
||||||
|
'type': int,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--height',),
|
||||||
|
'help': 'Height multiplier',
|
||||||
|
'type': int,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--density',),
|
||||||
|
'help': 'Print density',
|
||||||
|
'type': int,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--invert',),
|
||||||
|
'help': 'White on black printing',
|
||||||
|
'type': str_to_bool,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--smooth',),
|
||||||
|
'help': 'Text smoothing. Effective on >: 4x4 text',
|
||||||
|
'type': str_to_bool,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--flip',),
|
||||||
|
'help': 'Text smoothing. Effective on >: 4x4 text',
|
||||||
|
'type': str_to_bool,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'hw',
|
||||||
|
'help': 'Hardware operations',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'hw',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--hw',),
|
||||||
|
'help': 'Operation',
|
||||||
|
'choices': ['INIT', 'SELECT', 'RESET'],
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'control',
|
||||||
|
'help': 'Control sequences',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'control',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--ctl',),
|
||||||
|
'help': 'Control sequence',
|
||||||
|
'choices': ['LF', 'FF', 'CR', 'HT', 'VT'],
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'option_strings': ('--pos',),
|
||||||
|
'help': 'Horizontal tab position (1-4)',
|
||||||
|
'type': int,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'panel_buttons',
|
||||||
|
'help': 'Controls panel buttons',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': 'panel_buttons',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--enable',),
|
||||||
|
'help': 'Feed button enabled',
|
||||||
|
'type': str_to_bool,
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'parser': {
|
||||||
|
'name': 'raw',
|
||||||
|
'help': 'Raw data',
|
||||||
|
},
|
||||||
|
'defaults': {
|
||||||
|
'func': '_raw',
|
||||||
|
},
|
||||||
|
'arguments': [
|
||||||
|
{
|
||||||
|
'option_strings': ('--msg',),
|
||||||
|
'help': 'Raw data to send',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
|
||||||
|
Handles loading of configuration and creating and processing of command
|
||||||
|
line arguments. Called when run from a CLI.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='CLI for python-escpos',
|
||||||
|
epilog='Printer configuration is defined in the python-escpos config'
|
||||||
|
'file. See documentation for details.',
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.register('type', 'bool', str_to_bool)
|
||||||
|
|
||||||
|
# Allow config file location to be passed
|
||||||
|
parser.add_argument(
|
||||||
|
'-c', '--config',
|
||||||
|
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:
|
||||||
|
parser_command = command_subparsers.add_parser(**command['parser'])
|
||||||
|
parser_command.set_defaults(**command['defaults'])
|
||||||
|
for argument in command['arguments']:
|
||||||
|
option_strings = argument.pop('option_strings')
|
||||||
|
parser_command.add_argument(*option_strings, **argument)
|
||||||
|
|
||||||
|
# Build any custom arguments
|
||||||
|
parser_command_demo = command_subparsers.add_parser('demo',
|
||||||
|
help='Demonstrates various functions')
|
||||||
|
parser_command_demo.set_defaults(func='demo')
|
||||||
|
demo_group = parser_command_demo.add_mutually_exclusive_group()
|
||||||
|
demo_group.add_argument(
|
||||||
|
'--barcodes-a',
|
||||||
|
help='Print demo barcodes for function type A',
|
||||||
|
action='store_true',
|
||||||
|
)
|
||||||
|
demo_group.add_argument(
|
||||||
|
'--barcodes-b',
|
||||||
|
help='Print demo barcodes for function type B',
|
||||||
|
action='store_true',
|
||||||
|
)
|
||||||
|
demo_group.add_argument(
|
||||||
|
'--qr',
|
||||||
|
help='Print some demo QR codes',
|
||||||
|
action='store_true',
|
||||||
|
)
|
||||||
|
demo_group.add_argument(
|
||||||
|
'--text',
|
||||||
|
help='Print some demo text',
|
||||||
|
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:
|
||||||
|
parser.print_help()
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Load the configuration and defined printer
|
||||||
|
saved_config = config.Config()
|
||||||
|
saved_config.load(config_path)
|
||||||
|
printer = saved_config.printer()
|
||||||
|
|
||||||
|
if not printer:
|
||||||
|
raise Exception('No printers loaded from config')
|
||||||
|
|
||||||
|
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)
|
||||||
|
if target_command in REQUIRES_NEWLINE:
|
||||||
|
printer.text("\n")
|
||||||
|
else:
|
||||||
|
command_arguments['printer'] = printer
|
||||||
|
globals()[target_command](**command_arguments)
|
||||||
|
|
||||||
|
|
||||||
|
def demo(printer, **kwargs):
|
||||||
|
"""
|
||||||
|
Prints specificed demos. Called when CLI is passed `demo`. This function
|
||||||
|
uses the DEMO_FUNCTIONS dictionary.
|
||||||
|
|
||||||
|
:param printer: A printer from escpos.printer
|
||||||
|
:param kwargs: A dict with a key for each function you want to test. It's
|
||||||
|
in this format since it usually comes from argparse.
|
||||||
|
"""
|
||||||
|
for demo_choice in kwargs.keys():
|
||||||
|
command = getattr(
|
||||||
|
printer,
|
||||||
|
demo_choice
|
||||||
|
.replace('barcodes_a', 'barcode')
|
||||||
|
.replace('barcodes_b', 'barcode')
|
||||||
|
)
|
||||||
|
for params in DEMO_FUNCTIONS[demo_choice]:
|
||||||
|
command(**params)
|
||||||
|
printer.cut()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
118
src/escpos/config.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
""" ESC/POS configuration manager.
|
||||||
|
|
||||||
|
This module contains the implentations of abstract base class :py:class:`Config`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import appdirs
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from . import printer
|
||||||
|
from . import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
class Config(object):
|
||||||
|
""" Configuration handler class.
|
||||||
|
|
||||||
|
This class loads configuration from a default or specificed directory. It
|
||||||
|
can create your defined printer and return it to you.
|
||||||
|
"""
|
||||||
|
_app_name = 'python-escpos'
|
||||||
|
_config_file = 'config.yaml'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
""" Initialize configuration.
|
||||||
|
|
||||||
|
Remember to add anything that needs to be reset between configurations
|
||||||
|
to self._reset_config
|
||||||
|
"""
|
||||||
|
self._has_loaded = False
|
||||||
|
self._printer = None
|
||||||
|
|
||||||
|
self._printer_name = None
|
||||||
|
self._printer_config = None
|
||||||
|
|
||||||
|
def _reset_config(self):
|
||||||
|
""" Clear the loaded configuration.
|
||||||
|
|
||||||
|
If we are loading a changed config, we don't want to have leftover
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
self._has_loaded = False
|
||||||
|
self._printer = None
|
||||||
|
|
||||||
|
self._printer_name = None
|
||||||
|
self._printer_config = None
|
||||||
|
|
||||||
|
def load(self, config_path=None):
|
||||||
|
""" Load and parse the configuration file using pyyaml
|
||||||
|
|
||||||
|
:param config_path: An optional file path, file handle, or byte string
|
||||||
|
for the configuration file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._reset_config()
|
||||||
|
|
||||||
|
if not config_path:
|
||||||
|
config_path = os.path.join(
|
||||||
|
appdirs.user_config_dir(self._app_name),
|
||||||
|
self._config_file
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# First check if it's file like. If it is, pyyaml can load it.
|
||||||
|
# I'm checking type instead of catching exceptions to keep the
|
||||||
|
# exception handling simple
|
||||||
|
if hasattr(config_path, 'read'):
|
||||||
|
config = yaml.safe_load(config_path)
|
||||||
|
else:
|
||||||
|
# If it isn't, it's a path. We have to open it first, otherwise
|
||||||
|
# pyyaml will try to read it as yaml
|
||||||
|
with open(config_path, 'rb') as config_file:
|
||||||
|
config = yaml.safe_load(config_file)
|
||||||
|
except EnvironmentError:
|
||||||
|
raise exceptions.ConfigNotFoundError('Couldn\'t read config at {config_path}'.format(
|
||||||
|
config_path=str(config_path),
|
||||||
|
))
|
||||||
|
except yaml.YAMLError:
|
||||||
|
raise exceptions.ConfigSyntaxError('Error parsing YAML')
|
||||||
|
|
||||||
|
if 'printer' in config:
|
||||||
|
self._printer_config = config['printer']
|
||||||
|
self._printer_name = self._printer_config.pop('type').title()
|
||||||
|
|
||||||
|
if not self._printer_name or not hasattr(printer, self._printer_name):
|
||||||
|
raise exceptions.ConfigSyntaxError(
|
||||||
|
'Printer type "{printer_name}" is invalid'.format(
|
||||||
|
printer_name=self._printer_name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self._has_loaded = True
|
||||||
|
|
||||||
|
def printer(self):
|
||||||
|
""" Returns a printer that was defined in the config, or throws an
|
||||||
|
exception.
|
||||||
|
|
||||||
|
This method loads the default config if one hasn't beeen already loaded.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not self._has_loaded:
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
if not self._printer_name:
|
||||||
|
raise exceptions.ConfigSectionMissingError('printer')
|
||||||
|
|
||||||
|
if not self._printer:
|
||||||
|
# We could catch init errors and make them a ConfigSyntaxError,
|
||||||
|
# but I'll just let them pass
|
||||||
|
self._printer = getattr(printer, self._printer_name)(**self._printer_config)
|
||||||
|
|
||||||
|
return self._printer
|
213
src/escpos/constants.py
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
""" Set of ESC/POS Commands (Constants)
|
||||||
|
|
||||||
|
This module contains constants that are described in the esc/pos-documentation.
|
||||||
|
Since there is no definitive and unified specification for all esc/pos-like printers the constants could later be
|
||||||
|
moved to `capabilities` as in `escpos-php by @mike42 <https://github.com/mike42/escpos-php>`_.
|
||||||
|
|
||||||
|
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
|
||||||
|
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2012 Bashlinux
|
||||||
|
:license: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
# Control characters
|
||||||
|
# as labelled in http://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf
|
||||||
|
NUL = b'\x00'
|
||||||
|
EOT = b'\x04'
|
||||||
|
ENQ = b'\x05'
|
||||||
|
DLE = b'\x10'
|
||||||
|
DC4 = b'\x14'
|
||||||
|
CAN = b'\x18'
|
||||||
|
ESC = b'\x1b'
|
||||||
|
FS = b'\x1c'
|
||||||
|
GS = b'\x1d'
|
||||||
|
|
||||||
|
# Feed control sequences
|
||||||
|
CTL_LF = b'\n' # Print and line feed
|
||||||
|
CTL_FF = b'\f' # Form feed
|
||||||
|
CTL_CR = b'\r' # Carriage return
|
||||||
|
CTL_HT = b'\t' # Horizontal tab
|
||||||
|
CTL_SET_HT = ESC + b'\x44' # Set horizontal tab positions
|
||||||
|
CTL_VT = b'\v' # Vertical tab
|
||||||
|
|
||||||
|
# Printer hardware
|
||||||
|
HW_INIT = ESC + b'@' # Clear data in buffer and reset modes
|
||||||
|
HW_SELECT = ESC + b'=\x01' # Printer select
|
||||||
|
|
||||||
|
HW_RESET = ESC + b'\x3f\x0a\x00' # Reset printer hardware
|
||||||
|
# (TODO: Where is this specified?)
|
||||||
|
|
||||||
|
# Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>)
|
||||||
|
_CASH_DRAWER = lambda m, t1='', t2='': ESC + b'p' + m + six.int2byte(t1) + six.int2byte(t2)
|
||||||
|
CD_KICK_2 = _CASH_DRAWER(b'\x00', 50, 50) # Sends a pulse to pin 2 []
|
||||||
|
CD_KICK_5 = _CASH_DRAWER(b'\x01', 50, 50) # Sends a pulse to pin 5 []
|
||||||
|
|
||||||
|
# Paper Cutter
|
||||||
|
_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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Text format
|
||||||
|
# TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll
|
||||||
|
# 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_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_2HEIGHT = ESC + b'!\x10' # Double height text
|
||||||
|
TXT_2WIDTH = ESC + b'!\x20' # Double width text
|
||||||
|
TXT_4SQUARE = ESC + b'!\x30' # Quad area text
|
||||||
|
TXT_UNDERL_OFF = ESC + b'\x2d\x00' # Underline font OFF
|
||||||
|
TXT_UNDERL_ON = ESC + b'\x2d\x01' # Underline font 1-dot ON
|
||||||
|
TXT_UNDERL2_ON = ESC + b'\x2d\x02' # Underline font 2-dot ON
|
||||||
|
TXT_BOLD_OFF = ESC + b'\x45\x00' # Bold font OFF
|
||||||
|
TXT_BOLD_ON = ESC + b'\x45\x01' # Bold font ON
|
||||||
|
TXT_FONT_A = ESC + b'\x4d\x00' # Font type A
|
||||||
|
TXT_FONT_B = ESC + b'\x4d\x01' # Font type B
|
||||||
|
TXT_ALIGN_LT = ESC + b'\x61\x00' # Left justification
|
||||||
|
TXT_ALIGN_CT = ESC + b'\x61\x01' # Centering
|
||||||
|
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
|
||||||
|
|
||||||
|
# Char code table
|
||||||
|
CHARCODE_PC437 = ESC + b'\x74\x00' # USA: Standard Europe
|
||||||
|
CHARCODE_JIS = ESC + b'\x74\x01' # Japanese Katakana
|
||||||
|
CHARCODE_PC850 = ESC + b'\x74\x02' # Multilingual
|
||||||
|
CHARCODE_PC860 = ESC + b'\x74\x03' # Portuguese
|
||||||
|
CHARCODE_PC863 = ESC + b'\x74\x04' # Canadian-French
|
||||||
|
CHARCODE_PC865 = ESC + b'\x74\x05' # Nordic
|
||||||
|
CHARCODE_WEU = ESC + b'\x74\x06' # Simplified Kanji, Hirakana
|
||||||
|
CHARCODE_GREEK = ESC + b'\x74\x07' # Simplified Kanji
|
||||||
|
CHARCODE_HEBREW = ESC + b'\x74\x08' # Simplified Kanji
|
||||||
|
CHARCODE_PC1252 = ESC + b'\x74\x11' # Western European Windows Code Set
|
||||||
|
CHARCODE_PC866 = ESC + b'\x74\x12' # Cirillic #2
|
||||||
|
CHARCODE_PC852 = ESC + b'\x74\x13' # Latin 2
|
||||||
|
CHARCODE_PC858 = ESC + b'\x74\x14' # Euro
|
||||||
|
CHARCODE_THAI42 = ESC + b'\x74\x15' # Thai character code 42
|
||||||
|
CHARCODE_THAI11 = ESC + b'\x74\x16' # Thai character code 11
|
||||||
|
CHARCODE_THAI13 = ESC + b'\x74\x17' # Thai character code 13
|
||||||
|
CHARCODE_THAI14 = ESC + b'\x74\x18' # Thai character code 14
|
||||||
|
CHARCODE_THAI16 = ESC + b'\x74\x19' # Thai character code 16
|
||||||
|
CHARCODE_THAI17 = ESC + b'\x74\x1a' # Thai character code 17
|
||||||
|
CHARCODE_THAI18 = ESC + b'\x74\x1b' # Thai character code 18
|
||||||
|
|
||||||
|
# Barcode format
|
||||||
|
_SET_BARCODE_TXT_POS = lambda n: GS + b'H' + n
|
||||||
|
BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS(b'\x00') # HRI barcode chars OFF
|
||||||
|
BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS(b'\x01') # HRI barcode chars above
|
||||||
|
BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS(b'\x02') # HRI barcode chars below
|
||||||
|
BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS(b'\x03') # HRI both above and below
|
||||||
|
|
||||||
|
_SET_HRI_FONT = lambda n: GS + b'f' + n
|
||||||
|
BARCODE_FONT_A = _SET_HRI_FONT(b'\x00') # Font type A for HRI barcode chars
|
||||||
|
BARCODE_FONT_B = _SET_HRI_FONT(b'\x01') # Font type B for HRI barcode chars
|
||||||
|
|
||||||
|
BARCODE_HEIGHT = GS + b'h' # Barcode Height [1-255]
|
||||||
|
BARCODE_WIDTH = GS + b'w' # Barcode Width [2-6]
|
||||||
|
|
||||||
|
# NOTE: This isn't actually an ESC/POS command. It's the common prefix to the
|
||||||
|
# two "print bar code" commands:
|
||||||
|
# - Type A: "GS k <type as integer> <data> NUL"
|
||||||
|
# - TYPE B: "GS k <type as letter> <data length> <data>"
|
||||||
|
# The latter command supports more barcode types
|
||||||
|
_SET_BARCODE_TYPE = lambda m: GS + b'k' + six.int2byte(m)
|
||||||
|
|
||||||
|
# Barcodes for printing function type A
|
||||||
|
BARCODE_TYPE_A = {
|
||||||
|
'UPC-A': _SET_BARCODE_TYPE(0),
|
||||||
|
'UPC-E': _SET_BARCODE_TYPE(1),
|
||||||
|
'EAN13': _SET_BARCODE_TYPE(2),
|
||||||
|
'EAN8': _SET_BARCODE_TYPE(3),
|
||||||
|
'CODE39': _SET_BARCODE_TYPE(4),
|
||||||
|
'ITF': _SET_BARCODE_TYPE(5),
|
||||||
|
'NW7': _SET_BARCODE_TYPE(6),
|
||||||
|
'CODABAR': _SET_BARCODE_TYPE(6), # Same as NW7
|
||||||
|
}
|
||||||
|
|
||||||
|
# Barcodes for printing function type B
|
||||||
|
# The first 8 are the same barcodes as type A
|
||||||
|
BARCODE_TYPE_B = {
|
||||||
|
'UPC-A': _SET_BARCODE_TYPE(65),
|
||||||
|
'UPC-E': _SET_BARCODE_TYPE(66),
|
||||||
|
'EAN13': _SET_BARCODE_TYPE(67),
|
||||||
|
'EAN8': _SET_BARCODE_TYPE(68),
|
||||||
|
'CODE39': _SET_BARCODE_TYPE(69),
|
||||||
|
'ITF': _SET_BARCODE_TYPE(70),
|
||||||
|
'NW7': _SET_BARCODE_TYPE(71),
|
||||||
|
'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7
|
||||||
|
'CODE93': _SET_BARCODE_TYPE(72),
|
||||||
|
'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),
|
||||||
|
'GS1 DATABAR LIMITED': _SET_BARCODE_TYPE(77),
|
||||||
|
'GS1 DATABAR EXPANDED': _SET_BARCODE_TYPE(78),
|
||||||
|
}
|
||||||
|
|
||||||
|
BARCODE_TYPES = {
|
||||||
|
'A': BARCODE_TYPE_A,
|
||||||
|
'B': BARCODE_TYPE_B,
|
||||||
|
}
|
||||||
|
|
||||||
|
# QRCode error correction levels
|
||||||
|
QR_ECLEVEL_L = 0
|
||||||
|
QR_ECLEVEL_M = 1
|
||||||
|
QR_ECLEVEL_Q = 2
|
||||||
|
QR_ECLEVEL_H = 3
|
||||||
|
|
||||||
|
# QRcode models
|
||||||
|
QR_MODEL_1 = 1
|
||||||
|
QR_MODEL_2 = 2
|
||||||
|
QR_MICRO = 3
|
||||||
|
|
||||||
|
# Image format
|
||||||
|
# NOTE: _PRINT_RASTER_IMG is the obsolete ESC/POS "print raster bit image"
|
||||||
|
# command. The constants include a fragment of the data's header.
|
||||||
|
_PRINT_RASTER_IMG = lambda data: GS + b'v0' + data
|
||||||
|
S_RASTER_N = _PRINT_RASTER_IMG(b'\x00') # Set raster image normal size
|
||||||
|
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_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple
|
||||||
|
|
||||||
|
# Printing Density
|
||||||
|
PD_N50 = GS + b'\x7c\x00' # Printing Density -50%
|
||||||
|
PD_N37 = GS + b'\x7c\x01' # Printing Density -37.5%
|
||||||
|
PD_N25 = GS + b'\x7c\x02' # Printing Density -25%
|
||||||
|
PD_N12 = GS + b'\x7c\x03' # Printing Density -12.5%
|
||||||
|
PD_0 = GS + b'\x7c\x04' # Printing Density 0%
|
||||||
|
PD_P50 = GS + b'\x7c\x08' # Printing Density +50%
|
||||||
|
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%
|
746
src/escpos/escpos.py
Normal file
@@ -0,0 +1,746 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
""" Main class
|
||||||
|
|
||||||
|
This module contains the abstract base class :py:class:`Escpos`.
|
||||||
|
|
||||||
|
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
|
||||||
|
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2012 Bashlinux
|
||||||
|
:license: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import qrcode
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
from .constants import *
|
||||||
|
from .exceptions import *
|
||||||
|
|
||||||
|
from abc import ABCMeta, abstractmethod # abstract base class support
|
||||||
|
from escpos.image import EscposImage
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(ABCMeta)
|
||||||
|
class Escpos(object):
|
||||||
|
""" ESC/POS Printer object
|
||||||
|
|
||||||
|
This class is the abstract base class for an esc/pos-printer. The printer implementations are children of this
|
||||||
|
class.
|
||||||
|
"""
|
||||||
|
device = None
|
||||||
|
codepage = None
|
||||||
|
|
||||||
|
def __init__(self, columns=32):
|
||||||
|
""" Initialize ESCPOS Printer
|
||||||
|
|
||||||
|
:param columns: Text columns used by the printer. Defaults to 32."""
|
||||||
|
self.columns = columns
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
""" call self.close upon deletion """
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _raw(self, msg):
|
||||||
|
""" Sends raw data to the printer
|
||||||
|
|
||||||
|
This function has to be individually implemented by the implementations.
|
||||||
|
|
||||||
|
:param msg: message string to be sent to the printer
|
||||||
|
:type msg: bytes
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster"):
|
||||||
|
""" Print an image
|
||||||
|
|
||||||
|
You can select whether the printer should print in high density or not. The default value is high density.
|
||||||
|
When printing in low density, the image will be stretched.
|
||||||
|
|
||||||
|
Esc/Pos supplies several commands for printing. This function supports three of them. Please try to vary the
|
||||||
|
implementations if you have any problems. For example the printer `IT80-002` will have trouble aligning
|
||||||
|
images that are not printed in Column-mode.
|
||||||
|
|
||||||
|
The available printing implementations are:
|
||||||
|
|
||||||
|
* `bitImageRaster`: prints with the `GS v 0`-command
|
||||||
|
* `graphics`: prints with the `GS ( L`-command
|
||||||
|
* `bitImageColumn`: prints with the `ESC *`-command
|
||||||
|
|
||||||
|
:param img_source: PIL image or filename to load: `jpg`, `gif`, `png` or `bmp`
|
||||||
|
: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`
|
||||||
|
|
||||||
|
"""
|
||||||
|
im = EscposImage(img_source)
|
||||||
|
|
||||||
|
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)
|
||||||
|
header = GS + b"v0" + six.int2byte(density_byte) + self._int_low_high(im.width_bytes, 2) + self._int_low_high(im.height, 2)
|
||||||
|
self._raw(header + im.to_raster_format())
|
||||||
|
|
||||||
|
if impl == "graphics":
|
||||||
|
# GS ( L raster format graphics
|
||||||
|
img_header = self._int_low_high(im.width, 2) + self._int_low_high(im.height, 2)
|
||||||
|
tone = b'0'
|
||||||
|
colors = b'1'
|
||||||
|
ym = six.int2byte(1 if high_density_vertical else 2)
|
||||||
|
xm = six.int2byte(1 if high_density_horizontal else 2)
|
||||||
|
header = tone + xm + ym + colors + img_header
|
||||||
|
raster_data = im.to_raster_format()
|
||||||
|
self._image_send_graphics_data(b'0', b'p', header + raster_data)
|
||||||
|
self._image_send_graphics_data(b'0', b'2', b'')
|
||||||
|
|
||||||
|
if impl == "bitImageColumn":
|
||||||
|
# ESC *, column format bit image
|
||||||
|
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)
|
||||||
|
outp = [ESC + b"3" + six.int2byte(16)] # Adjust line-feed size
|
||||||
|
for blob in im.to_column_format(high_density_vertical):
|
||||||
|
outp.append(header + blob + b"\n")
|
||||||
|
outp.append(ESC + b"2") # Reset line-feed size
|
||||||
|
self._raw(b''.join(outp))
|
||||||
|
|
||||||
|
def _image_send_graphics_data(self, m, fn, data):
|
||||||
|
"""
|
||||||
|
Wrapper for GS ( L, to calculate and send correct data length.
|
||||||
|
|
||||||
|
:param m: Modifier//variant for function. Usually '0'
|
||||||
|
:param fn: Function number to use, as byte
|
||||||
|
:param data: Data to send
|
||||||
|
"""
|
||||||
|
header = self._int_low_high(len(data) + 2, 2)
|
||||||
|
self._raw(GS + b'(L' + header + m + fn + data)
|
||||||
|
|
||||||
|
def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2, native=False):
|
||||||
|
""" Print QR Code for the provided string
|
||||||
|
|
||||||
|
:param content: The content of the code. Numeric data will be more efficiently compacted.
|
||||||
|
:param ec: Error-correction level to use. One of QR_ECLEVEL_L (default), QR_ECLEVEL_M, QR_ECLEVEL_Q or
|
||||||
|
QR_ECLEVEL_H.
|
||||||
|
Higher error correction results in a less compact code.
|
||||||
|
:param size: Pixel size to use. Must be 1-16 (default 3)
|
||||||
|
:param model: QR code model to use. Must be one of QR_MODEL_1, QR_MODEL_2 (default) or QR_MICRO (not supported
|
||||||
|
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
|
||||||
|
printer (Default)
|
||||||
|
"""
|
||||||
|
# Basic validation
|
||||||
|
if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]:
|
||||||
|
raise ValueError("Invalid error correction level")
|
||||||
|
if not 1 <= size <= 16:
|
||||||
|
raise ValueError("Invalid block size (must be 1-16)")
|
||||||
|
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)")
|
||||||
|
if content == "":
|
||||||
|
# Handle edge case by printing nothing.
|
||||||
|
return
|
||||||
|
if not native:
|
||||||
|
# Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image
|
||||||
|
if model != QR_MODEL_2:
|
||||||
|
raise ValueError("Invalid QR model for qrlib rendering (must be QR_MODEL_2)")
|
||||||
|
python_qr_ec = {
|
||||||
|
QR_ECLEVEL_H: qrcode.constants.ERROR_CORRECT_H,
|
||||||
|
QR_ECLEVEL_L: qrcode.constants.ERROR_CORRECT_L,
|
||||||
|
QR_ECLEVEL_M: qrcode.constants.ERROR_CORRECT_M,
|
||||||
|
QR_ECLEVEL_Q: qrcode.constants.ERROR_CORRECT_Q
|
||||||
|
}
|
||||||
|
qr_code = qrcode.QRCode(version=None, box_size=size, border=1, error_correction=python_qr_ec[ec])
|
||||||
|
qr_code.add_data(content)
|
||||||
|
qr_code.make(fit=True)
|
||||||
|
qr_img = qr_code.make_image()
|
||||||
|
im = qr_img._img.convert("RGB")
|
||||||
|
# Convert the RGB image in printable image
|
||||||
|
self.image(im)
|
||||||
|
return
|
||||||
|
# Native 2D code printing
|
||||||
|
cn = b'1' # Code type for QR code
|
||||||
|
# Select model: 1, 2 or micro.
|
||||||
|
self._send_2d_code_data(six.int2byte(65), cn, six.int2byte(48 + model) + six.int2byte(0))
|
||||||
|
# Set dot size.
|
||||||
|
self._send_2d_code_data(six.int2byte(67), cn, six.int2byte(size))
|
||||||
|
# Set error correction level: L, M, Q, or H
|
||||||
|
self._send_2d_code_data(six.int2byte(69), cn, six.int2byte(48 + ec))
|
||||||
|
# Send content & print
|
||||||
|
self._send_2d_code_data(six.int2byte(80), cn, content.encode('utf-8'), b'0')
|
||||||
|
self._send_2d_code_data(six.int2byte(81), cn, b'', b'0')
|
||||||
|
|
||||||
|
def _send_2d_code_data(self, fn, cn, data, m=b''):
|
||||||
|
""" Wrapper for GS ( k, to calculate and send correct data length.
|
||||||
|
|
||||||
|
:param fn: Function to use.
|
||||||
|
:param cn: Output code type. Affects available data.
|
||||||
|
:param data: Data to send.
|
||||||
|
:param m: Modifier/variant for function. Often '0' where used.
|
||||||
|
"""
|
||||||
|
if len(m) > 1 or len(cn) != 1 or len(fn) != 1:
|
||||||
|
raise ValueError("cn and fn must be one byte each.")
|
||||||
|
header = self._int_low_high(len(data) + len(m) + 2, 2)
|
||||||
|
self._raw(GS + b'(k' + header + cn + fn + m + data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _int_low_high(inp_number, out_bytes):
|
||||||
|
""" Generate multiple bytes for a number: In lower and higher parts, or more parts as needed.
|
||||||
|
|
||||||
|
:param inp_number: Input number
|
||||||
|
:param out_bytes: The number of bytes to output (1 - 4).
|
||||||
|
"""
|
||||||
|
max_input = (256 << (out_bytes * 8) - 1)
|
||||||
|
if not 1 <= out_bytes <= 4:
|
||||||
|
raise ValueError("Can only output 1-4 byes")
|
||||||
|
if not 0 <= inp_number <= max_input:
|
||||||
|
raise ValueError("Number too large. Can only output up to {0} in {1} byes".format(max_input, out_bytes))
|
||||||
|
outp = b''
|
||||||
|
for _ in range(0, out_bytes):
|
||||||
|
outp += six.int2byte(inp_number % 256)
|
||||||
|
inp_number //= 256
|
||||||
|
return outp
|
||||||
|
|
||||||
|
def charcode(self, code):
|
||||||
|
""" Set Character Code Table
|
||||||
|
|
||||||
|
Sends the control sequence from :py:mod:`escpos.constants` to the printer
|
||||||
|
with :py:meth:`escpos.printer.'implementation'._raw()`.
|
||||||
|
|
||||||
|
:param code: Name of CharCode
|
||||||
|
:raises: :py:exc:`~escpos.exceptions.CharCodeError`
|
||||||
|
"""
|
||||||
|
# TODO improve this (rather unhandy code)
|
||||||
|
# TODO check the codepages
|
||||||
|
if code.upper() == "USA":
|
||||||
|
self._raw(CHARCODE_PC437)
|
||||||
|
self.codepage = 'cp437'
|
||||||
|
elif code.upper() == "JIS":
|
||||||
|
self._raw(CHARCODE_JIS)
|
||||||
|
self.codepage = 'cp932'
|
||||||
|
elif code.upper() == "MULTILINGUAL":
|
||||||
|
self._raw(CHARCODE_PC850)
|
||||||
|
self.codepage = 'cp850'
|
||||||
|
elif code.upper() == "PORTUGUESE":
|
||||||
|
self._raw(CHARCODE_PC860)
|
||||||
|
self.codepage = 'cp860'
|
||||||
|
elif code.upper() == "CA_FRENCH":
|
||||||
|
self._raw(CHARCODE_PC863)
|
||||||
|
self.codepage = 'cp863'
|
||||||
|
elif code.upper() == "NORDIC":
|
||||||
|
self._raw(CHARCODE_PC865)
|
||||||
|
self.codepage = 'cp865'
|
||||||
|
elif code.upper() == "WEST_EUROPE":
|
||||||
|
self._raw(CHARCODE_WEU)
|
||||||
|
self.codepage = 'latin_1'
|
||||||
|
elif code.upper() == "GREEK":
|
||||||
|
self._raw(CHARCODE_GREEK)
|
||||||
|
self.codepage = 'cp737'
|
||||||
|
elif code.upper() == "HEBREW":
|
||||||
|
self._raw(CHARCODE_HEBREW)
|
||||||
|
self.codepage = 'cp862'
|
||||||
|
# elif code.upper() == "LATVIAN": # this is not listed in the constants
|
||||||
|
# self._raw(CHARCODE_PC755)
|
||||||
|
# self.codepage = 'cp'
|
||||||
|
elif code.upper() == "WPC1252":
|
||||||
|
self._raw(CHARCODE_PC1252)
|
||||||
|
self.codepage = 'cp1252'
|
||||||
|
elif code.upper() == "CIRILLIC2":
|
||||||
|
self._raw(CHARCODE_PC866)
|
||||||
|
self.codepage = 'cp866'
|
||||||
|
elif code.upper() == "LATIN2":
|
||||||
|
self._raw(CHARCODE_PC852)
|
||||||
|
self.codepage = 'cp852'
|
||||||
|
elif code.upper() == "EURO":
|
||||||
|
self._raw(CHARCODE_PC858)
|
||||||
|
self.codepage = 'cp858'
|
||||||
|
elif code.upper() == "THAI42":
|
||||||
|
self._raw(CHARCODE_THAI42)
|
||||||
|
self.codepage = 'cp874'
|
||||||
|
elif code.upper() == "THAI11":
|
||||||
|
self._raw(CHARCODE_THAI11)
|
||||||
|
self.codepage = 'cp874'
|
||||||
|
elif code.upper() == "THAI13":
|
||||||
|
self._raw(CHARCODE_THAI13)
|
||||||
|
self.codepage = 'cp874'
|
||||||
|
elif code.upper() == "THAI14":
|
||||||
|
self._raw(CHARCODE_THAI14)
|
||||||
|
self.codepage = 'cp874'
|
||||||
|
elif code.upper() == "THAI16":
|
||||||
|
self._raw(CHARCODE_THAI16)
|
||||||
|
self.codepage = 'cp874'
|
||||||
|
elif code.upper() == "THAI17":
|
||||||
|
self._raw(CHARCODE_THAI17)
|
||||||
|
self.codepage = 'cp874'
|
||||||
|
elif code.upper() == "THAI18":
|
||||||
|
self._raw(CHARCODE_THAI18)
|
||||||
|
self.codepage = 'cp874'
|
||||||
|
else:
|
||||||
|
raise CharCodeError()
|
||||||
|
|
||||||
|
def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A", align_ct=True, function_type="A"):
|
||||||
|
""" Print Barcode
|
||||||
|
|
||||||
|
This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to
|
||||||
|
be supported by the unit. Currently you have to check manually whether your barcode text is correct. Uncorrect
|
||||||
|
barcodes may lead to unexpected printer behaviour. 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
|
||||||
|
will not be printed if it is outside of the printable area. (Which should be impossible with this method, so
|
||||||
|
this information is probably more useful for debugging purposes.)
|
||||||
|
|
||||||
|
.. todo:: On TM-T88II width from 1 to 6 is accepted. Try to acquire command reference and correct the code.
|
||||||
|
.. todo:: Supplying pos does not have an effect for every barcode type. Check and document for which types this
|
||||||
|
is true.
|
||||||
|
|
||||||
|
If you do not want to center the barcode you can call the method with `align_ct=False`, which will disable
|
||||||
|
automatic centering. Please note that when you use center alignment, then the alignment of text will be changed
|
||||||
|
automatically to centered. You have to manually restore the alignment if necessary.
|
||||||
|
|
||||||
|
.. todo:: If further barcode-types are needed they could be rendered transparently as an image. (This could also
|
||||||
|
be of help if the printer does not support types that others do.)
|
||||||
|
|
||||||
|
:param code: alphanumeric data to be printed as bar code
|
||||||
|
:param bc: barcode format, possible values are for type A are:
|
||||||
|
|
||||||
|
* UPC-A
|
||||||
|
* UPC-E
|
||||||
|
* EAN13
|
||||||
|
* EAN8
|
||||||
|
* CODE39
|
||||||
|
* ITF
|
||||||
|
* NW7
|
||||||
|
|
||||||
|
Possible values for type B:
|
||||||
|
|
||||||
|
* All types from function type A
|
||||||
|
* CODE93
|
||||||
|
* CODE128
|
||||||
|
* GS1-128
|
||||||
|
* GS1 DataBar Omnidirectional
|
||||||
|
* GS1 DataBar Truncated
|
||||||
|
* GS1 DataBar Limited
|
||||||
|
* GS1 DataBar Expanded
|
||||||
|
|
||||||
|
If none is specified, the method raises :py:exc:`~escpos.exceptions.BarcodeTypeError`.
|
||||||
|
:param height: barcode height, has to be between 1 and 255
|
||||||
|
*default*: 64
|
||||||
|
:type height: int
|
||||||
|
:param width: barcode width, has to be between 2 and 6
|
||||||
|
*default*: 3
|
||||||
|
:type width: int
|
||||||
|
:param pos: where to place the text relative to the barcode, *default*: BELOW
|
||||||
|
|
||||||
|
* ABOVE
|
||||||
|
* BELOW
|
||||||
|
* BOTH
|
||||||
|
* OFF
|
||||||
|
|
||||||
|
:param font: select font (see ESC/POS-documentation, the device often has two fonts), *default*: A
|
||||||
|
|
||||||
|
* A
|
||||||
|
* B
|
||||||
|
|
||||||
|
:param align_ct: If this parameter is True the barcode will be centered. Otherwise no alignment command will be
|
||||||
|
issued.
|
||||||
|
:type align_ct: bool
|
||||||
|
|
||||||
|
:param function_type: Choose between ESCPOS function type A or B, depending on printer support and desired
|
||||||
|
barcode.
|
||||||
|
*default*: A
|
||||||
|
|
||||||
|
:raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`,
|
||||||
|
:py:exc:`~escpos.exceptions.BarcodeTypeError`,
|
||||||
|
:py:exc:`~escpos.exceptions.BarcodeCodeError`
|
||||||
|
"""
|
||||||
|
# Align Bar Code()
|
||||||
|
if align_ct:
|
||||||
|
self._raw(TXT_ALIGN_CT)
|
||||||
|
# Height
|
||||||
|
if 1 <= height <= 255:
|
||||||
|
self._raw(BARCODE_HEIGHT + six.int2byte(height))
|
||||||
|
else:
|
||||||
|
raise BarcodeSizeError("height = {height}".format(height=height))
|
||||||
|
# Width
|
||||||
|
if 2 <= width <= 6:
|
||||||
|
self._raw(BARCODE_WIDTH + six.int2byte(width))
|
||||||
|
else:
|
||||||
|
raise BarcodeSizeError("width = {width}".format(width=width))
|
||||||
|
# Font
|
||||||
|
if font.upper() == "B":
|
||||||
|
self._raw(BARCODE_FONT_B)
|
||||||
|
else: # DEFAULT FONT: A
|
||||||
|
self._raw(BARCODE_FONT_A)
|
||||||
|
# Position
|
||||||
|
if pos.upper() == "OFF":
|
||||||
|
self._raw(BARCODE_TXT_OFF)
|
||||||
|
elif pos.upper() == "BOTH":
|
||||||
|
self._raw(BARCODE_TXT_BTH)
|
||||||
|
elif pos.upper() == "ABOVE":
|
||||||
|
self._raw(BARCODE_TXT_ABV)
|
||||||
|
else: # DEFAULT POSITION: BELOW
|
||||||
|
self._raw(BARCODE_TXT_BLW)
|
||||||
|
|
||||||
|
bc_types = BARCODE_TYPES[function_type.upper()]
|
||||||
|
if bc.upper() not in bc_types.keys():
|
||||||
|
# TODO: Raise a better error, or fix the message of this error type
|
||||||
|
raise BarcodeTypeError("Barcode type {bc} not valid for barcode function type {function_type}".format(
|
||||||
|
bc=bc,
|
||||||
|
function_type=function_type,
|
||||||
|
))
|
||||||
|
|
||||||
|
self._raw(bc_types[bc.upper()])
|
||||||
|
|
||||||
|
if function_type.upper() == "B":
|
||||||
|
self._raw(six.int2byte(len(code)))
|
||||||
|
|
||||||
|
# Print Code
|
||||||
|
if code:
|
||||||
|
self._raw(code.encode())
|
||||||
|
else:
|
||||||
|
raise BarcodeCodeError()
|
||||||
|
|
||||||
|
if function_type.upper() == "A":
|
||||||
|
self._raw(NUL)
|
||||||
|
|
||||||
|
def text(self, txt):
|
||||||
|
""" Print alpha-numeric text
|
||||||
|
|
||||||
|
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
|
||||||
|
:raises: :py:exc:`~escpos.exceptions.TextError`
|
||||||
|
"""
|
||||||
|
if txt:
|
||||||
|
if self.codepage:
|
||||||
|
self._raw(txt.encode(self.codepage))
|
||||||
|
else:
|
||||||
|
self._raw(txt.encode())
|
||||||
|
else:
|
||||||
|
# TODO: why is it problematic to print an empty string?
|
||||||
|
raise TextError()
|
||||||
|
|
||||||
|
def block_text(self, txt, columns=None):
|
||||||
|
""" Text is printed wrapped to specified columns
|
||||||
|
|
||||||
|
Text has to be encoded in unicode.
|
||||||
|
|
||||||
|
:param txt: text to be printed
|
||||||
|
:param columns: amount of columns
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
col_count = self.columns if columns is None else columns
|
||||||
|
self.text(textwrap.fill(txt, col_count))
|
||||||
|
|
||||||
|
def set(self, align='left', font='a', text_type='normal', width=1, height=1, density=9, invert=False, smooth=False,
|
||||||
|
flip=False):
|
||||||
|
""" Set text properties by sending them to the printer
|
||||||
|
|
||||||
|
:param align: horizontal position for text, possible values are:
|
||||||
|
|
||||||
|
* CENTER
|
||||||
|
* LEFT
|
||||||
|
* RIGHT
|
||||||
|
|
||||||
|
*default*: LEFT
|
||||||
|
:param font: font type, possible values are A or B, *default*: A
|
||||||
|
:param text_type: text type, possible values are:
|
||||||
|
|
||||||
|
* B for bold
|
||||||
|
* U for underlined
|
||||||
|
* U2 for underlined, version 2
|
||||||
|
* BU for bold and underlined
|
||||||
|
* BU2 for bold and underlined, version 2
|
||||||
|
* NORMAL for normal text
|
||||||
|
|
||||||
|
*default*: NORMAL
|
||||||
|
:param width: text width 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 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 flip: True enables upside-down printing, *default*: False
|
||||||
|
:type invert: bool
|
||||||
|
"""
|
||||||
|
# Width
|
||||||
|
if height == 2 and width == 2:
|
||||||
|
self._raw(TXT_NORMAL)
|
||||||
|
self._raw(TXT_4SQUARE)
|
||||||
|
elif height == 2 and width == 1:
|
||||||
|
self._raw(TXT_NORMAL)
|
||||||
|
self._raw(TXT_2HEIGHT)
|
||||||
|
elif width == 2 and height == 1:
|
||||||
|
self._raw(TXT_NORMAL)
|
||||||
|
self._raw(TXT_2WIDTH)
|
||||||
|
elif width == 1 and height == 1:
|
||||||
|
self._raw(TXT_NORMAL)
|
||||||
|
elif 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and isinstance(height, int):
|
||||||
|
self._raw(TXT_SIZE + six.int2byte(TXT_WIDTH[width] + TXT_HEIGHT[height]))
|
||||||
|
else:
|
||||||
|
raise SetVariableError()
|
||||||
|
# Upside down
|
||||||
|
if flip:
|
||||||
|
self._raw(TXT_FLIP_ON)
|
||||||
|
else:
|
||||||
|
self._raw(TXT_FLIP_OFF)
|
||||||
|
# Smoothing
|
||||||
|
if smooth:
|
||||||
|
self._raw(TXT_SMOOTH_ON)
|
||||||
|
else:
|
||||||
|
self._raw(TXT_SMOOTH_OFF)
|
||||||
|
# Type
|
||||||
|
if text_type.upper() == "B":
|
||||||
|
self._raw(TXT_BOLD_ON)
|
||||||
|
self._raw(TXT_UNDERL_OFF)
|
||||||
|
elif text_type.upper() == "U":
|
||||||
|
self._raw(TXT_BOLD_OFF)
|
||||||
|
self._raw(TXT_UNDERL_ON)
|
||||||
|
elif text_type.upper() == "U2":
|
||||||
|
self._raw(TXT_BOLD_OFF)
|
||||||
|
self._raw(TXT_UNDERL2_ON)
|
||||||
|
elif text_type.upper() == "BU":
|
||||||
|
self._raw(TXT_BOLD_ON)
|
||||||
|
self._raw(TXT_UNDERL_ON)
|
||||||
|
elif text_type.upper() == "BU2":
|
||||||
|
self._raw(TXT_BOLD_ON)
|
||||||
|
self._raw(TXT_UNDERL2_ON)
|
||||||
|
elif text_type.upper() == "NORMAL":
|
||||||
|
self._raw(TXT_BOLD_OFF)
|
||||||
|
self._raw(TXT_UNDERL_OFF)
|
||||||
|
# Font
|
||||||
|
if font.upper() == "B":
|
||||||
|
self._raw(TXT_FONT_B)
|
||||||
|
else: # DEFAULT FONT: A
|
||||||
|
self._raw(TXT_FONT_A)
|
||||||
|
# Align
|
||||||
|
if align.upper() == "CENTER":
|
||||||
|
self._raw(TXT_ALIGN_CT)
|
||||||
|
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 cut(self, mode=''):
|
||||||
|
""" Cut paper.
|
||||||
|
|
||||||
|
Without any arguments the paper will be cut completely. With 'mode=PART' a partial cut will
|
||||||
|
be attempted. Note however, that not all models can do a partial cut. See the documentation of
|
||||||
|
your printer for details.
|
||||||
|
|
||||||
|
.. todo:: Check this function on TM-T88II.
|
||||||
|
|
||||||
|
:param mode: set to 'PART' for a partial cut
|
||||||
|
"""
|
||||||
|
# Fix the size between last line and cut
|
||||||
|
# TODO: handle this with a line feed
|
||||||
|
self._raw(b"\n\n\n\n\n\n")
|
||||||
|
if mode.upper() == "PART":
|
||||||
|
self._raw(PAPER_PART_CUT)
|
||||||
|
else: # DEFAULT MODE: FULL CUT
|
||||||
|
self._raw(PAPER_FULL_CUT)
|
||||||
|
|
||||||
|
def cashdraw(self, pin):
|
||||||
|
""" Send pulse to kick the cash drawer
|
||||||
|
|
||||||
|
Kick cash drawer on pin 2 or pin 5 according to parameter.
|
||||||
|
|
||||||
|
:param pin: pin number, 2 or 5
|
||||||
|
:raises: :py:exc:`~escpos.exceptions.CashDrawerError`
|
||||||
|
"""
|
||||||
|
if pin == 2:
|
||||||
|
self._raw(CD_KICK_2)
|
||||||
|
elif pin == 5:
|
||||||
|
self._raw(CD_KICK_5)
|
||||||
|
else:
|
||||||
|
raise CashDrawerError()
|
||||||
|
|
||||||
|
def hw(self, hw):
|
||||||
|
""" Hardware operations
|
||||||
|
|
||||||
|
:param hw: hardware action, may be:
|
||||||
|
|
||||||
|
* INIT
|
||||||
|
* SELECT
|
||||||
|
* RESET
|
||||||
|
"""
|
||||||
|
if hw.upper() == "INIT":
|
||||||
|
self._raw(HW_INIT)
|
||||||
|
elif hw.upper() == "SELECT":
|
||||||
|
self._raw(HW_SELECT)
|
||||||
|
elif hw.upper() == "RESET":
|
||||||
|
self._raw(HW_RESET)
|
||||||
|
else: # DEFAULT: DOES NOTHING
|
||||||
|
pass
|
||||||
|
|
||||||
|
def control(self, ctl, pos=4):
|
||||||
|
""" Feed control sequences
|
||||||
|
|
||||||
|
:param ctl: string for the following control sequences:
|
||||||
|
|
||||||
|
* LF *for Line Feed*
|
||||||
|
* FF *for Form Feed*
|
||||||
|
* CR *for Carriage Return*
|
||||||
|
* HT *for Horizontal Tab*
|
||||||
|
* VT *for Vertical Tab*
|
||||||
|
|
||||||
|
:param pos: integer between 1 and 16, controls the horizontal tab position
|
||||||
|
:raises: :py:exc:`~escpos.exceptions.TabPosError`
|
||||||
|
"""
|
||||||
|
# Set tab positions
|
||||||
|
if not (1 <= pos <= 16):
|
||||||
|
raise TabPosError()
|
||||||
|
else:
|
||||||
|
self._raw(CTL_SET_HT + six.int2byte(pos))
|
||||||
|
# Set position
|
||||||
|
if ctl.upper() == "LF":
|
||||||
|
self._raw(CTL_LF)
|
||||||
|
elif ctl.upper() == "FF":
|
||||||
|
self._raw(CTL_FF)
|
||||||
|
elif ctl.upper() == "CR":
|
||||||
|
self._raw(CTL_CR)
|
||||||
|
elif ctl.upper() == "HT":
|
||||||
|
self._raw(CTL_HT)
|
||||||
|
elif ctl.upper() == "VT":
|
||||||
|
self._raw(CTL_VT)
|
||||||
|
|
||||||
|
def panel_buttons(self, enable=True):
|
||||||
|
""" Controls the panel buttons on the printer (e.g. FEED)
|
||||||
|
|
||||||
|
When enable is set to False the panel buttons on the printer will be disabled. Calling the method with
|
||||||
|
enable=True or without argument will enable the panel buttons.
|
||||||
|
|
||||||
|
If panel buttons are enabled, the function of the panel button, such as feeding, will be executed upon pressing
|
||||||
|
the button. If the panel buttons are disabled, pressing them will not have any effect.
|
||||||
|
|
||||||
|
This command is effective until the printer is initialized, reset or power-cycled. The default is enabled panel
|
||||||
|
buttons.
|
||||||
|
|
||||||
|
Some panel buttons will always work, especially when printer is opened. See for more information the manual
|
||||||
|
of your printer and the escpos-command-reference.
|
||||||
|
|
||||||
|
:param enable: controls the panel buttons
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
|
if enable:
|
||||||
|
self._raw(PANEL_BUTTON_ON)
|
||||||
|
else:
|
||||||
|
self._raw(PANEL_BUTTON_OFF)
|
||||||
|
|
||||||
|
|
||||||
|
class EscposIO(object):
|
||||||
|
"""ESC/POS Printer IO object
|
||||||
|
|
||||||
|
Allows the class to be used together with the `with`-statement. You have to define a printer instance
|
||||||
|
and assign it to the EsposIO-class.
|
||||||
|
This example explains the usage:
|
||||||
|
|
||||||
|
.. code-block:: Python
|
||||||
|
|
||||||
|
with EscposIO(printer.Serial('/dev/ttyUSB0')) as p:
|
||||||
|
p.set(font='a', height=2, align='center', text_type='bold')
|
||||||
|
p.printer.set(align='left')
|
||||||
|
p.printer.image('logo.gif')
|
||||||
|
p.writelines('Big line\\n', font='b')
|
||||||
|
p.writelines('Привет')
|
||||||
|
p.writelines('BIG TEXT', width=2)
|
||||||
|
|
||||||
|
After the `with`-statement the printer automatically cuts the paper if `autocut` is `True`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, printer, autocut=True, autoclose=True, **kwargs):
|
||||||
|
"""
|
||||||
|
:param printer: An EscPos-printer object
|
||||||
|
:type printer: escpos.Escpos
|
||||||
|
:param autocut: If True, paper is automatically cut after the `with`-statement *default*: True
|
||||||
|
:param kwargs: These arguments will be passed to :py:meth:`escpos.Escpos.set()`
|
||||||
|
"""
|
||||||
|
self.printer = printer
|
||||||
|
self.params = kwargs
|
||||||
|
self.autocut = autocut
|
||||||
|
self.autoclose = autoclose
|
||||||
|
|
||||||
|
def set(self, **kwargs):
|
||||||
|
""" Set the printer-parameters
|
||||||
|
|
||||||
|
Controls which parameters will be passed to :py:meth:`Escpos.set() <escpos.escpos.Escpos.set()>`.
|
||||||
|
For more information on the parameters see the :py:meth:`set() <escpos.escpos.Escpos.set()>`-methods
|
||||||
|
documentation. These parameters can also be passed with this class' constructor or the
|
||||||
|
:py:meth:`~escpos.escpos.EscposIO.writelines()`-method.
|
||||||
|
|
||||||
|
:param kwargs: keyword-parameters that will be passed to :py:meth:`Escpos.set() <escpos.escpos.Escpos.set()>`
|
||||||
|
"""
|
||||||
|
self.params.update(kwargs)
|
||||||
|
|
||||||
|
def writelines(self, text, **kwargs):
|
||||||
|
params = dict(self.params)
|
||||||
|
params.update(kwargs)
|
||||||
|
|
||||||
|
if isinstance(text, six.text_type):
|
||||||
|
lines = text.split('\n')
|
||||||
|
elif isinstance(text, list) or isinstance(text, tuple):
|
||||||
|
lines = text
|
||||||
|
else:
|
||||||
|
lines = ["{0}".format(text), ]
|
||||||
|
|
||||||
|
# TODO check unicode handling
|
||||||
|
# TODO flush? or on print? (this should prob rather be handled by the _raw-method)
|
||||||
|
for line in lines:
|
||||||
|
self.printer.set(**params)
|
||||||
|
if isinstance(text, six.text_type):
|
||||||
|
self.printer.text(u"{0}\n".format(line))
|
||||||
|
else:
|
||||||
|
self.printer.text("{0}\n".format(line))
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
""" called upon closing the `with`-statement
|
||||||
|
"""
|
||||||
|
self.printer.close()
|
||||||
|
|
||||||
|
def __enter__(self, **kwargs):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
"""
|
||||||
|
|
||||||
|
If :py:attr:`autocut <escpos.escpos.EscposIO.autocut>` is `True` (set by this class' constructor),
|
||||||
|
then :py:meth:`printer.cut() <escpos.escpos.Escpos.cut()>` will be called here.
|
||||||
|
"""
|
||||||
|
if not (type is not None and issubclass(type, Exception)):
|
||||||
|
if self.autocut:
|
||||||
|
self.printer.cut()
|
||||||
|
|
||||||
|
if self.autoclose:
|
||||||
|
self.close()
|
241
src/escpos/exceptions.py
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
""" ESC/POS Exceptions classes
|
||||||
|
|
||||||
|
Result/Exit codes:
|
||||||
|
|
||||||
|
- `0` = success
|
||||||
|
- `10` = No Barcode type defined :py:exc:`~escpos.exceptions.BarcodeTypeError`
|
||||||
|
- `20` = Barcode size values are out of range :py:exc:`~escpos.exceptions.BarcodeSizeError`
|
||||||
|
- `30` = Barcode text not supplied :py:exc:`~escpos.exceptions.BarcodeCodeError`
|
||||||
|
- `40` = Image height is too large :py:exc:`~escpos.exceptions.ImageSizeError`
|
||||||
|
- `50` = No string supplied to be printed :py:exc:`~escpos.exceptions.TextError`
|
||||||
|
- `60` = Invalid pin to send Cash Drawer pulse :py:exc:`~escpos.exceptions.CashDrawerError`
|
||||||
|
- `70` = Invalid number of tab positions :py:exc:`~escpos.exceptions.TabPosError`
|
||||||
|
- `80` = Invalid char code :py:exc:`~escpos.exceptions.CharCodeError`
|
||||||
|
- `90` = USB device not found :py:exc:`~escpos.exceptions.USBNotFoundError`
|
||||||
|
- `100` = Set variable out of range :py:exc:`~escpos.exceptions.SetVariableError`
|
||||||
|
- `200` = Configuration not found :py:exc:`~escpos.exceptions.ConfigNotFoundError`
|
||||||
|
- `210` = Configuration syntax error :py:exc:`~escpos.exceptions.ConfigSyntaxError`
|
||||||
|
- `220` = Configuration section not found :py:exc:`~escpos.exceptions.ConfigSectionMissingError`
|
||||||
|
|
||||||
|
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
|
||||||
|
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2012 Bashlinux
|
||||||
|
:license: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
""" Base class for ESC/POS errors """
|
||||||
|
def __init__(self, msg, status=None):
|
||||||
|
Exception.__init__(self)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 1
|
||||||
|
if status is not None:
|
||||||
|
self.resultcode = status
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.msg
|
||||||
|
|
||||||
|
|
||||||
|
class BarcodeTypeError(Error):
|
||||||
|
""" No Barcode type defined.
|
||||||
|
|
||||||
|
This exception indicates that no known barcode-type has been entered. The barcode-type has to be
|
||||||
|
one of those specified in :py:meth:`escpos.escpos.Escpos.barcode`.
|
||||||
|
The returned error code is `10`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 10
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "No Barcode type is defined ({msg})".format(msg=self.msg)
|
||||||
|
|
||||||
|
|
||||||
|
class BarcodeSizeError(Error):
|
||||||
|
""" Barcode size is out of range.
|
||||||
|
|
||||||
|
This exception indicates that the values for the barcode size are out of range.
|
||||||
|
The size of the barcode has to be in the range that is specified in :py:meth:`escpos.escpos.Escpos.barcode`.
|
||||||
|
The resulting returncode is `20`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 20
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Barcode size is out of range ({msg})".format(msg=self.msg)
|
||||||
|
|
||||||
|
|
||||||
|
class BarcodeCodeError(Error):
|
||||||
|
""" No Barcode code was supplied.
|
||||||
|
|
||||||
|
No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode`.
|
||||||
|
The returncode for this exception is `30`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 30
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "No Barcode code was supplied"
|
||||||
|
|
||||||
|
|
||||||
|
class ImageSizeError(Error):
|
||||||
|
""" Image height is longer than 255px and can't be printed.
|
||||||
|
|
||||||
|
The returncode for this exception is `40`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 40
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Image height is longer than 255px and can't be printed"
|
||||||
|
|
||||||
|
|
||||||
|
class TextError(Error):
|
||||||
|
""" Text string must be supplied to the `text()` method.
|
||||||
|
|
||||||
|
This exception is raised when an empty string is passed to :py:meth:`escpos.escpos.Escpos.text`.
|
||||||
|
The returncode for this exception is `50`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 50
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Text string must be supplied to the text() method"
|
||||||
|
|
||||||
|
|
||||||
|
class CashDrawerError(Error):
|
||||||
|
""" Valid pin must be set in order to send pulse.
|
||||||
|
|
||||||
|
A valid pin number has to be passed onto the method :py:meth:`escpos.escpos.Escpos.cashdraw`.
|
||||||
|
The returncode for this exception is `60`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 60
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Valid pin must be set to send pulse"
|
||||||
|
|
||||||
|
|
||||||
|
class TabPosError(Error):
|
||||||
|
""" Valid tab positions must be in the range 0 to 16.
|
||||||
|
|
||||||
|
This exception is raised by :py:meth:`escpos.escpos.Escpos.control`.
|
||||||
|
The returncode for this exception is `70`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 70
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Valid tab positions must be in the range 0 to 16"
|
||||||
|
|
||||||
|
|
||||||
|
class CharCodeError(Error):
|
||||||
|
""" Valid char code must be set.
|
||||||
|
|
||||||
|
The supplied charcode-name in :py:meth:`escpos.escpos.Escpos.charcode` is unknown.
|
||||||
|
Ths returncode for this exception is `80`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 80
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Valid char code must be set"
|
||||||
|
|
||||||
|
|
||||||
|
class USBNotFoundError(Error):
|
||||||
|
""" Device wasn't found (probably not plugged in)
|
||||||
|
|
||||||
|
The USB device seems to be not plugged in.
|
||||||
|
Ths returncode for this exception is `90`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 90
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "USB device not found"
|
||||||
|
|
||||||
|
|
||||||
|
class SetVariableError(Error):
|
||||||
|
""" A set method variable was out of range
|
||||||
|
|
||||||
|
Check set variables against minimum and maximum values
|
||||||
|
Ths returncode for this exception is `100`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 100
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Set variable out of range"
|
||||||
|
|
||||||
|
|
||||||
|
# Configuration errors
|
||||||
|
|
||||||
|
class ConfigNotFoundError(Error):
|
||||||
|
""" The configuration file was not found
|
||||||
|
|
||||||
|
The default or passed configuration file could not be read
|
||||||
|
Ths returncode for this exception is `200`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 200
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Configuration not found ({msg})".format(msg=self.msg)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigSyntaxError(Error):
|
||||||
|
""" The configuration file is invalid
|
||||||
|
|
||||||
|
The syntax is incorrect
|
||||||
|
Ths returncode for this exception is `210`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 210
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Configuration syntax is invalid ({msg})".format(msg=self.msg)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigSectionMissingError(Error):
|
||||||
|
""" The configuration file is missing a section
|
||||||
|
|
||||||
|
The part of the config asked for doesn't exist in the loaded configuration
|
||||||
|
Ths returncode for this exception is `220`.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg=""):
|
||||||
|
Error.__init__(self, msg)
|
||||||
|
self.msg = msg
|
||||||
|
self.resultcode = 220
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Configuration section is missing ({msg})".format(msg=self.msg)
|
90
src/escpos/image.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
""" Image format handling class
|
||||||
|
|
||||||
|
This module contains the image format handler :py:class:`EscposImage`.
|
||||||
|
|
||||||
|
:author: `Michael Billington <michael.billington@gmail.com>`_
|
||||||
|
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2016 Michael Billington <michael.billington@gmail.com>
|
||||||
|
:license: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
from PIL import Image, ImageOps
|
||||||
|
|
||||||
|
|
||||||
|
class EscposImage(object):
|
||||||
|
"""
|
||||||
|
Load images in, and output ESC/POS formats.
|
||||||
|
|
||||||
|
The class is designed to efficiently delegate image processing to
|
||||||
|
PIL, rather than spend CPU cycles looping over pixels.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, img_source):
|
||||||
|
"""
|
||||||
|
Load in an image
|
||||||
|
|
||||||
|
:param img_source: PIL.Image, or filename to load one from.
|
||||||
|
"""
|
||||||
|
if isinstance(img_source, Image.Image):
|
||||||
|
img_original = img_source
|
||||||
|
else:
|
||||||
|
img_original = Image.open(img_source)
|
||||||
|
|
||||||
|
# Convert to white RGB background, paste over white background
|
||||||
|
# to strip alpha.
|
||||||
|
img_original = img_original.convert('RGBA')
|
||||||
|
im = Image.new("RGB", img_original.size, (255, 255, 255))
|
||||||
|
im.paste(img_original, mask=img_original.split()[3])
|
||||||
|
# Convert down to greyscale
|
||||||
|
im = im.convert("L")
|
||||||
|
# Invert: Only works on 'L' images
|
||||||
|
im = ImageOps.invert(im)
|
||||||
|
# Pure black and white
|
||||||
|
self._im = im.convert("1")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
"""
|
||||||
|
Width of image in pixels
|
||||||
|
"""
|
||||||
|
width_pixels, _ = self._im.size
|
||||||
|
return width_pixels
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width_bytes(self):
|
||||||
|
"""
|
||||||
|
Width of image if you use 8 pixels per byte and 0-pad at the end.
|
||||||
|
"""
|
||||||
|
return (self.width + 7) >> 3
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
"""
|
||||||
|
Height of image in pixels
|
||||||
|
"""
|
||||||
|
_, height_pixels = self._im.size
|
||||||
|
return height_pixels
|
||||||
|
|
||||||
|
def to_column_format(self, high_density_vertical=True):
|
||||||
|
"""
|
||||||
|
Extract slices of an image as equal-sized blobs of column-format data.
|
||||||
|
|
||||||
|
:param high_density_vertical: Printed line height in dots
|
||||||
|
"""
|
||||||
|
im = self._im.transpose(Image.ROTATE_270).transpose(Image.FLIP_LEFT_RIGHT)
|
||||||
|
line_height = 24 if high_density_vertical else 8
|
||||||
|
width_pixels, height_pixels = im.size
|
||||||
|
top = 0
|
||||||
|
left = 0
|
||||||
|
while left < width_pixels:
|
||||||
|
box = (left, top, left + line_height, top + height_pixels)
|
||||||
|
im_slice = im.transform((line_height, height_pixels), Image.EXTENT, box)
|
||||||
|
im_bytes = im_slice.tobytes()
|
||||||
|
yield(im_bytes)
|
||||||
|
left += line_height
|
||||||
|
|
||||||
|
def to_raster_format(self):
|
||||||
|
"""
|
||||||
|
Convert image to raster-format binary
|
||||||
|
"""
|
||||||
|
return self._im.tobytes()
|
304
src/escpos/printer.py
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
""" This module contains the implementations of abstract base class :py:class:`Escpos`.
|
||||||
|
|
||||||
|
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
|
||||||
|
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2012 Bashlinux
|
||||||
|
:license: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import usb.core
|
||||||
|
import usb.util
|
||||||
|
import serial
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from .escpos import Escpos
|
||||||
|
from .exceptions import USBNotFoundError
|
||||||
|
|
||||||
|
|
||||||
|
class Usb(Escpos):
|
||||||
|
""" USB printer
|
||||||
|
|
||||||
|
This class describes a printer that natively speaks USB.
|
||||||
|
|
||||||
|
inheritance:
|
||||||
|
|
||||||
|
.. inheritance-diagram:: escpos.printer.Usb
|
||||||
|
:parts: 1
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, idVendor, idProduct, timeout=0, in_ep=0x82, out_ep=0x01, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
:param idVendor: Vendor ID
|
||||||
|
:param idProduct: Product ID
|
||||||
|
: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.timeout = timeout
|
||||||
|
self.in_ep = in_ep
|
||||||
|
self.out_ep = out_ep
|
||||||
|
self.open()
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
""" Search device on USB tree and set it as escpos device """
|
||||||
|
self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct)
|
||||||
|
if self.device is None:
|
||||||
|
raise USBNotFoundError("Device not found or cable not plugged in.")
|
||||||
|
|
||||||
|
check_driver = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
check_driver = self.device.is_kernel_driver_active(0)
|
||||||
|
except NotImplementedError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if check_driver is None or check_driver:
|
||||||
|
try:
|
||||||
|
self.device.detach_kernel_driver(0)
|
||||||
|
except usb.core.USBError as e:
|
||||||
|
if check_driver is not None:
|
||||||
|
print("Could not detatch kernel driver: {0}".format(str(e)))
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.device.set_configuration()
|
||||||
|
self.device.reset()
|
||||||
|
except usb.core.USBError as e:
|
||||||
|
print("Could not set configuration: {0}".format(str(e)))
|
||||||
|
|
||||||
|
def _raw(self, msg):
|
||||||
|
""" Print any command sent in raw format
|
||||||
|
|
||||||
|
:param msg: arbitrary code to be printed
|
||||||
|
:type msg: bytes
|
||||||
|
"""
|
||||||
|
self.device.write(self.out_ep, msg, self.timeout)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
""" Release USB interface """
|
||||||
|
if self.device:
|
||||||
|
usb.util.dispose_resources(self.device)
|
||||||
|
self.device = None
|
||||||
|
|
||||||
|
|
||||||
|
class Serial(Escpos):
|
||||||
|
""" Serial printer
|
||||||
|
|
||||||
|
This class describes a printer that is connected by serial interface.
|
||||||
|
|
||||||
|
inheritance:
|
||||||
|
|
||||||
|
.. inheritance-diagram:: escpos.printer.Serial
|
||||||
|
:parts: 1
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1,
|
||||||
|
parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE,
|
||||||
|
xonxoff=False, dsrdtr=True, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param devfile: Device file under dev filesystem
|
||||||
|
:param baudrate: Baud rate for serial transmission
|
||||||
|
:param bytesize: Serial buffer size
|
||||||
|
:param timeout: Read/Write timeout
|
||||||
|
:param parity: Parity checking
|
||||||
|
:param stopbits: Number of stop bits
|
||||||
|
:param xonxoff: Software flow control
|
||||||
|
:param dsrdtr: Hardware flow control (False to enable RTS/CTS)
|
||||||
|
"""
|
||||||
|
Escpos.__init__(self, *args, **kwargs)
|
||||||
|
self.devfile = devfile
|
||||||
|
self.baudrate = baudrate
|
||||||
|
self.bytesize = bytesize
|
||||||
|
self.timeout = timeout
|
||||||
|
self.parity = parity
|
||||||
|
self.stopbits = stopbits
|
||||||
|
self.xonxoff = xonxoff
|
||||||
|
self.dsrdtr = dsrdtr
|
||||||
|
|
||||||
|
self.open()
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
""" Setup serial port and set is as escpos device """
|
||||||
|
self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate,
|
||||||
|
bytesize=self.bytesize, parity=self.parity,
|
||||||
|
stopbits=self.stopbits, timeout=self.timeout,
|
||||||
|
xonxoff=self.xonxoff, dsrdtr=self.dsrdtr)
|
||||||
|
|
||||||
|
if self.device is not None:
|
||||||
|
print("Serial printer enabled")
|
||||||
|
else:
|
||||||
|
print("Unable to open serial printer on: {0}".format(str(self.devfile)))
|
||||||
|
|
||||||
|
def _raw(self, msg):
|
||||||
|
""" Print any command sent in raw format
|
||||||
|
|
||||||
|
:param msg: arbitrary code to be printed
|
||||||
|
:type msg: bytes
|
||||||
|
"""
|
||||||
|
self.device.write(msg)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
""" Close Serial interface """
|
||||||
|
if self.device is not None:
|
||||||
|
self.device.flush()
|
||||||
|
self.device.close()
|
||||||
|
|
||||||
|
|
||||||
|
class Network(Escpos):
|
||||||
|
""" Network printer
|
||||||
|
|
||||||
|
This class is used to attach to a networked printer. You can also use this in order to attach to a printer that
|
||||||
|
is forwarded with ``socat``.
|
||||||
|
|
||||||
|
If you have a local printer on parallel port ``/dev/usb/lp0`` then you could start ``socat`` with:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
socat -u TCP4-LISTEN:4242,reuseaddr,fork OPEN:/dev/usb/lp0
|
||||||
|
|
||||||
|
Then you should be able to attach to port ``4242`` with this class.
|
||||||
|
Otherwise the normal usecase would be to have a printer with ethernet interface. This type of printer should
|
||||||
|
work the same with this class. For the address of the printer check its manuals.
|
||||||
|
|
||||||
|
inheritance:
|
||||||
|
|
||||||
|
.. inheritance-diagram:: escpos.printer.Network
|
||||||
|
:parts: 1
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, host, port=9100, timeout=60, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param host : Printer's hostname or IP address
|
||||||
|
:param port : Port to write to
|
||||||
|
:param timeout : timeout in seconds for the socket-library
|
||||||
|
"""
|
||||||
|
Escpos.__init__(self, *args, **kwargs)
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.timeout = timeout
|
||||||
|
self.open()
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
""" Open TCP socket with ``socket``-library and set it as escpos device """
|
||||||
|
self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.device.settimeout(self.timeout)
|
||||||
|
self.device.connect((self.host, self.port))
|
||||||
|
|
||||||
|
if self.device is None:
|
||||||
|
print("Could not open socket for {0}".format(self.host))
|
||||||
|
|
||||||
|
def _raw(self, msg):
|
||||||
|
""" Print any command sent in raw format
|
||||||
|
|
||||||
|
:param msg: arbitrary code to be printed
|
||||||
|
:type msg: bytes
|
||||||
|
"""
|
||||||
|
self.device.sendall(msg)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
""" Close TCP connection """
|
||||||
|
self.device.shutdown(socket.SHUT_RDWR)
|
||||||
|
self.device.close()
|
||||||
|
|
||||||
|
|
||||||
|
class File(Escpos):
|
||||||
|
""" Generic file printer
|
||||||
|
|
||||||
|
This class is used for parallel port printer or other printers that are directly attached to the filesystem.
|
||||||
|
Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable
|
||||||
|
and produce arbitrary errors.
|
||||||
|
|
||||||
|
inheritance:
|
||||||
|
|
||||||
|
.. inheritance-diagram:: escpos.printer.File
|
||||||
|
:parts: 1
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
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):
|
||||||
|
""" Open system file """
|
||||||
|
self.device = open(self.devfile, "wb")
|
||||||
|
|
||||||
|
if self.device is None:
|
||||||
|
print("Could not open the specified file {0}".format(self.devfile))
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
""" Flush printing content """
|
||||||
|
self.device.flush()
|
||||||
|
|
||||||
|
def _raw(self, msg):
|
||||||
|
""" Print any command sent in raw format
|
||||||
|
|
||||||
|
:param msg: arbitrary code to be printed
|
||||||
|
:type msg: bytes
|
||||||
|
"""
|
||||||
|
self.device.write(msg)
|
||||||
|
if self.auto_flush:
|
||||||
|
self.flush()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
""" Close system file """
|
||||||
|
self.device.flush()
|
||||||
|
self.device.close()
|
||||||
|
|
||||||
|
|
||||||
|
class Dummy(Escpos):
|
||||||
|
""" Dummy printer
|
||||||
|
|
||||||
|
This class is used for saving commands to a variable, for use in situations where
|
||||||
|
there is no need to send commands to an actual printer. This includes
|
||||||
|
generating print jobs for later use, or testing output.
|
||||||
|
|
||||||
|
inheritance:
|
||||||
|
|
||||||
|
.. inheritance-diagram:: escpos.printer.Dummy
|
||||||
|
:parts: 1
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
Escpos.__init__(self, *args, **kwargs)
|
||||||
|
self._output_list = []
|
||||||
|
|
||||||
|
def _raw(self, msg):
|
||||||
|
""" Print any command sent in raw format
|
||||||
|
|
||||||
|
:param msg: arbitrary code to be printed
|
||||||
|
:type msg: bytes
|
||||||
|
"""
|
||||||
|
self._output_list.append(msg)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def output(self):
|
||||||
|
""" Get the data that was sent to this printer """
|
||||||
|
return b''.join(self._output_list)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
1
test/Dies ist ein Test.LF.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Dies ist ein Test.
|
BIN
test/resources/black_transparent.gif
Normal file
After Width: | Height: | Size: 65 B |
BIN
test/resources/black_transparent.png
Normal file
After Width: | Height: | Size: 167 B |
BIN
test/resources/black_white.gif
Normal file
After Width: | Height: | Size: 65 B |
BIN
test/resources/black_white.jpg
Normal file
After Width: | Height: | Size: 175 B |
BIN
test/resources/black_white.png
Normal file
After Width: | Height: | Size: 156 B |
BIN
test/resources/canvas_black.gif
Normal file
After Width: | Height: | Size: 72 B |
BIN
test/resources/canvas_black.jpg
Normal file
After Width: | Height: | Size: 160 B |
BIN
test/resources/canvas_black.png
Normal file
After Width: | Height: | Size: 239 B |
BIN
test/resources/canvas_white.gif
Normal file
After Width: | Height: | Size: 72 B |
BIN
test/resources/canvas_white.jpg
Normal file
After Width: | Height: | Size: 160 B |
BIN
test/resources/canvas_white.png
Normal file
After Width: | Height: | Size: 239 B |
30
test/test_abstract_base_class.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
"""verifies that the metaclass abc is properly used by Escpos
|
||||||
|
|
||||||
|
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
|
||||||
|
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2016 Patrick Kanzler
|
||||||
|
:license: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from nose.tools import raises
|
||||||
|
|
||||||
|
import escpos.escpos as escpos
|
||||||
|
from abc import ABCMeta
|
||||||
|
|
||||||
|
|
||||||
|
@raises(TypeError)
|
||||||
|
def test_abstract_base_class_raises():
|
||||||
|
"""test whether the abstract base class raises an exception for Escpos"""
|
||||||
|
escpos.Escpos() # This call should raise TypeError because of abstractmethod _raw()
|
||||||
|
|
||||||
|
|
||||||
|
def test_abstract_base_class():
|
||||||
|
""" test whether Escpos has the metaclass ABCMeta """
|
||||||
|
assert issubclass(escpos.Escpos, object)
|
||||||
|
assert type(escpos.Escpos) is ABCMeta
|
122
test/test_cli.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
"""Test for the CLI
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
DEVFILE_NAME = 'testfile'
|
||||||
|
|
||||||
|
DEVFILE = os.path.join(TEST_DIR, DEVFILE_NAME)
|
||||||
|
CONFIGFILE = 'testconfig.yaml'
|
||||||
|
CONFIG_YAML = '''
|
||||||
|
---
|
||||||
|
|
||||||
|
printer:
|
||||||
|
type: file
|
||||||
|
devfile: {testfile}
|
||||||
|
'''.format(
|
||||||
|
testfile=DEVFILE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCLI():
|
||||||
|
""" Contains setups, teardowns, and tests for CLI
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
""" Initalize the tests.
|
||||||
|
Just define some vars here since most of them get set during
|
||||||
|
setup_class and teardown_class
|
||||||
|
"""
|
||||||
|
self.env = None
|
||||||
|
self.default_args = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def setup_class():
|
||||||
|
""" Create a config file to read from """
|
||||||
|
with open(CONFIGFILE, 'w') as config:
|
||||||
|
config.write(CONFIG_YAML)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def teardown_class():
|
||||||
|
""" Remove config file """
|
||||||
|
os.remove(CONFIGFILE)
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
""" Create a file to print to and set up env"""
|
||||||
|
self.env = TestFileEnvironment(
|
||||||
|
base_path=TEST_DIR,
|
||||||
|
cwd=os.getcwd(),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.default_args = (
|
||||||
|
'python-escpos',
|
||||||
|
'-c',
|
||||||
|
CONFIGFILE,
|
||||||
|
)
|
||||||
|
|
||||||
|
fhandle = open(DEVFILE, 'a')
|
||||||
|
try:
|
||||||
|
os.utime(DEVFILE, None)
|
||||||
|
finally:
|
||||||
|
fhandle.close()
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
""" Destroy printer file and env """
|
||||||
|
os.remove(DEVFILE)
|
||||||
|
self.env.clear()
|
||||||
|
|
||||||
|
def test_cli_help(self):
|
||||||
|
""" Test getting help from cli """
|
||||||
|
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'
|
||||||
|
result = self.env.run(
|
||||||
|
*(self.default_args + (
|
||||||
|
'text',
|
||||||
|
'--txt',
|
||||||
|
test_text,
|
||||||
|
))
|
||||||
|
)
|
||||||
|
assert not result.stderr
|
||||||
|
assert DEVFILE_NAME in result.files_updated.keys()
|
||||||
|
assert_equals(
|
||||||
|
result.files_updated[DEVFILE_NAME].bytes,
|
||||||
|
test_text + '\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_cli_text_inavlid_args(self):
|
||||||
|
""" Test a failure to send valid arguments """
|
||||||
|
result = self.env.run(
|
||||||
|
*(self.default_args + (
|
||||||
|
'text',
|
||||||
|
'--invalid-param',
|
||||||
|
'some data'
|
||||||
|
)),
|
||||||
|
expect_error=True,
|
||||||
|
expect_stderr=True
|
||||||
|
)
|
||||||
|
assert_equals(result.returncode, 2)
|
||||||
|
assert 'error:' in result.stderr
|
||||||
|
assert not result.files_updated
|
132
test/test_function_image.py
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
""" Image function tests- Check that image print commands are sent correctly.
|
||||||
|
|
||||||
|
:author: `Michael Billington <michael.billington@gmail.com>`_
|
||||||
|
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_
|
||||||
|
:license: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
# Raster format print
|
||||||
|
def test_bit_image_black():
|
||||||
|
"""
|
||||||
|
Test printing solid black bit image (raster)
|
||||||
|
"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.image('test/resources/canvas_black.png', impl="bitImageRaster")
|
||||||
|
assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\x80')
|
||||||
|
# Same thing w/ object created on the fly, rather than a filename
|
||||||
|
instance = printer.Dummy()
|
||||||
|
im = Image.new("RGB", (1, 1), (0, 0, 0))
|
||||||
|
instance.image(im, impl="bitImageRaster")
|
||||||
|
assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\x80')
|
||||||
|
|
||||||
|
|
||||||
|
def test_bit_image_white():
|
||||||
|
"""
|
||||||
|
Test printing solid white bit image (raster)
|
||||||
|
"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.image('test/resources/canvas_white.png', impl="bitImageRaster")
|
||||||
|
assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\x00')
|
||||||
|
|
||||||
|
|
||||||
|
def test_bit_image_both():
|
||||||
|
"""
|
||||||
|
Test printing black/white bit image (raster)
|
||||||
|
"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.image('test/resources/black_white.png', impl="bitImageRaster")
|
||||||
|
assert(instance.output == b'\x1dv0\x00\x01\x00\x02\x00\xc0\x00')
|
||||||
|
|
||||||
|
|
||||||
|
def test_bit_image_transparent():
|
||||||
|
"""
|
||||||
|
Test printing black/transparent bit image (raster)
|
||||||
|
"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.image('test/resources/black_transparent.png', impl="bitImageRaster")
|
||||||
|
assert(instance.output == b'\x1dv0\x00\x01\x00\x02\x00\xc0\x00')
|
||||||
|
|
||||||
|
|
||||||
|
# Column format print
|
||||||
|
def test_bit_image_colfmt_black():
|
||||||
|
"""
|
||||||
|
Test printing solid black bit image (column format)
|
||||||
|
"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.image('test/resources/canvas_black.png', impl="bitImageColumn")
|
||||||
|
assert(instance.output == b'\x1b3\x10\x1b*!\x01\x00\x80\x00\x00\x0a\x1b2')
|
||||||
|
|
||||||
|
|
||||||
|
def test_bit_image_colfmt_white():
|
||||||
|
"""
|
||||||
|
Test printing solid white bit image (column format)
|
||||||
|
"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.image('test/resources/canvas_white.png', impl="bitImageColumn")
|
||||||
|
assert(instance.output == b'\x1b3\x10\x1b*!\x01\x00\x00\x00\x00\x0a\x1b2')
|
||||||
|
|
||||||
|
|
||||||
|
def test_bit_image_colfmt_both():
|
||||||
|
"""
|
||||||
|
Test printing black/white bit image (column format)
|
||||||
|
"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.image('test/resources/black_white.png', impl="bitImageColumn")
|
||||||
|
assert(instance.output == b'\x1b3\x10\x1b*!\x02\x00\x80\x00\x00\x80\x00\x00\x0a\x1b2')
|
||||||
|
|
||||||
|
|
||||||
|
def test_bit_image_colfmt_transparent():
|
||||||
|
"""
|
||||||
|
Test printing black/transparent bit image (column format)
|
||||||
|
"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.image('test/resources/black_transparent.png', impl="bitImageColumn")
|
||||||
|
assert(instance.output == b'\x1b3\x10\x1b*!\x02\x00\x80\x00\x00\x80\x00\x00\x0a\x1b2')
|
||||||
|
|
||||||
|
|
||||||
|
# Graphics print
|
||||||
|
def test_graphics_black():
|
||||||
|
"""
|
||||||
|
Test printing solid black graphics
|
||||||
|
"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.image('test/resources/canvas_black.png', impl="graphics")
|
||||||
|
assert(instance.output == b'\x1d(L\x0b\x000p0\x01\x011\x01\x00\x01\x00\x80\x1d(L\x02\x0002')
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphics_white():
|
||||||
|
"""
|
||||||
|
Test printing solid white graphics
|
||||||
|
"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.image('test/resources/canvas_white.png', impl="graphics")
|
||||||
|
assert(instance.output == b'\x1d(L\x0b\x000p0\x01\x011\x01\x00\x01\x00\x00\x1d(L\x02\x0002')
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphics_both():
|
||||||
|
"""
|
||||||
|
Test printing black/white graphics
|
||||||
|
"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.image('test/resources/black_white.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_graphics_transparent():
|
||||||
|
"""
|
||||||
|
Test printing black/transparent graphics
|
||||||
|
"""
|
||||||
|
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')
|
54
test/test_function_panel_button.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
"""tests for panel button function
|
||||||
|
|
||||||
|
: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
|
||||||
|
|
||||||
|
from nose.tools import with_setup
|
||||||
|
|
||||||
|
import escpos.printer as printer
|
||||||
|
import os
|
||||||
|
|
||||||
|
devfile = 'testfile'
|
||||||
|
|
||||||
|
|
||||||
|
def setup_testfile():
|
||||||
|
"""create a testfile as devfile"""
|
||||||
|
fhandle = open(devfile, 'a')
|
||||||
|
try:
|
||||||
|
os.utime(devfile, None)
|
||||||
|
finally:
|
||||||
|
fhandle.close()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_testfile():
|
||||||
|
"""destroy testfile again"""
|
||||||
|
os.remove(devfile)
|
||||||
|
|
||||||
|
|
||||||
|
@with_setup(setup_testfile, teardown_testfile)
|
||||||
|
def test_function_panel_button_on():
|
||||||
|
"""test the panel button function (enabling) by comparing output"""
|
||||||
|
instance = printer.File(devfile=devfile)
|
||||||
|
instance.panel_buttons()
|
||||||
|
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():
|
||||||
|
"""test the panel button function (disabling) by comparing output"""
|
||||||
|
instance = printer.File(devfile=devfile)
|
||||||
|
instance.panel_buttons(False)
|
||||||
|
instance.flush()
|
||||||
|
with open(devfile, "rb") as f:
|
||||||
|
assert(f.read() == b'\x1B\x63\x35\x01')
|
99
test/test_function_qr_native.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
"""test native QR code printing
|
||||||
|
|
||||||
|
:author: `Michael Billington <michael.billington@gmail.com>`_
|
||||||
|
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_
|
||||||
|
:license: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from nose.tools import raises
|
||||||
|
import escpos.printer as printer
|
||||||
|
from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1
|
||||||
|
|
||||||
|
|
||||||
|
def test_defaults():
|
||||||
|
"""Test QR code with defaults"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.qr("1234", native=True)
|
||||||
|
expected = b'\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d' \
|
||||||
|
b'(k\x07\x001P01234\x1d(k\x03\x001Q0'
|
||||||
|
assert(instance.output == expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty():
|
||||||
|
"""Test QR printing blank code"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.qr("", native=True)
|
||||||
|
assert(instance.output == b'')
|
||||||
|
|
||||||
|
|
||||||
|
def test_ec():
|
||||||
|
"""Test QR error correction setting"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.qr("1234", native=True, ec=QR_ECLEVEL_H)
|
||||||
|
expected = b'\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E3\x1d' \
|
||||||
|
b'(k\x07\x001P01234\x1d(k\x03\x001Q0'
|
||||||
|
assert(instance.output == expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_size():
|
||||||
|
"""Test QR box size"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.qr("1234", native=True, size=7)
|
||||||
|
expected = b'\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x07\x1d(k\x03\x001E0\x1d' \
|
||||||
|
b'(k\x07\x001P01234\x1d(k\x03\x001Q0'
|
||||||
|
assert(instance.output == expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_model():
|
||||||
|
"""Test QR model"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.qr("1234", native=True, model=QR_MODEL_1)
|
||||||
|
expected = b'\x1d(k\x04\x001A1\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d' \
|
||||||
|
b'(k\x07\x001P01234\x1d(k\x03\x001Q0'
|
||||||
|
assert(instance.output == expected)
|
||||||
|
|
||||||
|
|
||||||
|
@raises(ValueError)
|
||||||
|
def test_invalid_ec():
|
||||||
|
"""Test invalid QR error correction"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.qr("1234", native=True, ec=-1)
|
||||||
|
|
||||||
|
|
||||||
|
@raises(ValueError)
|
||||||
|
def test_invalid_size():
|
||||||
|
"""Test invalid QR size"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.qr("1234", native=True, size=0)
|
||||||
|
|
||||||
|
|
||||||
|
@raises(ValueError)
|
||||||
|
def test_invalid_model():
|
||||||
|
"""Test invalid QR model"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.qr("1234", native=True, model="Hello")
|
||||||
|
|
||||||
|
|
||||||
|
def test_image():
|
||||||
|
"""Test QR as image"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.qr("1", native=False, size=1)
|
||||||
|
print(instance.output)
|
||||||
|
expected = 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'i(\x7f<\xa8A \xd8]\'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00'
|
||||||
|
assert(instance.output == expected)
|
||||||
|
|
||||||
|
|
||||||
|
@raises(ValueError)
|
||||||
|
def test_image_invalid_model():
|
||||||
|
"""Test unsupported QR model as image"""
|
||||||
|
instance = printer.Dummy()
|
||||||
|
instance.qr("1234", native=False, model=QR_MODEL_1)
|
45
test/test_function_text.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
"""tests for the text printing function
|
||||||
|
|
||||||
|
: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
|
||||||
|
|
||||||
|
from nose.tools import with_setup
|
||||||
|
|
||||||
|
import escpos.printer as printer
|
||||||
|
import os
|
||||||
|
|
||||||
|
import filecmp
|
||||||
|
|
||||||
|
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_text_dies_ist_ein_test_lf():
|
||||||
|
"""test the text printing function with simple string and compare output"""
|
||||||
|
instance = printer.File(devfile=devfile)
|
||||||
|
instance.text('Dies ist ein Test.\n')
|
||||||
|
instance.flush()
|
||||||
|
assert(filecmp.cmp('test/Dies ist ein Test.LF.txt', devfile))
|
57
test/test_image.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
""" Image tests- Check that images from different source formats are correctly
|
||||||
|
converted to ESC/POS column & raster formats.
|
||||||
|
|
||||||
|
:author: `Michael Billington <michael.billington@gmail.com>`_
|
||||||
|
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_
|
||||||
|
:license: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
from escpos.image import EscposImage
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_black():
|
||||||
|
"""
|
||||||
|
Test rendering solid black image
|
||||||
|
"""
|
||||||
|
for img_format in ['png', 'jpg', 'gif']:
|
||||||
|
_load_and_check_img('canvas_black.' + img_format, 1, 1, b'\x80', [b'\x80'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_black_transparent():
|
||||||
|
"""
|
||||||
|
Test rendering black/transparent image
|
||||||
|
"""
|
||||||
|
for img_format in ['png', 'gif']:
|
||||||
|
_load_and_check_img('black_transparent.' + img_format, 2, 2, b'\xc0\x00', [b'\x80\x80'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_black_white():
|
||||||
|
"""
|
||||||
|
Test rendering black/white image
|
||||||
|
"""
|
||||||
|
for img_format in ['png', 'jpg', 'gif']:
|
||||||
|
_load_and_check_img('black_white.' + img_format, 2, 2, b'\xc0\x00', [b'\x80\x80'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_white():
|
||||||
|
"""
|
||||||
|
Test rendering solid white image
|
||||||
|
"""
|
||||||
|
for img_format in ['png', 'jpg', 'gif']:
|
||||||
|
_load_and_check_img('canvas_white.' + img_format, 1, 1, b'\x00', [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.
|
||||||
|
"""
|
||||||
|
im = EscposImage('test/resources/' + filename)
|
||||||
|
assert(im.width == width_expected)
|
||||||
|
assert(im.height == height_expected)
|
||||||
|
assert(im.to_raster_format() == raster_format_expected)
|
||||||
|
i = 0
|
||||||
|
for row in im.to_column_format(False):
|
||||||
|
assert(row == column_format_expected[i])
|
||||||
|
i += 1
|
41
test/test_load_module.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
"""very basic test cases that load the classes
|
||||||
|
|
||||||
|
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
|
||||||
|
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:license: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from nose.tools import with_setup
|
||||||
|
|
||||||
|
import escpos.printer as printer
|
||||||
|
import os
|
||||||
|
|
||||||
|
devfile = 'testfile'
|
||||||
|
|
||||||
|
|
||||||
|
def setup_testfile():
|
||||||
|
"""create a testfile as devfile"""
|
||||||
|
fhandle = open(devfile, 'a')
|
||||||
|
try:
|
||||||
|
os.utime(devfile, None)
|
||||||
|
finally:
|
||||||
|
fhandle.close()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_testfile():
|
||||||
|
"""destroy testfile again"""
|
||||||
|
os.remove(devfile)
|
||||||
|
|
||||||
|
|
||||||
|
@with_setup(setup_testfile, teardown_testfile)
|
||||||
|
def test_instantiation():
|
||||||
|
"""test the instantiation of a escpos-printer class and basic printing"""
|
||||||
|
instance = printer.File(devfile=devfile)
|
||||||
|
instance.text('This is a test\n')
|
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
|
24
test/test_with_statement.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
"""test the facility which enables usage of the with-statement
|
||||||
|
|
||||||
|
: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 escpos.printer as printer
|
||||||
|
import escpos.escpos as escpos
|
||||||
|
|
||||||
|
|
||||||
|
def test_with_statement():
|
||||||
|
"""Use with statement"""
|
||||||
|
dummy_printer = printer.Dummy()
|
||||||
|
with escpos.EscposIO(dummy_printer) as p:
|
||||||
|
p.writelines('Some text.\n')
|
||||||
|
# TODO extend these tests as they don't really do anything at the moment
|
17
tox.ini
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[tox]
|
||||||
|
envlist = py27, py34, py35, docs
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
deps = nose
|
||||||
|
coverage
|
||||||
|
scripttest
|
||||||
|
mock
|
||||||
|
hypothesis
|
||||||
|
commands = nosetests --with-coverage --cover-erase --cover-branches
|
||||||
|
|
||||||
|
[testenv:docs]
|
||||||
|
basepython = python
|
||||||
|
changedir = doc
|
||||||
|
deps = sphinx
|
||||||
|
setuptools_scm
|
||||||
|
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
|