Compare commits
59 Commits
appveyor
...
update-fla
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4d479337d3 | ||
![]() |
87b33367c0 | ||
![]() |
594ab83fc3 | ||
![]() |
9b8e56cd59 | ||
![]() |
c0af9aaaf4 | ||
![]() |
18e4c1b0ac | ||
![]() |
0051c876bf | ||
![]() |
854759d312 | ||
![]() |
a0343c66af | ||
![]() |
6c94f88c24 | ||
![]() |
6fb23d6826 | ||
![]() |
f649814091 | ||
![]() |
47b4d41b28 | ||
![]() |
599f4f3ca5 | ||
![]() |
d085e5c467 | ||
![]() |
b418302311 | ||
![]() |
f6acb72bbe | ||
![]() |
0c9856c1f6 | ||
![]() |
a748563395 | ||
![]() |
b84e280efb | ||
![]() |
4390dc4a9c | ||
![]() |
6e09fd1e97 | ||
![]() |
100c6b5e89 | ||
![]() |
26d72a69f0 | ||
![]() |
01e28bbcf6 | ||
![]() |
2a7e2a6a36 | ||
![]() |
3c3dab95f5 | ||
![]() |
d1e7052fa1 | ||
![]() |
10e1dfe1d1 | ||
![]() |
cd1bcb57b4 | ||
![]() |
d6d12f99d4 | ||
![]() |
128221363f | ||
![]() |
6b0b1371e5 | ||
![]() |
44f01a212b | ||
![]() |
456f5b7aa6 | ||
![]() |
d78a6f1699 | ||
![]() |
5e784c060a | ||
![]() |
1439b14686 | ||
![]() |
b648cfd67f | ||
![]() |
50c627fbb0 | ||
![]() |
99034d0575 | ||
![]() |
19663ec574 | ||
![]() |
281eea125f | ||
![]() |
5bed0bfbb4 | ||
![]() |
f12470d3cd | ||
![]() |
fb0e4c28ba | ||
![]() |
af29fcca77 | ||
![]() |
f8b269d859 | ||
![]() |
c259263f26 | ||
![]() |
27c843935f | ||
![]() |
f3da6a9725 | ||
![]() |
b64b534394 | ||
![]() |
81426ab6dc | ||
![]() |
df1193ab35 | ||
![]() |
b494c9a4bd | ||
![]() |
f8a2174108 | ||
![]() |
1f57b04974 | ||
![]() |
c7080165a7 | ||
![]() |
cf0cf127fe |
1
.gitignore
vendored
@@ -20,6 +20,7 @@ dist/
|
||||
.coverage
|
||||
src/escpos/version.py
|
||||
.hypothesis
|
||||
.pytest_cache/
|
||||
|
||||
# testing temporary directories
|
||||
test/test-cli-output/
|
||||
|
1
.gitmodules
vendored
@@ -1,3 +1,4 @@
|
||||
[submodule "capabilities-data"]
|
||||
path = capabilities-data
|
||||
url = https://github.com/receipt-print-hq/escpos-printer-db.git
|
||||
branch = master
|
||||
|
5
.mailmap
@@ -8,4 +8,7 @@ Cody (Quantified Code Bot) <cody@quantifiedcode.com> Cody <cody@quantifiedcode.c
|
||||
Renato Lorenzi <renato.lorenzi@senior.com.br> Renato.Lorenzi <renato.lorenzi@senior.com.br>
|
||||
Ahmed Tahri <nyuubi.10@gmail.com> TAHRI Ahmed <nyuubi.10@gmail.com>
|
||||
Michael Elsdörfer <michael@elsdoerfer.com> Michael Elsdörfer <michael@elsdoerfer.info>
|
||||
csoft2k <csoft2k@hotmail.com>
|
||||
Juanmi Taboada <juanmi@juanmitaboada.com> Juanmi Taboada <juanmi@juanmitaboada.com>
|
||||
csoft2k <csoft2k@hotmail.com>
|
||||
Sergio Pulgarin <sergio.pulgarin@gmail.com>
|
||||
reck31 <rakesh.gunduka@gmail.com>
|
||||
|
10
.travis.yml
@@ -7,12 +7,13 @@ addons:
|
||||
apt:
|
||||
packages:
|
||||
- graphviz
|
||||
env:
|
||||
global:
|
||||
- ESCPOS_CAPABILITIES_FILE=/home/travis/build/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
|
||||
matrix:
|
||||
include:
|
||||
- python: 2.7
|
||||
env: TOXENV=py27
|
||||
- python: 3.3
|
||||
env: TOXENV=py33
|
||||
- python: 3.4
|
||||
env: TOXENV=py34
|
||||
- python: 3.5
|
||||
@@ -21,6 +22,8 @@ matrix:
|
||||
env: TOXENV=py36
|
||||
- python: 3.6-dev
|
||||
env: TOXENV=py36
|
||||
- python: 3.7-dev
|
||||
env: TOXENV=py37
|
||||
- python: nightly
|
||||
env: TOXENV=py37
|
||||
- python: pypy
|
||||
@@ -35,6 +38,7 @@ matrix:
|
||||
env: TOXENV=flake8
|
||||
allow_failures:
|
||||
- python: 3.6-dev
|
||||
- python: 3.7-dev
|
||||
- python: nightly
|
||||
- python: pypy3
|
||||
before_install:
|
||||
@@ -59,4 +63,4 @@ deploy:
|
||||
tags: true
|
||||
repo: python-escpos/python-escpos
|
||||
branch: master
|
||||
condition: $TRAVIS_PYTHON_VERSION = "3.5"
|
||||
condition: $TRAVIS_PYTHON_VERSION = "3.6"
|
||||
|
6
AUTHORS
@@ -10,17 +10,23 @@ Dean Rispin
|
||||
Dmytro Katyukha
|
||||
Hark
|
||||
Joel Lehtonen
|
||||
kennedy
|
||||
Kristi
|
||||
ldos
|
||||
Lucy Linder
|
||||
Manuel F Martinez
|
||||
Michael Billington
|
||||
Michael Elsdörfer
|
||||
mrwunderbar666
|
||||
Nathan Bookham
|
||||
Patrick Kanzler
|
||||
primax79
|
||||
Qian Linfeng
|
||||
reck31
|
||||
Renato Lorenzi
|
||||
Romain Porte
|
||||
Sam Cheng
|
||||
Sergio Pulgarin
|
||||
Stephan Sokolow
|
||||
Thijs Triemstra
|
||||
Thomas van den Berg
|
||||
|
@@ -1,6 +1,79 @@
|
||||
*********
|
||||
Changelog
|
||||
*********
|
||||
2018-05-15 - Version 3.0a4 - "Kakistocrat"
|
||||
------------------------------------------
|
||||
This release is the fifth alpha release of the new version 3.0. Please
|
||||
be aware that the API will still change until v3.0 is released.
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- raise exception when TypeError occurs in cashdraw (#268)
|
||||
- Feature/clear content in dummy printer (#271)
|
||||
- fix is_online() (#282)
|
||||
- improve documentation
|
||||
- Modified submodule to always pull from master branch (#283)
|
||||
- parameter for implementation of nonnative qrcode (#289)
|
||||
- improve platform independence (#296)
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- Christoph Heuel
|
||||
- Patrick Kanzler
|
||||
- kennedy
|
||||
- primax79
|
||||
- reck31
|
||||
- Thijs Triemstra
|
||||
|
||||
2017-10-08 - Version 3.0a3 - "Just Testing"
|
||||
-------------------------------------------
|
||||
This release is the fourth alpha release of the new version 3.0. Please
|
||||
be aware that the API will still change until v3.0 is released.
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- minor changes in documentation, tests and examples
|
||||
- pickle capabilities for faster startup
|
||||
- first implementation of centering images and QR
|
||||
- check barcodes based on regex
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- Patrick Kanzler
|
||||
- Lucy Linder
|
||||
- Romain Porte
|
||||
- Sergio Pulgarin
|
||||
|
||||
2017-08-04 - Version 3.0a2 - "It's My Party And I'll Sing If I Want To"
|
||||
-----------------------------------------------------------------------
|
||||
This release is the third alpha release of the new version 3.0. Please
|
||||
be aware that the API will still change until v3.0 is released.
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- refactor of the set-method
|
||||
- preliminary support of POS "line display" printing
|
||||
- improvement of tests
|
||||
- added ImageWidthError
|
||||
- list authors in repository
|
||||
- add support for software-based barcode-rendering
|
||||
- fix SerialException when trying to close device on __del__
|
||||
- added the DLE EOT querying command for USB and Serial
|
||||
- ensure QR codes have a large enough border
|
||||
- make feed for cut optional
|
||||
- fix the behavior of horizontal tabs
|
||||
- added test script for hard an soft barcodes
|
||||
- implemented paper sensor querying command
|
||||
- added weather forecast example script
|
||||
- added a method for simpler newlines
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- csoft2k
|
||||
- Patrick Kanzler
|
||||
- mrwunderbar666
|
||||
- Romain Porte
|
||||
- Ahmed Tahri
|
||||
|
||||
2017-03-29 - Version 3.0a1 - "Headcrash"
|
||||
----------------------------------------
|
||||
|
@@ -20,6 +20,7 @@ When contributing the first time, please include a commit with the output of thi
|
||||
Otherwise the integration-check will fail.
|
||||
|
||||
When you change your username or mail-address, please also update the `.mailmap` and the authors-list.
|
||||
You can find a good documentation on the mapping-feature in the `documentation of git-shortlog <https://git-scm.com/docs/git-shortlog#_mapping_authors>`_.
|
||||
|
||||
Style-Guide
|
||||
-----------
|
||||
@@ -47,9 +48,11 @@ Often you can achieve compatibility quite easily with a tool from the `six`-pack
|
||||
|
||||
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.
|
||||
The entire codebase adheres to the rules of PEP8.
|
||||
These rules are enforced by running `flake8` in the integration-checks.
|
||||
Please adhere to these rules as your contribution can only be merged if the check succeeds.
|
||||
You can use flake8 or similar tools locally in order to check your code.
|
||||
Apart from that the travis-log and the check by Landscape will provide you with hints.
|
||||
|
||||
GIT
|
||||
^^^
|
||||
|
21
README.rst
@@ -6,10 +6,6 @@ python-escpos - Python library to manipulate ESC/POS Printers
|
||||
: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
|
||||
@@ -63,9 +59,9 @@ The basic usage is:
|
||||
from escpos.printer import Usb
|
||||
|
||||
""" Seiko Epson Corp. Receipt Printer (EPSON TM-T88III) """
|
||||
p = Usb(0x04b8, 0x0202, 0, profile="TM-T88III")
|
||||
p.text("Hello World\n")
|
||||
p.image("logo.gif")
|
||||
p = Usb(0x04b8, 0x0202, 0, profile='TM-T88III')
|
||||
p.text('Hello World\n')
|
||||
p.image('logo.gif')
|
||||
p.barcode('1324354657687', 'EAN13', 64, 2, '', '')
|
||||
p.cut()
|
||||
|
||||
@@ -74,4 +70,13 @@ The full project-documentation is available on `Read the Docs <https://python-es
|
||||
Contributing
|
||||
------------
|
||||
|
||||
This project is open for any contribution! Please see CONTRIBUTING.rst for more information.
|
||||
This project is open for any contribution! Please see `CONTRIBUTING.rst <http://python-escpos.readthedocs.io/en/latest/dev/contributing.html>`_ for more information.
|
||||
|
||||
|
||||
Disclaimer
|
||||
----------
|
||||
|
||||
None of the vendors cited in this project agree or endorse any of the patterns or implementations.
|
||||
Its names are used only to maintain context.
|
||||
|
||||
|
||||
|
125
appveyor.yml
@@ -1,125 +0,0 @@
|
||||
environment:
|
||||
global:
|
||||
# SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
|
||||
# /E:ON and /V:ON options are not enabled in the batch script intepreter
|
||||
# See: http://stackoverflow.com/a/13751649/163740
|
||||
CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd"
|
||||
|
||||
matrix:
|
||||
|
||||
# Python 2.7.10 is the latest version and is not pre-installed.
|
||||
|
||||
- PYTHON: "C:\\Python27.10"
|
||||
PYTHON_VERSION: "2.7.10"
|
||||
PYTHON_ARCH: "32"
|
||||
|
||||
- PYTHON: "C:\\Python27.10-x64"
|
||||
PYTHON_VERSION: "2.7.10"
|
||||
PYTHON_ARCH: "64"
|
||||
|
||||
# Pre-installed Python versions, which Appveyor may upgrade to
|
||||
# a later point release.
|
||||
# See: http://www.appveyor.com/docs/installed-software#python
|
||||
|
||||
- PYTHON: "C:\\Python27"
|
||||
PYTHON_VERSION: "2.7.x" # currently 2.7.9
|
||||
PYTHON_ARCH: "32"
|
||||
|
||||
- PYTHON: "C:\\Python27-x64"
|
||||
PYTHON_VERSION: "2.7.x" # currently 2.7.9
|
||||
PYTHON_ARCH: "64"
|
||||
|
||||
- PYTHON: "C:\\Python33"
|
||||
PYTHON_VERSION: "3.3.x" # currently 3.3.5
|
||||
PYTHON_ARCH: "32"
|
||||
|
||||
- PYTHON: "C:\\Python33-x64"
|
||||
PYTHON_VERSION: "3.3.x" # currently 3.3.5
|
||||
PYTHON_ARCH: "64"
|
||||
|
||||
- PYTHON: "C:\\Python34"
|
||||
PYTHON_VERSION: "3.4.x" # currently 3.4.3
|
||||
PYTHON_ARCH: "32"
|
||||
|
||||
- PYTHON: "C:\\Python34-x64"
|
||||
PYTHON_VERSION: "3.4.x" # currently 3.4.3
|
||||
PYTHON_ARCH: "64"
|
||||
|
||||
# Python versions not pre-installed
|
||||
|
||||
- PYTHON: "C:\\Python35"
|
||||
PYTHON_VERSION: "3.5.0"
|
||||
PYTHON_ARCH: "32"
|
||||
|
||||
- PYTHON: "C:\\Python35-x64"
|
||||
PYTHON_VERSION: "3.5.0"
|
||||
PYTHON_ARCH: "64"
|
||||
|
||||
# Major and minor releases (i.e x.0.0 and x.y.0) prior to 3.3.0 use
|
||||
# a different naming scheme.
|
||||
|
||||
- PYTHON: "C:\\Python270"
|
||||
PYTHON_VERSION: "2.7.0"
|
||||
PYTHON_ARCH: "32"
|
||||
|
||||
- PYTHON: "C:\\Python270-x64"
|
||||
PYTHON_VERSION: "2.7.0"
|
||||
PYTHON_ARCH: "64"
|
||||
|
||||
install:
|
||||
# If there is a newer build queued for the same PR, cancel this one.
|
||||
# The AppVeyor 'rollout builds' option is supposed to serve the same
|
||||
# purpose but it is problematic because it tends to cancel builds pushed
|
||||
# directly to master instead of just PR builds (or the converse).
|
||||
# credits: JuliaLang developers.
|
||||
- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
|
||||
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
|
||||
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
|
||||
throw "There are newer queued builds for this pull request, failing early." }
|
||||
- ECHO "Filesystem root:"
|
||||
- ps: "ls \"C:/\""
|
||||
|
||||
- ECHO "Installed SDKs:"
|
||||
- ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\""
|
||||
|
||||
# Install Python (from the official .msi of http://python.org) and pip when
|
||||
# not already installed.
|
||||
- ps: if (-not(Test-Path($env:PYTHON))) { & appveyor\install.ps1 }
|
||||
|
||||
# Prepend newly installed Python to the PATH of this build (this cannot be
|
||||
# done from inside the powershell script as it would require to restart
|
||||
# the parent CMD process).
|
||||
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
|
||||
# Check that we have the expected version and architecture for Python
|
||||
- "python --version"
|
||||
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
||||
|
||||
# Upgrade to the latest version of pip to avoid it displaying warnings
|
||||
# about it being out of date.
|
||||
- "pip install --disable-pip-version-check --user --upgrade pip"
|
||||
|
||||
# Install the build dependencies of the project. If some dependencies contain
|
||||
# compiled extensions and are not provided as pre-built wheel packages,
|
||||
# pip will build them from source using the MSVC compiler matching the
|
||||
# target Python version and architecture
|
||||
- "%CMD_IN_ENV% pip install -r requirements.txt"
|
||||
|
||||
build_script:
|
||||
# Build the compiled extension
|
||||
- "%CMD_IN_ENV% python setup.py build"
|
||||
|
||||
test_script:
|
||||
# Run the project tests
|
||||
- "%CMD_IN_ENV% python setup.py test"
|
||||
|
||||
after_test:
|
||||
# If tests are successful, create binary packages for the project.
|
||||
- "%CMD_IN_ENV% python setup.py bdist_wheel"
|
||||
- "%CMD_IN_ENV% python setup.py bdist_wininst"
|
||||
- "%CMD_IN_ENV% python setup.py bdist_msi"
|
||||
- ps: "ls dist"
|
||||
|
||||
artifacts:
|
||||
# Archive the generated packages in the ci.appveyor.com build report.
|
||||
- path: dist\*
|
@@ -1,229 +0,0 @@
|
||||
# Sample script to install Python and pip under Windows
|
||||
# Authors: Olivier Grisel, Jonathan Helmus, Kyle Kastner, and Alex Willmer
|
||||
# License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
$MINICONDA_URL = "http://repo.continuum.io/miniconda/"
|
||||
$BASE_URL = "https://www.python.org/ftp/python/"
|
||||
$GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
|
||||
$GET_PIP_PATH = "C:\get-pip.py"
|
||||
|
||||
$PYTHON_PRERELEASE_REGEX = @"
|
||||
(?x)
|
||||
(?<major>\d+)
|
||||
\.
|
||||
(?<minor>\d+)
|
||||
\.
|
||||
(?<micro>\d+)
|
||||
(?<prerelease>[a-z]{1,2}\d+)
|
||||
"@
|
||||
|
||||
|
||||
function Download ($filename, $url) {
|
||||
$webclient = New-Object System.Net.WebClient
|
||||
|
||||
$basedir = $pwd.Path + "\"
|
||||
$filepath = $basedir + $filename
|
||||
if (Test-Path $filename) {
|
||||
Write-Host "Reusing" $filepath
|
||||
return $filepath
|
||||
}
|
||||
|
||||
# Download and retry up to 3 times in case of network transient errors.
|
||||
Write-Host "Downloading" $filename "from" $url
|
||||
$retry_attempts = 2
|
||||
for ($i = 0; $i -lt $retry_attempts; $i++) {
|
||||
try {
|
||||
$webclient.DownloadFile($url, $filepath)
|
||||
break
|
||||
}
|
||||
Catch [Exception]{
|
||||
Start-Sleep 1
|
||||
}
|
||||
}
|
||||
if (Test-Path $filepath) {
|
||||
Write-Host "File saved at" $filepath
|
||||
} else {
|
||||
# Retry once to get the error message if any at the last try
|
||||
$webclient.DownloadFile($url, $filepath)
|
||||
}
|
||||
return $filepath
|
||||
}
|
||||
|
||||
|
||||
function ParsePythonVersion ($python_version) {
|
||||
if ($python_version -match $PYTHON_PRERELEASE_REGEX) {
|
||||
return ([int]$matches.major, [int]$matches.minor, [int]$matches.micro,
|
||||
$matches.prerelease)
|
||||
}
|
||||
$version_obj = [version]$python_version
|
||||
return ($version_obj.major, $version_obj.minor, $version_obj.build, "")
|
||||
}
|
||||
|
||||
|
||||
function DownloadPython ($python_version, $platform_suffix) {
|
||||
$major, $minor, $micro, $prerelease = ParsePythonVersion $python_version
|
||||
|
||||
if (($major -le 2 -and $micro -eq 0) `
|
||||
-or ($major -eq 3 -and $minor -le 2 -and $micro -eq 0) `
|
||||
) {
|
||||
$dir = "$major.$minor"
|
||||
$python_version = "$major.$minor$prerelease"
|
||||
} else {
|
||||
$dir = "$major.$minor.$micro"
|
||||
}
|
||||
|
||||
if ($prerelease) {
|
||||
if (($major -le 2) `
|
||||
-or ($major -eq 3 -and $minor -eq 1) `
|
||||
-or ($major -eq 3 -and $minor -eq 2) `
|
||||
-or ($major -eq 3 -and $minor -eq 3) `
|
||||
) {
|
||||
$dir = "$dir/prev"
|
||||
}
|
||||
}
|
||||
|
||||
if (($major -le 2) -or ($major -le 3 -and $minor -le 4)) {
|
||||
$ext = "msi"
|
||||
if ($platform_suffix) {
|
||||
$platform_suffix = ".$platform_suffix"
|
||||
}
|
||||
} else {
|
||||
$ext = "exe"
|
||||
if ($platform_suffix) {
|
||||
$platform_suffix = "-$platform_suffix"
|
||||
}
|
||||
}
|
||||
|
||||
$filename = "python-$python_version$platform_suffix.$ext"
|
||||
$url = "$BASE_URL$dir/$filename"
|
||||
$filepath = Download $filename $url
|
||||
return $filepath
|
||||
}
|
||||
|
||||
|
||||
function InstallPython ($python_version, $architecture, $python_home) {
|
||||
Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
|
||||
if (Test-Path $python_home) {
|
||||
Write-Host $python_home "already exists, skipping."
|
||||
return $false
|
||||
}
|
||||
if ($architecture -eq "32") {
|
||||
$platform_suffix = ""
|
||||
} else {
|
||||
$platform_suffix = "amd64"
|
||||
}
|
||||
$installer_path = DownloadPython $python_version $platform_suffix
|
||||
$installer_ext = [System.IO.Path]::GetExtension($installer_path)
|
||||
Write-Host "Installing $installer_path to $python_home"
|
||||
$install_log = $python_home + ".log"
|
||||
if ($installer_ext -eq '.msi') {
|
||||
InstallPythonMSI $installer_path $python_home $install_log
|
||||
} else {
|
||||
InstallPythonEXE $installer_path $python_home $install_log
|
||||
}
|
||||
if (Test-Path $python_home) {
|
||||
Write-Host "Python $python_version ($architecture) installation complete"
|
||||
} else {
|
||||
Write-Host "Failed to install Python in $python_home"
|
||||
Get-Content -Path $install_log
|
||||
Exit 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function InstallPythonEXE ($exepath, $python_home, $install_log) {
|
||||
$install_args = "/quiet InstallAllUsers=1 TargetDir=$python_home"
|
||||
RunCommand $exepath $install_args
|
||||
}
|
||||
|
||||
|
||||
function InstallPythonMSI ($msipath, $python_home, $install_log) {
|
||||
$install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home"
|
||||
$uninstall_args = "/qn /x $msipath"
|
||||
RunCommand "msiexec.exe" $install_args
|
||||
if (-not(Test-Path $python_home)) {
|
||||
Write-Host "Python seems to be installed else-where, reinstalling."
|
||||
RunCommand "msiexec.exe" $uninstall_args
|
||||
RunCommand "msiexec.exe" $install_args
|
||||
}
|
||||
}
|
||||
|
||||
function RunCommand ($command, $command_args) {
|
||||
Write-Host $command $command_args
|
||||
Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru
|
||||
}
|
||||
|
||||
|
||||
function InstallPip ($python_home) {
|
||||
$pip_path = $python_home + "\Scripts\pip.exe"
|
||||
$python_path = $python_home + "\python.exe"
|
||||
if (-not(Test-Path $pip_path)) {
|
||||
Write-Host "Installing pip..."
|
||||
$webclient = New-Object System.Net.WebClient
|
||||
$webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH)
|
||||
Write-Host "Executing:" $python_path $GET_PIP_PATH
|
||||
& $python_path $GET_PIP_PATH
|
||||
} else {
|
||||
Write-Host "pip already installed."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function DownloadMiniconda ($python_version, $platform_suffix) {
|
||||
if ($python_version -eq "3.4") {
|
||||
$filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe"
|
||||
} else {
|
||||
$filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe"
|
||||
}
|
||||
$url = $MINICONDA_URL + $filename
|
||||
$filepath = Download $filename $url
|
||||
return $filepath
|
||||
}
|
||||
|
||||
|
||||
function InstallMiniconda ($python_version, $architecture, $python_home) {
|
||||
Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
|
||||
if (Test-Path $python_home) {
|
||||
Write-Host $python_home "already exists, skipping."
|
||||
return $false
|
||||
}
|
||||
if ($architecture -eq "32") {
|
||||
$platform_suffix = "x86"
|
||||
} else {
|
||||
$platform_suffix = "x86_64"
|
||||
}
|
||||
$filepath = DownloadMiniconda $python_version $platform_suffix
|
||||
Write-Host "Installing" $filepath "to" $python_home
|
||||
$install_log = $python_home + ".log"
|
||||
$args = "/S /D=$python_home"
|
||||
Write-Host $filepath $args
|
||||
Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru
|
||||
if (Test-Path $python_home) {
|
||||
Write-Host "Python $python_version ($architecture) installation complete"
|
||||
} else {
|
||||
Write-Host "Failed to install Python in $python_home"
|
||||
Get-Content -Path $install_log
|
||||
Exit 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function InstallMinicondaPip ($python_home) {
|
||||
$pip_path = $python_home + "\Scripts\pip.exe"
|
||||
$conda_path = $python_home + "\Scripts\conda.exe"
|
||||
if (-not(Test-Path $pip_path)) {
|
||||
Write-Host "Installing pip..."
|
||||
$args = "install --yes pip"
|
||||
Write-Host $conda_path $args
|
||||
Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru
|
||||
} else {
|
||||
Write-Host "pip already installed."
|
||||
}
|
||||
}
|
||||
|
||||
function main () {
|
||||
InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON
|
||||
InstallPip $env:PYTHON
|
||||
}
|
||||
|
||||
main
|
@@ -1,88 +0,0 @@
|
||||
:: To build extensions for 64 bit Python 3, we need to configure environment
|
||||
:: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of:
|
||||
:: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1)
|
||||
::
|
||||
:: To build extensions for 64 bit Python 2, we need to configure environment
|
||||
:: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of:
|
||||
:: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0)
|
||||
::
|
||||
:: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific
|
||||
:: environment configurations.
|
||||
::
|
||||
:: Note: this script needs to be run with the /E:ON and /V:ON flags for the
|
||||
:: cmd interpreter, at least for (SDK v7.0)
|
||||
::
|
||||
:: More details at:
|
||||
:: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows
|
||||
:: http://stackoverflow.com/a/13751649/163740
|
||||
::
|
||||
:: Author: Olivier Grisel
|
||||
:: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
::
|
||||
:: Notes about batch files for Python people:
|
||||
::
|
||||
:: Quotes in values are literally part of the values:
|
||||
:: SET FOO="bar"
|
||||
:: FOO is now five characters long: " b a r "
|
||||
:: If you don't want quotes, don't include them on the right-hand side.
|
||||
::
|
||||
:: The CALL lines at the end of this file look redundant, but if you move them
|
||||
:: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y
|
||||
:: case, I don't know why.
|
||||
@ECHO OFF
|
||||
|
||||
SET COMMAND_TO_RUN=%*
|
||||
SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows
|
||||
SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf
|
||||
|
||||
:: Extract the major and minor versions, and allow for the minor version to be
|
||||
:: more than 9. This requires the version number to have two dots in it.
|
||||
SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1%
|
||||
IF "%PYTHON_VERSION:~3,1%" == "." (
|
||||
SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1%
|
||||
) ELSE (
|
||||
SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2%
|
||||
)
|
||||
|
||||
:: Based on the Python version, determine what SDK version to use, and whether
|
||||
:: to set the SDK for 64-bit.
|
||||
IF %MAJOR_PYTHON_VERSION% == 2 (
|
||||
SET WINDOWS_SDK_VERSION="v7.0"
|
||||
SET SET_SDK_64=Y
|
||||
) ELSE (
|
||||
IF %MAJOR_PYTHON_VERSION% == 3 (
|
||||
SET WINDOWS_SDK_VERSION="v7.1"
|
||||
IF %MINOR_PYTHON_VERSION% LEQ 4 (
|
||||
SET SET_SDK_64=Y
|
||||
) ELSE (
|
||||
SET SET_SDK_64=N
|
||||
IF EXIST "%WIN_WDK%" (
|
||||
:: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/
|
||||
REN "%WIN_WDK%" 0wdf
|
||||
)
|
||||
)
|
||||
) ELSE (
|
||||
ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%"
|
||||
EXIT 1
|
||||
)
|
||||
)
|
||||
|
||||
IF %PYTHON_ARCH% == 64 (
|
||||
IF %SET_SDK_64% == Y (
|
||||
ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture
|
||||
SET DISTUTILS_USE_SDK=1
|
||||
SET MSSdk=1
|
||||
"%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION%
|
||||
"%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release
|
||||
ECHO Executing: %COMMAND_TO_RUN%
|
||||
call %COMMAND_TO_RUN% || EXIT 1
|
||||
) ELSE (
|
||||
ECHO Using default MSVC build environment for 64 bit architecture
|
||||
ECHO Executing: %COMMAND_TO_RUN%
|
||||
call %COMMAND_TO_RUN% || EXIT 1
|
||||
)
|
||||
) ELSE (
|
||||
ECHO Using default MSVC build environment for 32 bit architecture
|
||||
ECHO Executing: %COMMAND_TO_RUN%
|
||||
call %COMMAND_TO_RUN% || EXIT 1
|
||||
)
|
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
GENLIST=$(git shortlog -s -n | cut -f2 | sort)
|
||||
GENLIST=$(git shortlog -s -n | cut -f2 | sort -f)
|
||||
AUTHORSFILE="$(dirname $0)/../AUTHORS"
|
||||
TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile"
|
||||
|
||||
|
@@ -5,3 +5,4 @@ pyserial
|
||||
sphinx-rtd-theme
|
||||
setuptools-scm
|
||||
docutils>=0.12
|
||||
viivakoodi
|
||||
|
@@ -67,7 +67,7 @@ IP by DHCP or you set it manually.
|
||||
|
||||
::
|
||||
|
||||
Epson = printer.Network("192.168.1.99")
|
||||
Epson = printer.Network('192.168.1.99')
|
||||
|
||||
Serial printer
|
||||
^^^^^^^^^^^^^^
|
||||
@@ -81,7 +81,7 @@ to.
|
||||
|
||||
::
|
||||
|
||||
Epson = printer.Serial("/dev/tty0")
|
||||
Epson = printer.Serial('/dev/tty0')
|
||||
|
||||
Other printers
|
||||
^^^^^^^^^^^^^^
|
||||
@@ -93,7 +93,7 @@ passing the device node name.
|
||||
|
||||
::
|
||||
|
||||
Epson = printer.File("/dev/usb/lp1")
|
||||
Epson = printer.File('/dev/usb/lp1')
|
||||
|
||||
The default is "/dev/usb/lp0", so if the printer is located on that
|
||||
node, then you don't necessary need to pass the node name.
|
||||
@@ -110,11 +110,11 @@ on a USB interface.
|
||||
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
|
||||
Epson = printer.Usb(0x04b8,0x0202)
|
||||
# Print text
|
||||
Epson.text("Hello World\n")
|
||||
Epson.text('Hello World\n')
|
||||
# Print image
|
||||
Epson.image("logo.gif")
|
||||
Epson.image('logo.gif')
|
||||
# Print QR Code
|
||||
Epson.qr("You can readme from your smartphone")
|
||||
Epson.qr('You can readme from your smartphone')
|
||||
# Print barcode
|
||||
Epson.barcode('1324354657687','EAN13',64,2,'','')
|
||||
# Cut paper
|
||||
@@ -214,7 +214,7 @@ 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
|
||||
file = open('binary-blob.bin', 'rb') # read in the file containing your commands in binary-mode
|
||||
data = file.read()
|
||||
file.close()
|
||||
|
||||
@@ -273,8 +273,8 @@ This is probably best explained by an example:
|
||||
d = Dummy()
|
||||
|
||||
# create ESC/POS for the print job, this should go really fast
|
||||
d.text("This is my image:\n")
|
||||
d.image("funny_cat.png")
|
||||
d.text('This is my image:\n')
|
||||
d.image('funny_cat.png')
|
||||
d.cut()
|
||||
|
||||
# send code to printer
|
||||
|
17
examples/barcodes.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from escpos.printer import Usb
|
||||
|
||||
|
||||
# Adapt to your needs
|
||||
p = Usb(0x0416, 0x5011, profile='POS-5890')
|
||||
|
||||
# Print software and then hardware barcode with the same content
|
||||
p.soft_barcode('code39', '123456')
|
||||
p.text('\n')
|
||||
p.text('\n')
|
||||
p.barcode('123456', 'CODE39')
|
@@ -1,13 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Prints code page tables.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import six
|
||||
import sys
|
||||
|
||||
from escpos import printer
|
||||
from escpos.constants import CODEPAGE_CHANGE, ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT
|
||||
from escpos.constants import CODEPAGE_CHANGE, CTL_CR, CTL_FF, CTL_HT, CTL_LF, CTL_VT, ESC
|
||||
|
||||
import six
|
||||
|
||||
|
||||
def main():
|
||||
@@ -17,9 +21,9 @@ def main():
|
||||
|
||||
for codepage in sys.argv[1:] or ['USA']:
|
||||
dummy.set(height=2, width=2)
|
||||
dummy._raw(codepage + "\n\n\n")
|
||||
dummy._raw(codepage + '\n\n\n')
|
||||
print_codepage(dummy, codepage)
|
||||
dummy._raw("\n\n")
|
||||
dummy._raw('\n\n')
|
||||
|
||||
dummy.cut()
|
||||
|
||||
@@ -30,22 +34,22 @@ def print_codepage(printer, codepage):
|
||||
if codepage.isdigit():
|
||||
codepage = int(codepage)
|
||||
printer._raw(CODEPAGE_CHANGE + six.int2byte(codepage))
|
||||
printer._raw("after")
|
||||
printer._raw('after')
|
||||
else:
|
||||
printer.charcode(codepage)
|
||||
|
||||
sep = ""
|
||||
sep = ''
|
||||
|
||||
# Table header
|
||||
printer.set(text_type='B')
|
||||
printer._raw(" {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16)))))
|
||||
printer.set(font='b')
|
||||
printer._raw(' {}\n'.format(sep.join(map(lambda s: hex(s)[2:], range(0, 16)))))
|
||||
printer.set()
|
||||
|
||||
# The table
|
||||
for x in range(0, 16):
|
||||
# First column
|
||||
printer.set(text_type='B')
|
||||
printer._raw("{} ".format(hex(x)[2:]))
|
||||
printer.set(font='b')
|
||||
printer._raw('{} '.format(hex(x)[2:]))
|
||||
printer.set()
|
||||
|
||||
for y in range(0, 16):
|
||||
|
BIN
examples/graphics/climacons/clear-day.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
examples/graphics/climacons/clear-night.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
examples/graphics/climacons/cloudy.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
examples/graphics/climacons/fog.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
examples/graphics/climacons/partly-cloudy-day.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
examples/graphics/climacons/partly-cloudy-night.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
examples/graphics/climacons/rain.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
10
examples/graphics/climacons/readme.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Climacons by Adam Whitcroft
|
||||
|
||||
75 climatically categorised pictographs for web and UI design by [@adamwhitcroft](http://www.twitter.com/#!/adamwhitcroft).
|
||||
|
||||
Visit the [Climacons](http://adamwhitcroft.com/climacons/) website for more information.
|
||||
|
||||
Visit [Adam Whitcroft on GitHub](https://github.com/AdamWhitcroft)
|
||||
|
||||
## License
|
||||
You are free to use any of the Climacons Icons (the "icons") in any personal or commercial work without obligation of payment (monetary or otherwise) or attribution, however a credit for the work would be appreciated. **Do not** redistribute or sell and **do not** claim creative credit. Intellectual property rights are not transferred with the download of the icons.
|
BIN
examples/graphics/climacons/sleet.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
examples/graphics/climacons/snow.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
examples/graphics/climacons/wind.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
@@ -1,10 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
|
||||
from escpos.printer import Usb
|
||||
|
||||
|
||||
def usage():
|
||||
print("usage: qr_code.py <content>")
|
||||
print('usage: qr_code.py <content>')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -15,5 +21,5 @@ if __name__ == '__main__':
|
||||
content = sys.argv[1]
|
||||
|
||||
# Adapt to your needs
|
||||
p = Usb(0x0416, 0x5011, profile="POS-5890")
|
||||
p.qr(content)
|
||||
p = Usb(0x0416, 0x5011, profile='POS-5890')
|
||||
p.qr(content, center=True)
|
||||
|
@@ -1,8 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from escpos.printer import Usb
|
||||
|
||||
|
||||
# Adapt to your needs
|
||||
p = Usb(0x0416, 0x5011, profile="POS-5890")
|
||||
p = Usb(0x0416, 0x5011, profile='POS-5890')
|
||||
|
||||
# Some software barcodes
|
||||
p.soft_barcode('code128', 'Hello')
|
||||
|
130
examples/weather.py
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Adapted script from Adafruit
|
||||
# Weather forecast for Raspberry Pi w/Adafruit Mini Thermal Printer.
|
||||
# Retrieves data from DarkSky.net's API, prints current conditions and
|
||||
# forecasts for next two days.
|
||||
# Weather example using nice bitmaps.
|
||||
# Written by Adafruit Industries. MIT license.
|
||||
# Adapted and enhanced for escpos library by MrWunderbar666
|
||||
|
||||
# Icons taken from http://adamwhitcroft.com/climacons/
|
||||
# Check out his github: https://github.com/AdamWhitcroft/climacons
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import calendar
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import urllib
|
||||
from datetime import datetime
|
||||
|
||||
from escpos.printer import Usb
|
||||
|
||||
""" Setting up the main pathing """
|
||||
this_dir, this_filename = os.path.split(__file__)
|
||||
GRAPHICS_PATH = os.path.join(this_dir, 'graphics/climacons/')
|
||||
|
||||
# Adapt to your needs
|
||||
printer = Usb(0x0416, 0x5011, profile='POS-5890')
|
||||
|
||||
# You can get your API Key on www.darksky.net and register a dev account.
|
||||
# Technically you can use any other weather service, of course :)
|
||||
API_KEY = 'YOUR API KEY'
|
||||
|
||||
LAT = '22.345490' # Your Location
|
||||
LONG = '114.189945' # Your Location
|
||||
|
||||
|
||||
def forecast_icon(idx):
|
||||
icon = data['daily']['data'][idx]['icon']
|
||||
image = GRAPHICS_PATH + icon + '.png'
|
||||
return image
|
||||
|
||||
|
||||
# Dumps one forecast line to the printer
|
||||
def forecast(idx):
|
||||
date = datetime.fromtimestamp(int(data['daily']['data'][idx]['time']))
|
||||
day = calendar.day_name[date.weekday()]
|
||||
lo = data['daily']['data'][idx]['temperatureMin']
|
||||
hi = data['daily']['data'][idx]['temperatureMax']
|
||||
cond = data['daily']['data'][idx]['summary']
|
||||
print(date)
|
||||
print(day)
|
||||
print(lo)
|
||||
print(hi)
|
||||
print(cond)
|
||||
time.sleep(1)
|
||||
printer.set(
|
||||
font='a',
|
||||
height=2,
|
||||
align='left',
|
||||
bold=False,
|
||||
double_height=False)
|
||||
printer.text(day + ' \n ')
|
||||
time.sleep(5) # Sleep to prevent printer buffer overflow
|
||||
printer.text('\n')
|
||||
printer.image(forecast_icon(idx))
|
||||
printer.text('low ' + str(lo))
|
||||
printer.text(deg)
|
||||
printer.text('\n')
|
||||
printer.text(' high ' + str(hi))
|
||||
printer.text(deg)
|
||||
printer.text('\n')
|
||||
# take care of pesky unicode dash
|
||||
printer.text(cond.replace(u'\u2013', '-').encode('utf-8'))
|
||||
printer.text('\n \n')
|
||||
|
||||
|
||||
def icon():
|
||||
icon = data['currently']['icon']
|
||||
image = GRAPHICS_PATH + icon + '.png'
|
||||
return image
|
||||
|
||||
|
||||
deg = ' C' # Degree symbol on thermal printer, need to find a better way to use a proper degree symbol
|
||||
|
||||
# if you want Fahrenheit change units= to 'us'
|
||||
url = 'https://api.darksky.net/forecast/' + API_KEY + '/' + LAT + ',' + LONG + \
|
||||
'?exclude=[alerts,minutely,hourly,flags]&units=si' # change last bit to 'us' for Fahrenheit
|
||||
response = urllib.urlopen(url)
|
||||
data = json.loads(response.read())
|
||||
|
||||
printer.print_and_feed(n=1)
|
||||
printer.control('LF')
|
||||
printer.set(font='a', height=2, align='center', bold=True, double_height=True)
|
||||
printer.text('Weather Forecast')
|
||||
printer.text('\n')
|
||||
printer.set(align='center')
|
||||
|
||||
|
||||
# Print current conditions
|
||||
printer.set(font='a', height=2, align='center', bold=True, double_height=False)
|
||||
printer.text('Current conditions: \n')
|
||||
printer.image(icon())
|
||||
printer.text('\n')
|
||||
|
||||
printer.set(font='a', height=2, align='left', bold=False, double_height=False)
|
||||
temp = data['currently']['temperature']
|
||||
cond = data['currently']['summary']
|
||||
printer.text(temp)
|
||||
printer.text(' ')
|
||||
printer.text(deg)
|
||||
printer.text(' ')
|
||||
printer.text('\n')
|
||||
printer.text('Sky: ' + cond)
|
||||
printer.text('\n')
|
||||
printer.text('\n')
|
||||
|
||||
# Print forecast
|
||||
printer.set(font='a', height=2, align='center', bold=True, double_height=False)
|
||||
printer.text('Forecast: \n')
|
||||
forecast(0)
|
||||
forecast(1)
|
||||
printer.cut()
|
||||
printer.control('LF')
|
11
setup.cfg
@@ -9,4 +9,13 @@ universal=1
|
||||
[flake8]
|
||||
exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py
|
||||
max-line-length = 120
|
||||
# future-imports = absolute_import, division, print_function, unicode_literals # we are not there yet
|
||||
accept-encoding = utf-8, utf-16
|
||||
require-code = True
|
||||
# FI12 __future__ import "with_statement" missing
|
||||
# FI15 __future__ import "generator_stop" missing
|
||||
# FI16 __future__ import "nested_scopes" missing
|
||||
# FI17 __future__ import "generators" missing
|
||||
# FI5x __future__ import "xxx" present
|
||||
# I101 Imported names are in the wrong order.
|
||||
ignore = FI12,FI15,FI16,FI17,FI5, I101
|
||||
copyright-check = True
|
||||
|
50
setup.py
@@ -1,13 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
# from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import 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")
|
||||
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.
|
||||
@@ -19,33 +25,6 @@ def read(fname):
|
||||
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
|
||||
@@ -68,7 +47,6 @@ setup(
|
||||
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',
|
||||
bugtrack_url='https://github.com/python-escpos/python-escpos/issues',
|
||||
license='MIT',
|
||||
long_description=read('README.rst'),
|
||||
author='Manuel F Martinez and others',
|
||||
@@ -83,8 +61,8 @@ setup(
|
||||
'receipt,',
|
||||
],
|
||||
platforms='any',
|
||||
package_dir={"": "src"},
|
||||
packages=find_packages(where="src", exclude=["tests", "tests.*"]),
|
||||
package_dir={'': 'src'},
|
||||
packages=find_packages(where='src', exclude=['tests', 'tests.*']),
|
||||
package_data={'': ['COPYING', 'src/escpos/capabilities.json']},
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
@@ -97,7 +75,6 @@ setup(
|
||||
'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 :: 3.6',
|
||||
@@ -114,7 +91,7 @@ setup(
|
||||
'pyserial',
|
||||
'six',
|
||||
'appdirs',
|
||||
'pyyaml',
|
||||
'PyYAML',
|
||||
'argparse',
|
||||
'argcomplete',
|
||||
'future',
|
||||
@@ -126,16 +103,15 @@ setup(
|
||||
tests_require=[
|
||||
'jaconv',
|
||||
'tox',
|
||||
'pytest',
|
||||
'pytest!=3.2.0,!=3.3.0',
|
||||
'pytest-cov',
|
||||
'pytest-mock',
|
||||
'nose',
|
||||
'scripttest',
|
||||
'mock',
|
||||
'hypothesis',
|
||||
'hypothesis!=3.56.9',
|
||||
'flake8'
|
||||
],
|
||||
cmdclass={'test': Tox},
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'python-escpos = escpos.cli:main'
|
||||
|
@@ -7,7 +7,7 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__all__ = ["constants", "escpos", "exceptions", "printer"]
|
||||
__all__ = ['constants', 'escpos', 'exceptions', 'printer']
|
||||
|
||||
try:
|
||||
from .version import version as __version__ # noqa
|
||||
|
@@ -1,22 +1,62 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import pickle
|
||||
import platform
|
||||
import re
|
||||
import six
|
||||
import time
|
||||
from os import environ, path
|
||||
from tempfile import gettempdir
|
||||
|
||||
import six
|
||||
|
||||
import yaml
|
||||
|
||||
# Load external printer database
|
||||
if 'ESCPOS_CAPABILITIES_FILE' in environ:
|
||||
file_path = environ['ESCPOS_CAPABILITIES_FILE']
|
||||
else:
|
||||
file_path = path.join(path.dirname(__file__), 'capabilities.json')
|
||||
logging.basicConfig()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
pickle_dir = environ.get('ESCPOS_CAPABILITIES_PICKLE_DIR', gettempdir())
|
||||
pickle_path = path.join(pickle_dir, '{v}.capabilities.pickle'.format(v=platform.python_version()))
|
||||
capabilities_path = environ.get(
|
||||
'ESCPOS_CAPABILITIES_FILE',
|
||||
path.join(path.dirname(__file__), 'capabilities.json'))
|
||||
|
||||
# Load external printer database
|
||||
t0 = time.time()
|
||||
logger.debug('Using capabilities from file: %s', capabilities_path)
|
||||
if path.exists(pickle_path):
|
||||
if path.getmtime(capabilities_path) > path.getmtime(pickle_path):
|
||||
logger.debug('Found a more recent capabilities file')
|
||||
full_load = True
|
||||
else:
|
||||
full_load = False
|
||||
logger.debug('Loading capabilities from pickle in %s', pickle_path)
|
||||
with open(pickle_path, 'rb') as cf:
|
||||
CAPABILITIES = pickle.load(cf)
|
||||
else:
|
||||
logger.debug('Capabilities pickle file not found: %s', pickle_path)
|
||||
full_load = True
|
||||
|
||||
if full_load:
|
||||
logger.debug('Loading and pickling capabilities')
|
||||
with open(capabilities_path) as cp, open(pickle_path, 'wb') as pp:
|
||||
CAPABILITIES = yaml.load(cp)
|
||||
pickle.dump(CAPABILITIES, pp, protocol=2)
|
||||
|
||||
logger.debug('Finished loading capabilities took %.2fs', time.time() - t0)
|
||||
|
||||
with open(file_path) as f:
|
||||
CAPABILITIES = yaml.load(f)
|
||||
|
||||
PROFILES = CAPABILITIES['profiles']
|
||||
|
||||
|
||||
class NotSupported(Exception):
|
||||
"""Raised if a requested feature is not suppored by the
|
||||
"""Raised if a requested feature is not supported by the
|
||||
printer profile.
|
||||
"""
|
||||
pass
|
||||
@@ -59,7 +99,7 @@ class BaseProfile(object):
|
||||
return self.features.get(feature)
|
||||
|
||||
def get_code_pages(self):
|
||||
"""Return the support code pages as a {name: index} dict.
|
||||
"""Return the support code pages as a ``{name: index}`` dict.
|
||||
"""
|
||||
return {v: k for k, v in self.codePages.items()}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
""" CLI
|
||||
|
||||
@@ -15,13 +16,15 @@ from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
try:
|
||||
import argcomplete
|
||||
except ImportError:
|
||||
# this CLI works nevertheless without argcomplete
|
||||
pass # noqa
|
||||
import sys
|
||||
import six
|
||||
|
||||
from . import config
|
||||
from . import version
|
||||
|
||||
@@ -550,7 +553,7 @@ def main():
|
||||
# print command with args
|
||||
getattr(printer, target_command)(**command_arguments)
|
||||
if target_command in REQUIRES_NEWLINE:
|
||||
printer.text("\n")
|
||||
printer.text('\n')
|
||||
else:
|
||||
command_arguments['printer'] = printer
|
||||
globals()[target_command](**command_arguments)
|
||||
|
@@ -1,3 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .capabilities import CAPABILITIES
|
||||
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" ESC/POS configuration manager.
|
||||
|
||||
This module contains the implentations of abstract base class :py:class:`Config`.
|
||||
@@ -10,11 +11,13 @@ from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
import appdirs
|
||||
|
||||
import yaml
|
||||
|
||||
from . import printer
|
||||
from . import exceptions
|
||||
from . import printer
|
||||
|
||||
|
||||
class Config(object):
|
||||
|
@@ -225,6 +225,24 @@ BARCODE_TYPE_B = {
|
||||
'GS1 DATABAR EXPANDED': _SET_BARCODE_TYPE(78),
|
||||
}
|
||||
|
||||
BARCODE_FORMATS = {
|
||||
'UPC-A': ([(11, 12)], "^[0-9]{11,12}$"),
|
||||
'UPC-E': ([(7, 8), (11, 12)], "^([0-9]{7,8}|[0-9]{11,12})$"),
|
||||
'EAN13': ([(12, 13)], "^[0-9]{12,13}$"),
|
||||
'EAN8': ([(7, 8)], "^[0-9]{7,8}$"),
|
||||
'CODE39': ([(1, 255)], "^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"),
|
||||
'ITF': ([(2, 255)], "^([0-9]{2})+$"),
|
||||
'NW7': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"),
|
||||
'CODABAR': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), # Same as NW7
|
||||
'CODE93': ([(1, 255)], "^[\\x00-\\x7F]+$"),
|
||||
'CODE128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"),
|
||||
'GS1-128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), # same as CODE128
|
||||
'GS1 DATABAR OMNIDIRECTIONAL': ([(13,13)], "^[0-9]{13}$"),
|
||||
'GS1 DATABAR TRUNCATED': ([(13,13)], "^[0-9]{13}$"), # same as GS1 omnidirectional
|
||||
'GS1 DATABAR LIMITED': ([(13,13)], "^[01][0-9]{12}$"),
|
||||
'GS1 DATABAR EXPANDED': ([(2,255)], "^\([0-9][A-Za-z0-9 \!\"\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$"),
|
||||
}
|
||||
|
||||
BARCODE_TYPES = {
|
||||
'A': BARCODE_TYPE_A,
|
||||
'B': BARCODE_TYPE_B,
|
||||
@@ -251,5 +269,10 @@ 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
|
||||
|
||||
# Status Command
|
||||
RT_STATUS_ONLINE = DLE + EOT + b'\x01';
|
||||
RT_MASK_ONLINE = 8;
|
||||
RT_STATUS = DLE + EOT
|
||||
RT_STATUS_ONLINE = RT_STATUS + b'\x01'
|
||||
RT_STATUS_PAPER = RT_STATUS + b'\x04'
|
||||
RT_MASK_ONLINE = 8
|
||||
RT_MASK_PAPER = 18
|
||||
RT_MASK_LOWPAPER = 30
|
||||
RT_MASK_NOPAPER = 114
|
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Main class
|
||||
|
||||
@@ -15,38 +15,41 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import qrcode
|
||||
import os
|
||||
import textwrap
|
||||
import six
|
||||
import time
|
||||
from abc import ABCMeta, abstractmethod # abstract base class support
|
||||
from re import match as re_match
|
||||
|
||||
import barcode
|
||||
from barcode.writer import ImageWriter
|
||||
|
||||
from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q
|
||||
from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO, BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH
|
||||
from .constants import BARCODE_FONT_A, BARCODE_FONT_B
|
||||
import qrcode
|
||||
|
||||
import six
|
||||
|
||||
from .capabilities import BARCODE_B, get_profile
|
||||
from .constants import BARCODE_FONT_A, BARCODE_FONT_B, BARCODE_FORMATS
|
||||
from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW
|
||||
from .constants import TXT_SIZE, TXT_NORMAL
|
||||
from .constants import SET_FONT
|
||||
from .constants import BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH
|
||||
from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT
|
||||
from .constants import CTL_VT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON
|
||||
from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q
|
||||
from .constants import HW_INIT, HW_RESET, HW_SELECT
|
||||
from .constants import LINESPACING_FUNCS, LINESPACING_RESET
|
||||
from .constants import LINE_DISPLAY_OPEN, LINE_DISPLAY_CLEAR, LINE_DISPLAY_CLOSE
|
||||
from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT
|
||||
from .constants import HW_RESET, HW_SELECT, HW_INIT
|
||||
from .constants import CTL_VT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON
|
||||
from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO
|
||||
from .constants import RT_MASK_ONLINE, RT_STATUS_ONLINE
|
||||
from .constants import RT_STATUS_PAPER, RT_MASK_PAPER, RT_MASK_LOWPAPER, RT_MASK_NOPAPER
|
||||
from .constants import SET_FONT
|
||||
from .constants import TXT_SIZE, TXT_NORMAL
|
||||
from .constants import TXT_STYLE
|
||||
from .constants import RT_STATUS_ONLINE, RT_MASK_ONLINE
|
||||
|
||||
from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError
|
||||
from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError
|
||||
from .exceptions import ImageWidthError
|
||||
|
||||
from .exceptions import BarcodeCodeError, BarcodeSizeError, BarcodeTypeError
|
||||
from .exceptions import CashDrawerError, ImageWidthError
|
||||
from .exceptions import SetVariableError, TabPosError
|
||||
from .image import EscposImage
|
||||
from .magicencode import MagicEncode
|
||||
|
||||
from abc import ABCMeta, abstractmethod # abstract base class support
|
||||
from escpos.image import EscposImage
|
||||
from escpos.capabilities import get_profile, BARCODE_B
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class Escpos(object):
|
||||
@@ -79,14 +82,14 @@ class Escpos(object):
|
||||
"""
|
||||
pass
|
||||
|
||||
def _read(self, msg):
|
||||
def _read(self):
|
||||
""" Returns a NotImplementedError if the instance of the class doesn't override this method.
|
||||
:raises NotImplementedError
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster",
|
||||
fragment_height=960):
|
||||
def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl='bitImageRaster',
|
||||
fragment_height=960, center=False):
|
||||
""" Print an image
|
||||
|
||||
You can select whether the printer should print in high density or not. The default value is high density.
|
||||
@@ -107,14 +110,19 @@ class Escpos(object):
|
||||
:param high_density_horizontal: print in high density in horizontal direction *default:* True
|
||||
:param impl: choose image printing mode between `bitImageRaster`, `graphics` or `bitImageColumn`
|
||||
:param fragment_height: Images larger than this will be split into multiple fragments *default:* 960
|
||||
:param center: Center image horizontally *default:* False
|
||||
|
||||
"""
|
||||
im = EscposImage(img_source)
|
||||
|
||||
try:
|
||||
max_width = int(self.profile.profile_data['media']['width']['pixels'])
|
||||
|
||||
if im.width > max_width:
|
||||
raise ImageWidthError('{} > {}'.format(im.width, max_width))
|
||||
|
||||
if center:
|
||||
im.center(max_width)
|
||||
except KeyError:
|
||||
# If the printer's pixel width is not known, print anyways...
|
||||
pass
|
||||
@@ -132,14 +140,14 @@ class Escpos(object):
|
||||
fragment_height=fragment_height)
|
||||
return
|
||||
|
||||
if impl == "bitImageRaster":
|
||||
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) +\
|
||||
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":
|
||||
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'
|
||||
@@ -151,14 +159,14 @@ class Escpos(object):
|
||||
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":
|
||||
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
|
||||
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
|
||||
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):
|
||||
@@ -172,7 +180,8 @@ class Escpos(object):
|
||||
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):
|
||||
def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2,
|
||||
native=False, center=False, impl='bitImageRaster'):
|
||||
""" Print QR Code for the provided string
|
||||
|
||||
:param content: The content of the code. Numeric data will be more efficiently compacted.
|
||||
@@ -184,21 +193,22 @@ class Escpos(object):
|
||||
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)
|
||||
:param center: Centers the code *default:* False
|
||||
"""
|
||||
# Basic validation
|
||||
if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]:
|
||||
raise ValueError("Invalid error correction level")
|
||||
raise ValueError('Invalid error correction level')
|
||||
if not 1 <= size <= 16:
|
||||
raise ValueError("Invalid block size (must be 1-16)")
|
||||
raise ValueError('Invalid block size (must be 1-16)')
|
||||
if model not in [QR_MODEL_1, QR_MODEL_2, QR_MICRO]:
|
||||
raise ValueError("Invalid QR model (must be one of QR_MODEL_1, QR_MODEL_2, QR_MICRO)")
|
||||
if content == "":
|
||||
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)")
|
||||
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,
|
||||
@@ -209,13 +219,18 @@ class Escpos(object):
|
||||
qr_code.add_data(content)
|
||||
qr_code.make(fit=True)
|
||||
qr_img = qr_code.make_image()
|
||||
im = qr_img._img.convert("RGB")
|
||||
im = qr_img._img.convert('RGB')
|
||||
|
||||
# Convert the RGB image in printable image
|
||||
self.text('\n')
|
||||
self.image(im)
|
||||
self.image(im, center=center, impl=impl)
|
||||
self.text('\n')
|
||||
self.text('\n')
|
||||
return
|
||||
|
||||
if center:
|
||||
raise NotImplementedError('Centering not implemented for native QR rendering')
|
||||
|
||||
# Native 2D code printing
|
||||
cn = b'1' # Code type for QR code
|
||||
# Select model: 1, 2 or micro.
|
||||
@@ -237,7 +252,7 @@ class Escpos(object):
|
||||
: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.")
|
||||
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)
|
||||
|
||||
@@ -250,16 +265,16 @@ class Escpos(object):
|
||||
"""
|
||||
max_input = (256 << (out_bytes * 8) - 1)
|
||||
if not 1 <= out_bytes <= 4:
|
||||
raise ValueError("Can only output 1-4 byes")
|
||||
raise ValueError('Can only output 1-4 bytes')
|
||||
if not 0 <= inp_number <= max_input:
|
||||
raise ValueError("Number too large. Can only output up to {0} in {1} byes".format(max_input, out_bytes))
|
||||
raise ValueError('Number too large. Can only output up to {0} in {1} bytes'.format(max_input, out_bytes))
|
||||
outp = b''
|
||||
for _ in range(0, out_bytes):
|
||||
outp += six.int2byte(inp_number % 256)
|
||||
inp_number //= 256
|
||||
return outp
|
||||
|
||||
def charcode(self, code="AUTO"):
|
||||
def charcode(self, code='AUTO'):
|
||||
""" Set Character Code Table
|
||||
|
||||
Sets the control sequence from ``CHARCODE`` in :py:mod:`escpos.constants` as active. It will be sent with
|
||||
@@ -269,22 +284,47 @@ class Escpos(object):
|
||||
:param code: Name of CharCode
|
||||
:raises: :py:exc:`~escpos.exceptions.CharCodeError`
|
||||
"""
|
||||
if code.upper() == "AUTO":
|
||||
if code.upper() == 'AUTO':
|
||||
self.magic.force_encoding(False)
|
||||
else:
|
||||
self.magic.force_encoding(code)
|
||||
|
||||
def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A",
|
||||
align_ct=True, function_type=None):
|
||||
@staticmethod
|
||||
def check_barcode(bc, code):
|
||||
"""
|
||||
This method checks if the barcode is in the proper format.
|
||||
The validation concerns the barcode length and the set of characters, but won't compute/validate any checksum.
|
||||
The full set of requirement for each barcode type is available in the ESC/POS documentation.
|
||||
|
||||
As an example, using EAN13, the barcode `12345678901` will be correct, because it can be rendered by the
|
||||
printer. But it does not suit the EAN13 standard, because the checksum digit is missing. Adding a wrong
|
||||
checksum in the end will also be considered correct, but adding a letter won't (EAN13 is numeric only).
|
||||
|
||||
.. todo:: Add a method to compute the checksum for the different standards
|
||||
|
||||
.. todo:: For fixed-length standards with mandatory checksum (EAN, UPC),
|
||||
compute and add the checksum automatically if missing.
|
||||
|
||||
:param bc: barcode format, see :py:func`~escpos.Escpos.barcode`
|
||||
:param code: alphanumeric data to be printed as bar code, see :py:func`~escpos.Escpos.barcode`
|
||||
:return: bool
|
||||
"""
|
||||
if bc not in BARCODE_FORMATS:
|
||||
return False
|
||||
|
||||
bounds, regex = BARCODE_FORMATS[bc]
|
||||
return any(bound[0] <= len(code) <= bound[1] for bound in bounds) and re_match(regex, code)
|
||||
|
||||
def barcode(self, code, bc, height=64, width=3, pos='BELOW', font='A',
|
||||
align_ct=True, function_type=None, check=True):
|
||||
""" Print Barcode
|
||||
|
||||
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.
|
||||
be supported by the unit. By default, this method will check whether your barcode text is correct, that is
|
||||
the characters and lengths are supported by ESCPOS. Call the method with `check=False` to disable the check, but
|
||||
note that 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.
|
||||
|
||||
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
|
||||
@@ -352,6 +392,10 @@ class Escpos(object):
|
||||
function based on the current profile.
|
||||
*default*: A
|
||||
|
||||
:param check: If this parameter is True, the barcode format will be checked to ensure it meets the bc
|
||||
requirements as defigned in the esc/pos documentation. See py:func:`~escpos.Escpos.check_barcode`
|
||||
for more information. *default*: True.
|
||||
|
||||
:raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`,
|
||||
:py:exc:`~escpos.exceptions.BarcodeTypeError`,
|
||||
:py:exc:`~escpos.exceptions.BarcodeCodeError`
|
||||
@@ -364,21 +408,28 @@ class Escpos(object):
|
||||
if bc in BARCODE_TYPES['B']:
|
||||
if not self.profile.supports(BARCODE_B):
|
||||
raise BarcodeTypeError((
|
||||
"Barcode type '{bc} not supported for "
|
||||
"the current printer profile").format(bc=bc))
|
||||
"Barcode type '{bc}' not supported for "
|
||||
'the current printer profile').format(bc=bc))
|
||||
function_type = 'B'
|
||||
else:
|
||||
raise BarcodeTypeError((
|
||||
"Barcode type '{bc} is not valid").format(bc=bc))
|
||||
"Barcode type '{bc}' is not valid").format(bc=bc))
|
||||
|
||||
bc_types = BARCODE_TYPES[function_type.upper()]
|
||||
if bc.upper() not in bc_types.keys():
|
||||
raise BarcodeTypeError((
|
||||
"Barcode type '{bc}' not valid for barcode function type "
|
||||
"{function_type}").format(
|
||||
"Barcode '{bc}' not valid for barcode function type "
|
||||
'{function_type}').format(
|
||||
bc=bc,
|
||||
function_type=function_type,
|
||||
))
|
||||
function_type=function_type,)
|
||||
)
|
||||
|
||||
if check and not self.check_barcode(bc, code):
|
||||
raise BarcodeCodeError((
|
||||
"Barcode '{code}' not in a valid format for type '{bc}'").format(
|
||||
code=code,
|
||||
bc=bc,
|
||||
))
|
||||
|
||||
# Align Bar Code()
|
||||
if align_ct:
|
||||
@@ -387,30 +438,30 @@ class Escpos(object):
|
||||
if 1 <= height <= 255:
|
||||
self._raw(BARCODE_HEIGHT + six.int2byte(height))
|
||||
else:
|
||||
raise BarcodeSizeError("height = {height}".format(height=height))
|
||||
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))
|
||||
raise BarcodeSizeError('width = {width}'.format(width=width))
|
||||
# Font
|
||||
if font.upper() == "B":
|
||||
if font.upper() == 'B':
|
||||
self._raw(BARCODE_FONT_B)
|
||||
else: # DEFAULT FONT: A
|
||||
self._raw(BARCODE_FONT_A)
|
||||
# Position
|
||||
if pos.upper() == "OFF":
|
||||
if pos.upper() == 'OFF':
|
||||
self._raw(BARCODE_TXT_OFF)
|
||||
elif pos.upper() == "BOTH":
|
||||
elif pos.upper() == 'BOTH':
|
||||
self._raw(BARCODE_TXT_BTH)
|
||||
elif pos.upper() == "ABOVE":
|
||||
elif pos.upper() == 'ABOVE':
|
||||
self._raw(BARCODE_TXT_ABV)
|
||||
else: # DEFAULT POSITION: BELOW
|
||||
self._raw(BARCODE_TXT_BLW)
|
||||
|
||||
self._raw(bc_types[bc.upper()])
|
||||
|
||||
if function_type.upper() == "B":
|
||||
if function_type.upper() == 'B':
|
||||
self._raw(six.int2byte(len(code)))
|
||||
|
||||
# Print Code
|
||||
@@ -419,7 +470,7 @@ class Escpos(object):
|
||||
else:
|
||||
raise BarcodeCodeError()
|
||||
|
||||
if function_type.upper() == "A":
|
||||
if function_type.upper() == 'A':
|
||||
self._raw(NUL)
|
||||
|
||||
def soft_barcode(self, barcode_type, data, impl='bitImageColumn',
|
||||
@@ -437,11 +488,12 @@ class Escpos(object):
|
||||
barcode_class = barcode.get_barcode_class(barcode_type)
|
||||
my_code = barcode_class(data, writer=image_writer)
|
||||
|
||||
my_code.write("/dev/null", {
|
||||
'module_height': module_height,
|
||||
'module_width': module_width,
|
||||
'text_distance': text_distance
|
||||
})
|
||||
with open(os.devnull, 'wb') as nullfile:
|
||||
my_code.write(nullfile, {
|
||||
'module_height': module_height,
|
||||
'module_width': module_width,
|
||||
'text_distance': text_distance
|
||||
})
|
||||
|
||||
# Retrieve the Pillow image and print it
|
||||
image = my_code.writer._image
|
||||
@@ -459,6 +511,28 @@ class Escpos(object):
|
||||
txt = six.text_type(txt)
|
||||
self.magic.write(txt)
|
||||
|
||||
def textln(self, txt=''):
|
||||
"""Print alpha-numeric text with a newline
|
||||
|
||||
The text has to be encoded in the currently selected codepage.
|
||||
The input text has to be encoded in unicode.
|
||||
|
||||
:param txt: text to be printed with a newline
|
||||
:raises: :py:exc:`~escpos.exceptions.TextError`
|
||||
"""
|
||||
self.text('{}\n'.format(txt))
|
||||
|
||||
def ln(self, count=1):
|
||||
"""Print a newline or more
|
||||
|
||||
:param count: number of newlines to print
|
||||
:raises: :py:exc:`ValueError` if count < 0
|
||||
"""
|
||||
if count < 0:
|
||||
raise ValueError('Count cannot be lesser than 0')
|
||||
if count > 0:
|
||||
self.text('\n' * count)
|
||||
|
||||
def block_text(self, txt, font=None, columns=None):
|
||||
""" Text is printed wrapped to specified columns
|
||||
|
||||
@@ -516,8 +590,7 @@ class Escpos(object):
|
||||
"""
|
||||
|
||||
if custom_size:
|
||||
if 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and\
|
||||
isinstance(height, int):
|
||||
if 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and isinstance(height, int):
|
||||
size_byte = TXT_STYLE['width'][width] + TXT_STYLE['height'][height]
|
||||
self._raw(TXT_SIZE + six.int2byte(size_byte))
|
||||
else:
|
||||
@@ -565,12 +638,11 @@ class Escpos(object):
|
||||
return
|
||||
|
||||
if divisor not in LINESPACING_FUNCS:
|
||||
raise ValueError("divisor must be either 360, 180 or 60")
|
||||
if (divisor in [360, 180]
|
||||
and (not(0 <= spacing <= 255))):
|
||||
raise ValueError("spacing must be a int between 0 and 255 when divisor is 360 or 180")
|
||||
raise ValueError('divisor must be either 360, 180 or 60')
|
||||
if (divisor in [360, 180] and (not(0 <= spacing <= 255))):
|
||||
raise ValueError('spacing must be a int between 0 and 255 when divisor is 360 or 180')
|
||||
if divisor == 60 and (not(0 <= spacing <= 85)):
|
||||
raise ValueError("spacing must be a int between 0 and 85 when divisor is 60")
|
||||
raise ValueError('spacing must be a int between 0 and 85 when divisor is 60')
|
||||
|
||||
self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing))
|
||||
|
||||
@@ -598,12 +670,12 @@ class Escpos(object):
|
||||
if mode not in ('FULL', 'PART'):
|
||||
raise ValueError("Mode must be one of ('FULL', 'PART')")
|
||||
|
||||
if mode == "PART":
|
||||
if mode == 'PART':
|
||||
if self.profile.supports('paperPartCut'):
|
||||
self._raw(PAPER_PART_CUT)
|
||||
elif self.profile.supports('paperFullCut'):
|
||||
self._raw(PAPER_FULL_CUT)
|
||||
elif mode == "FULL":
|
||||
elif mode == 'FULL':
|
||||
if self.profile.supports('paperFullCut'):
|
||||
self._raw(PAPER_FULL_CUT)
|
||||
elif self.profile.supports('paperPartCut'):
|
||||
@@ -625,8 +697,8 @@ class Escpos(object):
|
||||
else:
|
||||
try:
|
||||
self._raw(CD_KICK_DEC_SEQUENCE(*pin))
|
||||
except:
|
||||
raise CashDrawerError()
|
||||
except TypeError as err:
|
||||
raise CashDrawerError(err)
|
||||
|
||||
def linedisplay_select(self, select_display=False):
|
||||
""" Selects the line display or the printer
|
||||
@@ -656,6 +728,7 @@ class Escpos(object):
|
||||
|
||||
You should connect a line display to your printer. You can do this by daisy-chaining
|
||||
the display between your computer and printer.
|
||||
|
||||
:param text: Text to display
|
||||
"""
|
||||
self.linedisplay_select(select_display=True)
|
||||
@@ -672,11 +745,11 @@ class Escpos(object):
|
||||
* SELECT
|
||||
* RESET
|
||||
"""
|
||||
if hw.upper() == "INIT":
|
||||
if hw.upper() == 'INIT':
|
||||
self._raw(HW_INIT)
|
||||
elif hw.upper() == "SELECT":
|
||||
elif hw.upper() == 'SELECT':
|
||||
self._raw(HW_SELECT)
|
||||
elif hw.upper() == "RESET":
|
||||
elif hw.upper() == 'RESET':
|
||||
self._raw(HW_RESET)
|
||||
else: # DEFAULT: DOES NOTHING
|
||||
pass
|
||||
@@ -691,9 +764,9 @@ class Escpos(object):
|
||||
"""
|
||||
if 0 <= n <= 255:
|
||||
# ESC d n
|
||||
self._raw(ESC + b"d" + six.int2byte(n))
|
||||
self._raw(ESC + b'd' + six.int2byte(n))
|
||||
else:
|
||||
raise ValueError("n must be betwen 0 and 255")
|
||||
raise ValueError('n must be betwen 0 and 255')
|
||||
|
||||
def control(self, ctl, count=5, tab_size=8):
|
||||
""" Feed control sequences
|
||||
@@ -711,13 +784,13 @@ class Escpos(object):
|
||||
:raises: :py:exc:`~escpos.exceptions.TabPosError`
|
||||
"""
|
||||
# Set position
|
||||
if ctl.upper() == "LF":
|
||||
if ctl.upper() == 'LF':
|
||||
self._raw(CTL_LF)
|
||||
elif ctl.upper() == "FF":
|
||||
elif ctl.upper() == 'FF':
|
||||
self._raw(CTL_FF)
|
||||
elif ctl.upper() == "CR":
|
||||
elif ctl.upper() == 'CR':
|
||||
self._raw(CTL_CR)
|
||||
elif ctl.upper() == "HT":
|
||||
elif ctl.upper() == 'HT':
|
||||
if not (0 <= count <= 32 and
|
||||
1 <= tab_size <= 255 and
|
||||
count * tab_size < 256):
|
||||
@@ -728,7 +801,7 @@ class Escpos(object):
|
||||
for iterator in range(1, count):
|
||||
self._raw(six.int2byte(iterator * tab_size))
|
||||
self._raw(NUL)
|
||||
elif ctl.upper() == "VT":
|
||||
elif ctl.upper() == 'VT':
|
||||
self._raw(CTL_VT)
|
||||
|
||||
def panel_buttons(self, enable=True):
|
||||
@@ -754,26 +827,58 @@ class Escpos(object):
|
||||
else:
|
||||
self._raw(PANEL_BUTTON_OFF)
|
||||
|
||||
def query_status(self):
|
||||
""" Queries the printer for its status, and returns an array of integers containing it.
|
||||
:rtype: array(integer)"""
|
||||
self._raw(RT_STATUS_ONLINE)
|
||||
def query_status(self, mode):
|
||||
"""
|
||||
Queries the printer for its status, and returns an array of integers containing it.
|
||||
|
||||
:param mode: Integer that sets the status mode queried to the printer.
|
||||
- RT_STATUS_ONLINE: Printer status.
|
||||
- RT_STATUS_PAPER: Paper sensor.
|
||||
:rtype: array(integer)
|
||||
"""
|
||||
self._raw(mode)
|
||||
time.sleep(1)
|
||||
status = self._read()
|
||||
return status or [RT_MASK_ONLINE]
|
||||
return status
|
||||
|
||||
def is_online(self):
|
||||
""" Queries the printer its online status.
|
||||
When online, returns True; False otherwise.
|
||||
:rtype: bool: True if online, False if offline."""
|
||||
return not (self.query_status()[0] & RT_MASK_ONLINE)
|
||||
"""
|
||||
Queries the online status of the printer.
|
||||
|
||||
:returns: When online, returns ``True``; ``False`` otherwise.
|
||||
:rtype: bool
|
||||
"""
|
||||
status = self.query_status(RT_STATUS_ONLINE)
|
||||
if len(status) == 0:
|
||||
return False
|
||||
return not (status[0] & RT_MASK_ONLINE)
|
||||
|
||||
def paper_status(self):
|
||||
"""
|
||||
Queries the paper status of the printer.
|
||||
|
||||
Returns 2 if there is plenty of paper, 1 if the paper has arrived to
|
||||
the near-end sensor and 0 if there is no paper.
|
||||
|
||||
:returns: 2: Paper is adequate. 1: Paper ending. 0: No paper.
|
||||
:rtype: int
|
||||
"""
|
||||
status = self.query_status(RT_STATUS_PAPER)
|
||||
if len(status) == 0:
|
||||
return 2
|
||||
if (status[0] & RT_MASK_NOPAPER == RT_MASK_NOPAPER):
|
||||
return 0
|
||||
if (status[0] & RT_MASK_LOWPAPER == RT_MASK_LOWPAPER):
|
||||
return 1
|
||||
if (status[0] & RT_MASK_PAPER == RT_MASK_PAPER):
|
||||
return 2
|
||||
|
||||
|
||||
class EscposIO(object):
|
||||
"""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.
|
||||
and assign it to the EscposIO class.
|
||||
This example explains the usage:
|
||||
|
||||
.. code-block:: Python
|
||||
@@ -822,16 +927,16 @@ class EscposIO(object):
|
||||
elif isinstance(text, list) or isinstance(text, tuple):
|
||||
lines = text
|
||||
else:
|
||||
lines = ["{0}".format(text), ]
|
||||
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))
|
||||
self.printer.text(u'{0}\n'.format(line))
|
||||
else:
|
||||
self.printer.text("{0}\n".format(line))
|
||||
self.printer.text('{0}\n'.format(line))
|
||||
|
||||
def close(self):
|
||||
""" called upon closing the `with`-statement
|
||||
|
@@ -51,13 +51,13 @@ class BarcodeTypeError(Error):
|
||||
one of those specified in :py:meth:`escpos.escpos.Escpos.barcode`.
|
||||
The returned error code is `10`.
|
||||
"""
|
||||
def __init__(self, msg=""):
|
||||
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)
|
||||
return 'No Barcode type is defined ({msg})'.format(msg=self.msg)
|
||||
|
||||
|
||||
class BarcodeSizeError(Error):
|
||||
@@ -67,28 +67,29 @@ class BarcodeSizeError(Error):
|
||||
The size of the barcode has to be in the range that is specified in :py:meth:`escpos.escpos.Escpos.barcode`.
|
||||
The resulting returncode is `20`.
|
||||
"""
|
||||
def __init__(self, msg=""):
|
||||
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)
|
||||
return 'Barcode size is out of range ({msg})'.format(msg=self.msg)
|
||||
|
||||
|
||||
class BarcodeCodeError(Error):
|
||||
""" No Barcode code was supplied.
|
||||
""" No Barcode code was supplied, or it is incorrect.
|
||||
|
||||
No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode`.
|
||||
No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode` or the the `check` parameter
|
||||
was True and the check failed.
|
||||
The returncode for this exception is `30`.
|
||||
"""
|
||||
def __init__(self, msg=""):
|
||||
def __init__(self, msg=''):
|
||||
Error.__init__(self, msg)
|
||||
self.msg = msg
|
||||
self.resultcode = 30
|
||||
|
||||
def __str__(self):
|
||||
return "No Barcode code was supplied ({msg})".format(msg=self.msg)
|
||||
return 'No Barcode code was supplied ({msg})'.format(msg=self.msg)
|
||||
|
||||
|
||||
class ImageSizeError(Error):
|
||||
@@ -96,7 +97,7 @@ class ImageSizeError(Error):
|
||||
|
||||
The returncode for this exception is `40`.
|
||||
"""
|
||||
def __init__(self, msg=""):
|
||||
def __init__(self, msg=''):
|
||||
Error.__init__(self, msg)
|
||||
self.msg = msg
|
||||
self.resultcode = 40
|
||||
@@ -110,13 +111,13 @@ class ImageWidthError(Error):
|
||||
|
||||
The return code for this exception is `41`.
|
||||
"""
|
||||
def __init__(self, msg=""):
|
||||
def __init__(self, msg=''):
|
||||
Error.__init__(self, msg)
|
||||
self.msg = msg
|
||||
self.resultcode = 41
|
||||
|
||||
def __str__(self):
|
||||
return "Image width is too large ({msg})".format(msg=self.msg)
|
||||
return 'Image width is too large ({msg})'.format(msg=self.msg)
|
||||
|
||||
|
||||
class TextError(Error):
|
||||
@@ -125,13 +126,13 @@ class TextError(Error):
|
||||
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=""):
|
||||
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 ({msg})".format(msg=self.msg)
|
||||
return 'Text string must be supplied to the text() method ({msg})'.format(msg=self.msg)
|
||||
|
||||
|
||||
class CashDrawerError(Error):
|
||||
@@ -140,13 +141,13 @@ class CashDrawerError(Error):
|
||||
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=""):
|
||||
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 ({msg})".format(msg=self.msg)
|
||||
return 'Valid pin must be set to send pulse ({msg})'.format(msg=self.msg)
|
||||
|
||||
|
||||
class TabPosError(Error):
|
||||
@@ -156,13 +157,13 @@ class TabPosError(Error):
|
||||
This exception is raised by :py:meth:`escpos.escpos.Escpos.control`.
|
||||
The returncode for this exception is `70`.
|
||||
"""
|
||||
def __init__(self, msg=""):
|
||||
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 ({msg})".format(msg=self.msg)
|
||||
return 'Valid tab positions must be in the range 0 to 16 ({msg})'.format(msg=self.msg)
|
||||
|
||||
|
||||
class CharCodeError(Error):
|
||||
@@ -171,13 +172,13 @@ class CharCodeError(Error):
|
||||
The supplied charcode-name in :py:meth:`escpos.escpos.Escpos.charcode` is unknown.
|
||||
Ths returncode for this exception is `80`.
|
||||
"""
|
||||
def __init__(self, msg=""):
|
||||
def __init__(self, msg=''):
|
||||
Error.__init__(self, msg)
|
||||
self.msg = msg
|
||||
self.resultcode = 80
|
||||
|
||||
def __str__(self):
|
||||
return "Valid char code must be set ({msg})".format(msg=self.msg)
|
||||
return 'Valid char code must be set ({msg})'.format(msg=self.msg)
|
||||
|
||||
|
||||
class USBNotFoundError(Error):
|
||||
@@ -186,13 +187,13 @@ class USBNotFoundError(Error):
|
||||
The USB device seems to be not plugged in.
|
||||
Ths returncode for this exception is `90`.
|
||||
"""
|
||||
def __init__(self, msg=""):
|
||||
def __init__(self, msg=''):
|
||||
Error.__init__(self, msg)
|
||||
self.msg = msg
|
||||
self.resultcode = 90
|
||||
|
||||
def __str__(self):
|
||||
return "USB device not found ({msg})".format(msg=self.msg)
|
||||
return 'USB device not found ({msg})'.format(msg=self.msg)
|
||||
|
||||
|
||||
class SetVariableError(Error):
|
||||
@@ -201,13 +202,13 @@ class SetVariableError(Error):
|
||||
Check set variables against minimum and maximum values
|
||||
Ths returncode for this exception is `100`.
|
||||
"""
|
||||
def __init__(self, msg=""):
|
||||
def __init__(self, msg=''):
|
||||
Error.__init__(self, msg)
|
||||
self.msg = msg
|
||||
self.resultcode = 100
|
||||
|
||||
def __str__(self):
|
||||
return "Set variable out of range ({msg})".format(msg=self.msg)
|
||||
return 'Set variable out of range ({msg})'.format(msg=self.msg)
|
||||
|
||||
|
||||
# Configuration errors
|
||||
@@ -218,13 +219,13 @@ class ConfigNotFoundError(Error):
|
||||
The default or passed configuration file could not be read
|
||||
Ths returncode for this exception is `200`.
|
||||
"""
|
||||
def __init__(self, msg=""):
|
||||
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)
|
||||
return 'Configuration not found ({msg})'.format(msg=self.msg)
|
||||
|
||||
|
||||
class ConfigSyntaxError(Error):
|
||||
@@ -233,13 +234,13 @@ class ConfigSyntaxError(Error):
|
||||
The syntax is incorrect
|
||||
Ths returncode for this exception is `210`.
|
||||
"""
|
||||
def __init__(self, msg=""):
|
||||
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)
|
||||
return 'Configuration syntax is invalid ({msg})'.format(msg=self.msg)
|
||||
|
||||
|
||||
class ConfigSectionMissingError(Error):
|
||||
@@ -248,10 +249,10 @@ class ConfigSectionMissingError(Error):
|
||||
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=""):
|
||||
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)
|
||||
return 'Configuration section is missing ({msg})'.format(msg=self.msg)
|
||||
|
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Image format handling class
|
||||
|
||||
This module contains the image format handler :py:class:`EscposImage`.
|
||||
@@ -14,6 +15,7 @@ from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import math
|
||||
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
|
||||
@@ -42,14 +44,14 @@ class EscposImage(object):
|
||||
# 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 = 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")
|
||||
im = im.convert('L')
|
||||
# Invert: Only works on 'L' images
|
||||
im = ImageOps.invert(im)
|
||||
# Pure black and white
|
||||
self._im = im.convert("1")
|
||||
self._im = im.convert('1')
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
@@ -105,7 +107,7 @@ class EscposImage(object):
|
||||
:param fragment_height: height of fragment
|
||||
:return: list of PIL objects
|
||||
"""
|
||||
passes = int(math.ceil(self.height/fragment_height))
|
||||
passes = int(math.ceil(self.height // fragment_height))
|
||||
fragments = []
|
||||
for n in range(0, passes):
|
||||
left = 0
|
||||
@@ -115,3 +117,19 @@ class EscposImage(object):
|
||||
box = (left, upper, right, lower)
|
||||
fragments.append(self.img_original.crop(box))
|
||||
return fragments
|
||||
|
||||
def center(self, max_width):
|
||||
"""In-place image centering
|
||||
|
||||
:param: Maximum width in order to deduce x offset for centering
|
||||
:return: None
|
||||
"""
|
||||
old_width, height = self._im.size
|
||||
new_size = (max_width, height)
|
||||
|
||||
new_im = Image.new('1', new_size)
|
||||
paste_x = int((max_width - old_width) / 2)
|
||||
|
||||
new_im.paste(self._im, (paste_x, 0))
|
||||
|
||||
self._im = new_im
|
||||
|
@@ -33,7 +33,7 @@ def encode_katakana(text):
|
||||
# TODO doesn't this discard all that is not in the map? Can we be sure that the input does contain only
|
||||
# encodable characters? We could at least throw an exception if encoding is not possible.
|
||||
pass
|
||||
return b"".join(encoded)
|
||||
return b''.join(encoded)
|
||||
|
||||
|
||||
TXT_ENC_KATAKANA_MAP = {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Magic Encode
|
||||
|
||||
@@ -18,10 +18,12 @@ from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from builtins import bytes
|
||||
|
||||
import six
|
||||
|
||||
from .codepages import CodePages
|
||||
from .constants import CODEPAGE_CHANGE
|
||||
from .exceptions import Error
|
||||
from .codepages import CodePages
|
||||
import six
|
||||
|
||||
|
||||
class Encoder(object):
|
||||
@@ -62,7 +64,7 @@ class Encoder(object):
|
||||
raise ValueError((
|
||||
'Encoding "{}" cannot be used for the current profile. '
|
||||
'Valid encodings are: {}'
|
||||
).format(encoding, ','.join(self.codepages.keys())))
|
||||
).format(encoding, ','.join(self.codepages.keys())))
|
||||
return encoding
|
||||
|
||||
@staticmethod
|
||||
@@ -75,11 +77,11 @@ class Encoder(object):
|
||||
"""
|
||||
codepage = CodePages.get_encoding(encoding)
|
||||
if 'data' in codepage:
|
||||
encodable_chars = list("".join(codepage['data']))
|
||||
encodable_chars = list(''.join(codepage['data']))
|
||||
assert(len(encodable_chars) == 128)
|
||||
return encodable_chars
|
||||
elif 'python_encode' in codepage:
|
||||
encodable_chars = [u" "] * 128
|
||||
encodable_chars = [u' '] * 128
|
||||
for i in range(0, 128):
|
||||
codepoint = i + 128
|
||||
try:
|
||||
@@ -280,7 +282,7 @@ class MagicEncode(object):
|
||||
|
||||
def write_with_encoding(self, encoding, text):
|
||||
if text is not None and type(text) is not six.text_type:
|
||||
raise Error("The supplied text has to be unicode, but is of type {type}.".format(
|
||||
raise Error('The supplied text has to be unicode, but is of type {type}.'.format(
|
||||
type=type(text)
|
||||
))
|
||||
|
||||
|
@@ -13,10 +13,12 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import socket
|
||||
|
||||
import serial
|
||||
|
||||
import usb.core
|
||||
import usb.util
|
||||
import serial
|
||||
import socket
|
||||
|
||||
from .escpos import Escpos
|
||||
from .exceptions import USBNotFoundError
|
||||
@@ -54,7 +56,7 @@ class Usb(Escpos):
|
||||
""" 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.")
|
||||
raise USBNotFoundError('Device not found or cable not plugged in.')
|
||||
|
||||
check_driver = None
|
||||
|
||||
@@ -68,13 +70,13 @@ class Usb(Escpos):
|
||||
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)))
|
||||
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)))
|
||||
print('Could not set configuration: {0}'.format(str(e)))
|
||||
|
||||
def _raw(self, msg):
|
||||
""" Print any command sent in raw format
|
||||
@@ -107,7 +109,7 @@ class Serial(Escpos):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1,
|
||||
def __init__(self, devfile='/dev/ttyS0', baudrate=9600, bytesize=8, timeout=1,
|
||||
parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE,
|
||||
xonxoff=False, dsrdtr=True, *args, **kwargs):
|
||||
"""
|
||||
@@ -143,9 +145,9 @@ class Serial(Escpos):
|
||||
xonxoff=self.xonxoff, dsrdtr=self.dsrdtr)
|
||||
|
||||
if self.device is not None:
|
||||
print("Serial printer enabled")
|
||||
print('Serial printer enabled')
|
||||
else:
|
||||
print("Unable to open serial printer on: {0}".format(str(self.devfile)))
|
||||
print('Unable to open serial printer on: {0}'.format(str(self.devfile)))
|
||||
|
||||
def _raw(self, msg):
|
||||
""" Print any command sent in raw format
|
||||
@@ -155,6 +157,10 @@ class Serial(Escpos):
|
||||
"""
|
||||
self.device.write(msg)
|
||||
|
||||
def _read(self):
|
||||
""" Reads a data buffer and returns it to the caller. """
|
||||
return self.device.read(16)
|
||||
|
||||
def close(self):
|
||||
""" Close Serial interface """
|
||||
if self.device is not None and self.device.is_open:
|
||||
@@ -205,7 +211,7 @@ class Network(Escpos):
|
||||
self.device.connect((self.host, self.port))
|
||||
|
||||
if self.device is None:
|
||||
print("Could not open socket for {0}".format(self.host))
|
||||
print('Could not open socket for {0}'.format(self.host))
|
||||
|
||||
def _raw(self, msg):
|
||||
""" Print any command sent in raw format
|
||||
@@ -236,10 +242,10 @@ class File(Escpos):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs):
|
||||
def __init__(self, devfile='/dev/usb/lp0', auto_flush=True, *args, **kwargs):
|
||||
"""
|
||||
|
||||
:param devfile : Device file under dev filesystem
|
||||
:param devfile: Device file under dev filesystem
|
||||
:param auto_flush: automatically call flush after every call of _raw()
|
||||
"""
|
||||
Escpos.__init__(self, *args, **kwargs)
|
||||
@@ -249,10 +255,10 @@ class File(Escpos):
|
||||
|
||||
def open(self):
|
||||
""" Open system file """
|
||||
self.device = open(self.devfile, "wb")
|
||||
self.device = open(self.devfile, 'wb')
|
||||
|
||||
if self.device is None:
|
||||
print("Could not open the specified file {0}".format(self.devfile))
|
||||
print('Could not open the specified file {0}'.format(self.devfile))
|
||||
|
||||
def flush(self):
|
||||
""" Flush printing content """
|
||||
@@ -308,5 +314,13 @@ class Dummy(Escpos):
|
||||
""" Get the data that was sent to this printer """
|
||||
return b''.join(self._output_list)
|
||||
|
||||
def clear(self):
|
||||
""" Clear the buffer of the printer
|
||||
|
||||
This method can be called if you send the contents to a physical printer
|
||||
and want to use the Dummy printer for new output.
|
||||
"""
|
||||
del self._output_list[:]
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
@@ -7,7 +7,7 @@ from __future__ import unicode_literals
|
||||
import escpos.printer as printer
|
||||
from escpos.constants import BARCODE_TYPE_A, BARCODE_TYPE_B
|
||||
from escpos.capabilities import Profile, BARCODE_B
|
||||
from escpos.exceptions import BarcodeTypeError
|
||||
from escpos.exceptions import BarcodeTypeError, BarcodeCodeError
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -36,3 +36,17 @@ def test_lacks_support(bctype, supports_b):
|
||||
instance.barcode('test', bctype)
|
||||
|
||||
assert instance.output == b''
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bctype,data", [
|
||||
('EAN13', 'AA'),
|
||||
('CODE128', '{D2354AA'),
|
||||
])
|
||||
def test_code_check(bctype, data):
|
||||
"""should raise an error if the barcode code is invalid.
|
||||
"""
|
||||
instance = printer.Dummy()
|
||||
with pytest.raises(BarcodeCodeError):
|
||||
instance.barcode(data, bctype)
|
||||
|
||||
assert instance.output == b''
|
||||
|
19
test/test_function_cashdraw.py
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/python
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import escpos.printer as printer
|
||||
from escpos.exceptions import CashDrawerError
|
||||
import pytest
|
||||
|
||||
|
||||
def test_raise_CashDrawerError():
|
||||
"""should raise an error if the sequence is invalid.
|
||||
"""
|
||||
instance = printer.Dummy()
|
||||
with pytest.raises(CashDrawerError):
|
||||
# call with sequence that is too long
|
||||
instance.cashdraw([1,1,1,1,1,1])
|
||||
|
104
test/test_function_check_barcode.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import escpos.printer as printer
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bctype,data", [
|
||||
('UPC-A', '01234567890'),
|
||||
('UPC-A', '012345678905'),
|
||||
('UPC-E', '01234567'),
|
||||
('UPC-E', '0123456'),
|
||||
('UPC-E', '012345678905'),
|
||||
('EAN13', '0123456789012'),
|
||||
('EAN13', '012345678901'),
|
||||
('EAN8', '01234567'),
|
||||
('EAN8', '0123456'),
|
||||
('CODE39', 'ABC-1234'),
|
||||
('CODE39', 'ABC-1234-$$-+A'),
|
||||
('CODE39', '*WIKIPEDIA*'),
|
||||
('ITF', '010203040506070809'),
|
||||
('ITF', '11221133113344556677889900'),
|
||||
('CODABAR', 'A2030405060B'),
|
||||
('CODABAR', 'C11221133113344556677889900D'),
|
||||
('CODABAR', 'D0D'),
|
||||
('NW7', 'A2030405060B'),
|
||||
('NW7', 'C11221133113344556677889900D'),
|
||||
('NW7', 'D0D'),
|
||||
('CODE93', 'A2030405060B'),
|
||||
('CODE93', '+:$&23-7@$'),
|
||||
('CODE93', 'D0D'),
|
||||
('CODE128', '{A2030405060B'),
|
||||
('CODE128', '{C+:$&23-7@$'),
|
||||
('CODE128', '{B0D'),
|
||||
('GS1-128', '{A2030405060B'),
|
||||
('GS1-128', '{C+:$&23-7@$'),
|
||||
('GS1-128', '{B0D'),
|
||||
('GS1 DATABAR OMNIDIRECTIONAL', '0123456789123'),
|
||||
('GS1 DATABAR TRUNCATED', '0123456789123'),
|
||||
('GS1 DATABAR LIMITED', '0123456789123'),
|
||||
('GS1 DATABAR EXPANDED', '(9A{A20304+-%&06a0B'),
|
||||
('GS1 DATABAR EXPANDED', '(1 {C+:&23-7%'),
|
||||
('GS1 DATABAR EXPANDED', '(00000001234567678'),
|
||||
])
|
||||
def test_check_valid_barcode(bctype, data):
|
||||
assert (printer.Escpos.check_barcode(bctype, data))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bctype,data", [
|
||||
('UPC-A', '01234567890123'), # too long
|
||||
('UPC-A', '0123456789'), # too short
|
||||
('UPC-A', '72527273-711'), # invalid '-'
|
||||
('UPC-A', 'A12345678901'), # invalid 'A'
|
||||
('UPC-E', '01234567890123'), # too long
|
||||
('UPC-E', '012345'), # too short
|
||||
('UPC-E', '72527-2'), # invalid '-'
|
||||
('UPC-E', 'A123456'), # invalid 'A'
|
||||
('EAN13', '0123456789'), # too short
|
||||
('EAN13', 'A123456789012'), # invalid 'A'
|
||||
('EAN13', '012345678901234'), # too long
|
||||
('EAN8', '012345'), # too short
|
||||
('EAN8', 'A123456789012'), # invalid 'A'
|
||||
('EAN8', '012345678901234'), # too long
|
||||
('CODE39', 'ALKJ_34'), # invalid '_'
|
||||
('CODE39', 'A' * 256), # too long
|
||||
('ITF', '010203040'), # odd length
|
||||
('ITF', '0' * 256), # too long
|
||||
('ITF', 'AB01'), # invalid 'A'
|
||||
('CODABAR', '010203040'), # no start/stop
|
||||
('CODABAR', '0' * 256), # too long
|
||||
('CODABAR', 'AB-01F'), # invalid 'B'
|
||||
('NW7', '010203040'), # no start/stop
|
||||
('NW7', '0' * 256), # too long
|
||||
('NW7', 'AB-01F'), # invalid 'B'
|
||||
('CODE93', 'é010203040'), # invalid 'é'
|
||||
('CODE93', '0' * 256), # too long
|
||||
('CODE128', '010203040'), # missing leading {
|
||||
('CODE128', '{D2354AA'), # second char not between A-C
|
||||
('CODE128', '0' * 256), # too long
|
||||
('GS1-128', '010203040'), # missing leading {
|
||||
('GS1-128', '{D2354AA'), # second char not between A-C
|
||||
('GS1-128', '0' * 256), # too long
|
||||
('GS1 DATABAR OMNIDIRECTIONAL', '01234567891234'), # too long
|
||||
('GS1 DATABAR OMNIDIRECTIONAL', '012345678912'), # too short
|
||||
('GS1 DATABAR OMNIDIRECTIONAL', '012345678A1234'), # invalid 'A'
|
||||
('GS1 DATABAR TRUNCATED', '01234567891234'), # too long
|
||||
('GS1 DATABAR TRUNCATED', '012345678912'), # too short
|
||||
('GS1 DATABAR TRUNCATED', '012345678A1234'), # invalid 'A'
|
||||
('GS1 DATABAR LIMITED', '01234567891234'), # too long
|
||||
('GS1 DATABAR LIMITED', '012345678912'), # too short
|
||||
('GS1 DATABAR LIMITED', '012345678A1234'), # invalid 'A'
|
||||
('GS1 DATABAR LIMITED', '02345678912341'), # invalid start (should be 01)
|
||||
('GS1 DATABAR EXPANDED', '010203040'), # missing leading (
|
||||
('GS1-128', '(' + ('0' * 256)), # too long
|
||||
('GS1 DATABAR EXPANDED', '(a{D2354AA'), # second char not between 0-9
|
||||
('GS1 DATABAR EXPANDED', 'IT will fail'), # first char not '('
|
||||
])
|
||||
def test_check_invalid_barcode(bctype, data):
|
||||
assert (not printer.Escpos.check_barcode(bctype, data))
|
8
test/test_function_dummy_clear.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from nose.tools import assert_raises
|
||||
from escpos.printer import Dummy
|
||||
|
||||
def test_printer_dummy_clear():
|
||||
printer = Dummy()
|
||||
printer.text("Hello")
|
||||
printer.clear()
|
||||
assert(printer.output == b'')
|
@@ -145,10 +145,8 @@ def test_large_graphics():
|
||||
assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00')
|
||||
|
||||
|
||||
def test_width_too_large():
|
||||
"""
|
||||
Test printing an image that is too large in width.
|
||||
"""
|
||||
@pytest.fixture
|
||||
def dummy_with_width():
|
||||
instance = printer.Dummy()
|
||||
instance.profile.profile_data = {
|
||||
'media': {
|
||||
@@ -157,8 +155,25 @@ def test_width_too_large():
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance
|
||||
|
||||
|
||||
def test_width_too_large(dummy_with_width):
|
||||
"""
|
||||
Test printing an image that is too large in width.
|
||||
"""
|
||||
instance = dummy_with_width
|
||||
|
||||
with pytest.raises(ImageWidthError):
|
||||
instance.image(Image.new("RGB", (385, 200)))
|
||||
|
||||
instance.image(Image.new("RGB", (384, 200)))
|
||||
instance.image(Image.new("RGB", (384, 200)))
|
||||
|
||||
|
||||
def test_center_image(dummy_with_width):
|
||||
instance = dummy_with_width
|
||||
|
||||
with pytest.raises(ImageWidthError):
|
||||
instance.image(Image.new("RGB", (385, 200)), center=True)
|
||||
|
||||
instance.image(Image.new("RGB", (384, 200)), center=True)
|
||||
|
@@ -13,6 +13,8 @@ from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from nose.tools import raises
|
||||
import pytest
|
||||
|
||||
import escpos.printer as printer
|
||||
from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1
|
||||
|
||||
@@ -25,7 +27,6 @@ def test_defaults():
|
||||
b'(k\x07\x001P01234\x1d(k\x03\x001Q0'
|
||||
assert(instance.output == expected)
|
||||
|
||||
|
||||
def test_empty():
|
||||
"""Test QR printing blank code"""
|
||||
instance = printer.Dummy()
|
||||
@@ -81,6 +82,7 @@ def test_invalid_model():
|
||||
instance.qr("1234", native=True, model="Hello")
|
||||
|
||||
|
||||
@pytest.mark.skip("this test has to be debugged")
|
||||
def test_image():
|
||||
"""Test QR as image"""
|
||||
instance = printer.Dummy()
|
||||
@@ -99,3 +101,13 @@ def test_image_invalid_model():
|
||||
"""Test unsupported QR model as image"""
|
||||
instance = printer.Dummy()
|
||||
instance.qr("1234", native=False, model=QR_MODEL_1)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def instance():
|
||||
return printer.Dummy()
|
||||
|
||||
|
||||
def test_center_not_implementer(instance):
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.qr("test", center=True, native=True)
|
||||
|
@@ -13,6 +13,7 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pytest
|
||||
import mock
|
||||
|
||||
from escpos.printer import Dummy
|
||||
@@ -30,3 +31,12 @@ def test_type_of_object_passed_to_image_function(img_function):
|
||||
d.qr("LoremIpsum")
|
||||
args, kwargs = img_function.call_args
|
||||
assert isinstance(args[0], Image.Image)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def instance():
|
||||
return Dummy()
|
||||
|
||||
|
||||
def test_center(instance):
|
||||
instance.qr("LoremIpsum", center=True)
|
||||
|
16
test/test_function_softbarcode.py
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/python
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import escpos.printer as printer
|
||||
import pytest
|
||||
|
||||
|
||||
def test_soft_barcode():
|
||||
"""just execute soft_barcode
|
||||
"""
|
||||
instance = printer.Dummy()
|
||||
instance.soft_barcode("ean8", "1234")
|
||||
|
@@ -39,3 +39,27 @@ def test_block_text():
|
||||
"All the presidents men were eating falafel for breakfast.", font='a')
|
||||
assert printer.output == \
|
||||
b'All the presidents men were eating falafel\nfor breakfast.'
|
||||
|
||||
|
||||
def test_textln():
|
||||
printer = get_printer()
|
||||
printer.textln('hello, world')
|
||||
assert printer.output == b'hello, world\n'
|
||||
|
||||
|
||||
def test_textln_empty():
|
||||
printer = get_printer()
|
||||
printer.textln()
|
||||
assert printer.output == b'\n'
|
||||
|
||||
|
||||
def test_ln():
|
||||
printer = get_printer()
|
||||
printer.ln()
|
||||
assert printer.output == b'\n'
|
||||
|
||||
|
||||
def test_multiple_ln():
|
||||
printer = get_printer()
|
||||
printer.ln(3)
|
||||
assert printer.output == b'\n\n\n'
|
||||
|
@@ -16,7 +16,7 @@ from __future__ import unicode_literals
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from hypothesis import given
|
||||
from hypothesis import given, settings
|
||||
from hypothesis.strategies import text
|
||||
|
||||
import escpos.printer as printer
|
||||
@@ -27,6 +27,8 @@ else:
|
||||
mock_open_call = '__builtin__.open'
|
||||
|
||||
|
||||
@pytest.mark.skip("this test is broken and has to be fixed or discarded")
|
||||
@settings(use_coverage=False)
|
||||
@given(path=text())
|
||||
def test_load_file_printer(mocker, path):
|
||||
"""test the loading of the file-printer"""
|
||||
@@ -37,6 +39,8 @@ def test_load_file_printer(mocker, path):
|
||||
mock_open.assert_called_with(path, "wb")
|
||||
|
||||
|
||||
@pytest.mark.skip("this test is broken and has to be fixed or discarded")
|
||||
@settings(deadline=None, use_coverage=False)
|
||||
@given(txt=text())
|
||||
def test_auto_flush(mocker, txt):
|
||||
"""test auto_flush in file-printer"""
|
||||
@@ -57,6 +61,8 @@ def test_auto_flush(mocker, txt):
|
||||
assert mock_device.flush.called
|
||||
|
||||
|
||||
@pytest.mark.skip("this test is broken and has to be fixed or discarded")
|
||||
@settings(deadline=None, use_coverage=False)
|
||||
@given(txt=text())
|
||||
def test_flush_on_close(mocker, txt):
|
||||
"""test flush on close in file-printer"""
|
||||
|
32
test/test_raise_arbitrary_error.py
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/python
|
||||
"""test the raising of errors with the error module
|
||||
|
||||
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
|
||||
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||
:copyright: Copyright (c) 2017 `python-escpos <https://github.com/python-escpos>`_
|
||||
:license: MIT
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pytest
|
||||
import escpos
|
||||
import escpos.exceptions
|
||||
|
||||
|
||||
def test_raise_error_wrongly():
|
||||
"""raise error the wrong way
|
||||
|
||||
should reproduce https://github.com/python-escpos/python-escpos/issues/257
|
||||
"""
|
||||
with pytest.raises(AttributeError):
|
||||
raise escpos.Error("This should raise an AttributeError.")
|
||||
|
||||
|
||||
def tests_raise_error():
|
||||
"""raise error the right way"""
|
||||
with pytest.raises(escpos.exceptions.Error):
|
||||
raise escpos.exceptions.Error("This should raise an error.")
|
14
tox.ini
@@ -7,22 +7,30 @@ deps = nose
|
||||
coverage
|
||||
scripttest
|
||||
mock
|
||||
pytest
|
||||
pytest!=3.2.0,!=3.3.0
|
||||
pytest-cov
|
||||
pytest-mock
|
||||
hypothesis
|
||||
hypothesis!=3.56.9
|
||||
viivakoodi
|
||||
commands = py.test --cov escpos
|
||||
passenv = ESCPOS_CAPABILITIES_PICKLE_DIR ESCPOS_CAPABILITIES_FILE CI TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* CODECOV_*
|
||||
|
||||
[testenv:docs]
|
||||
basepython = python
|
||||
changedir = doc
|
||||
deps = sphinx>=1.5.1
|
||||
setuptools_scm
|
||||
viivakoodi
|
||||
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
|
||||
|
||||
[testenv:flake8]
|
||||
basepython = python
|
||||
# TODO add flake8-future
|
||||
# TODO add flake8-print after adding logging
|
||||
# TODO add flake8-docstrings
|
||||
deps = flake8
|
||||
flake8-coding
|
||||
flake8-copyright
|
||||
flake8-future-import
|
||||
flake8-quotes
|
||||
flake8-import-order
|
||||
commands = flake8
|
||||
|