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

5 Commits

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

View File

@@ -18,8 +18,7 @@ I have:
**Printer:** Manufacturer Model XVI
<!-- since version 2.0.1 you can type 'python-escpos version' in your shell.
Starting with python-escpos version 3.0, please replace the information below
with the output of `python-escpos version_extended`. -->
Alternatively you could use '__version__' in module escpos. -->
**python-escpos version:** 0.0.0
**python version:** 0.0

View File

@@ -1,5 +1,10 @@
### Contributor checklist
<!-- mark with x between the brackets -->
- [ ] I have read the CONTRIBUTING.rst
- [ ] I have tested my contribution on these devices:
* e.g. Epson TM-T88II
- [ ] My contribution is ready to be merged as is
----------
### Description
### Tested with
_If applicable, please describe with which device you have tested._

View File

@@ -1,23 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "pip"
directory: "/doc"
schedule:
interval: "daily"
- package-ecosystem: "gitsubmodule"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

View File

@@ -1,13 +0,0 @@
name: Lint (Black code style)
on: [push, pull_request]
jobs:
black-code-style:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: psf/black@stable
with:
version: "23.3.0"

View File

@@ -1,66 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 1 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['python']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@@ -1,32 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: Documentation build
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "docs"
docs:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install packages
run:
sudo apt-get update -y &&
sudo apt-get install -y git python3-sphinx graphviz libenchant-2-2 &&
sudo apt-get install -y gcc libcups2-dev python3-dev python3-setuptools &&
sudo pip install tox pycups
- name: Test doc build
run: tox -e docs

View File

@@ -1,55 +0,0 @@
name: Python package on Windows
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
python-version: ['3.11', '3.12']
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4.7.1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest tox tox-gh-actions
If (Test-Path .\requirements.txt) { pip install -r .\requirements.txt }
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E,F,W --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with tox
run: |
tox
env:
ESCPOS_CAPABILITIES_FILE: D:\a\python-escpos\python-escpos\capabilities-data\dist\capabilities.json
- name: Test mypy with tox
run: |
tox -e mypy
env:
ESCPOS_CAPABILITIES_FILE: D:\a\python-escpos\python-escpos\capabilities-data\dist\capabilities.json
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
directory: ./coverage/reports/
env_vars: OS,PYTHON
fail_ci_if_error: true
files: ./coverage.xml,!./cache
flags: unittests
name: coverage-tox-${{ matrix.python-version }}
verbose: true

View File

@@ -1,65 +0,0 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python package
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4.7.1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y graphviz libenchant-2-2 gcc libcups2-dev python3-dev xindy
python -m pip install --upgrade pip
pip install flake8 pytest tox tox-gh-actions
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Check sorting of imports
uses: isort/isort-action@master
with:
requirementsFiles: "requirements.txt doc/requirements.txt"
sortPaths: "./doc ./src ./examples ./test ./setup.py"
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E,F,W --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with tox
run: |
tox
env:
ESCPOS_CAPABILITIES_FILE: /home/runner/work/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
- name: Test mypy with tox
run: |
tox -e mypy
env:
ESCPOS_CAPABILITIES_FILE: /home/runner/work/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
directory: ./coverage/reports/
env_vars: OS,PYTHON
fail_ci_if_error: true
files: ./coverage.xml,!./cache
flags: unittests
name: coverage-tox-${{ matrix.python-version }}
verbose: true

14
.gitignore vendored
View File

@@ -6,7 +6,6 @@ $~
.idea/
.directory
.cache/
settings.json
# temporary data
temp
@@ -21,11 +20,6 @@ dist/
.coverage
src/escpos/version.py
.hypothesis
.pytest_cache/
coverage.xml
# pyenv
.python-version
# testing temporary directories
test/test-cli-output/
@@ -34,11 +28,3 @@ test/test-cli-output/
*.swp
*.swn
*.swo
# vscode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

1
.gitmodules vendored
View File

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

View File

@@ -1,20 +0,0 @@
<dev@pkanzler.de> <patrick.kanzler@fablab.fau.de>
<manpaz@gmail.com> <manpaz@bashlinux.com>
Manuel F Martinez <manpaz@gmail.com> manpaz <manpaz@bashlinux.com>
<emailofdavis@gmail.com> <davis.goglin@oregonicecream.com>
Davis Goglin <emailofdavis@gmail.com> davisgoglin <emailofdavis@gmail.com>
Michael Billington <michael.billington@gmail.com> Michael <michael.billington@gmail.com>
Cody (Quantified Code Bot) <cody@quantifiedcode.com> Cody <cody@quantifiedcode.com>
Renato Lorenzi <renato.lorenzi@senior.com.br> Renato.Lorenzi <renato.lorenzi@senior.com.br>
Ahmed Tahri <nyuubi.10@gmail.com> TAHRI Ahmed <nyuubi.10@gmail.com>
Michael Elsdörfer <michael@elsdoerfer.com> Michael Elsdörfer <michael@elsdoerfer.info>
Juanmi Taboada <juanmi@juanmitaboada.com> Juanmi Taboada <juanmi@juanmitaboada.com>
csoft2k <csoft2k@hotmail.com>
Sergio Pulgarin <sergio.pulgarin@gmail.com>
reck31 <rakesh.gunduka@gmail.com>
Alex Debiasio <alex.debiasio@thinkin.io> <alex.debiasio@studenti.unitn.it>
白月秋见心 <ourweijiang@gmail.com>
Maximilian Wagenbach <maximilian.wagenbach@native-instruments.de>
<belono@users.noreply.github.com> <tiotil.lindeman@gmail.com>
belono <belono@users.noreply.github.com> Benito López <belono@users.noreply.github.com>
Alfredo Orozco <alfredoopa@gmail.com> Alfredo orozco <alfreedom@users.noreply.github.com>

View File

@@ -1,27 +0,0 @@
version: 2
formats:
- epub
- pdf
build:
os: ubuntu-22.04
tools:
python: "3.11"
apt_packages:
- graphviz
- libenchant-2-2
- gcc
- libcups2-dev
- python3-dev
- xindy
sphinx:
configuration: doc/conf.py
submodules:
include:
- capabilities-data
recursive: true
python:
install:
- requirements: doc/requirements.txt
- method: pip
path: .

59
.travis.yml Normal file
View File

@@ -0,0 +1,59 @@
language: python
sudo: false
cache: pip
addons:
apt:
packages:
- graphviz
matrix:
include:
- python: 2.7
env: TOXENV=py27
- python: 3.3
env: TOXENV=py33
- python: 3.4
env: TOXENV=py34
- python: 3.5
env: TOXENV=py35
- python: 3.6
env: TOXENV=py36
- python: 3.6-dev
env: TOXENV=py36
- python: nightly
env: TOXENV=py37
- python: pypy
env: TOXENV=pypy
- python: pypy3
env: TOXENV=pypy3
- python: 2.7
env: TOXENV=docs
- python: 2.7
env: TOXENV=flake8
- python: 3.6
env: TOXENV=flake8
allow_failures:
- python: 3.6-dev
- python: nightly
- python: pypy3
before_install:
- pip install tox codecov 'sphinx>=1.5.1'
script:
- tox
- codecov
notifications:
email:
on_success: never
on_failure: change
deploy:
# Github deployment
- provider: releases
api_key:
secure: oiR3r5AIx9ENIRtbUKIxorRx8GMv4BxgVIZcieXbgSTN4DBZdRWdzs1Xxngu/90Xf79G0X+XGxZyXrYN7eFFNp0kUYj8kwZ1aS/dyR88scskumERWi1Hv5WUJrYGrDe7PcjNGsJ2jw0nNnRPKG87Y84aR4lQygyGBSlDcdrOBnBv0sHYJMxRvHSRkGgWpur06QIOGOk4oOipTXR/7E9cg3YQC5nvZAf2QiprwTa8IcOSFlZQPykEVRYSiAgXrgqBYcZzpX0hAGuIBv7DmPI2ORTF+t79Wbhxhnho3gGJleDv7Z96//sf1vQNCG6qOgeIc9ZY08Jm1AwXQoW0p6F1/XcEPxeyPDkXJzlojE9rjYNLCPL4gxb/LESEuUafm0U4JGMsZ6hnsBOw583yTuAdfQuJ9M+QaSyem6OVNkky3+DKAD3z0WJnl9jmGXIXigNSIxD25XhpvY+j9P0XTLBG1GT2Q+wXCIjSYJc2XnYcdgVJcLoxSWk1fKj/KPi7buAWtqwnL3tjeldpMMOZMliPUTWMM14zoGskHztt0JCkAtcotm9AQtvL8eZ2LHLDK/jyLzjv0wAwU5vzSVp14XHLZl7Q0AIoNc20p1EYGa9C/gSPd9CkrWZoG4lMOiAu3tp2PRLVrdXH3ZWSPQq4Ek5MczrUTkmB82XErNbOa8QB1Dw=
file: .tox/dist/python-escpos*.zip
file_glob: true
skip_cleanup: true
on:
tags: true
repo: python-escpos/python-escpos
branch: master
condition: $TRAVIS_PYTHON_VERSION = "3.5"

17
.vscode/settings.json vendored
View File

@@ -1,17 +0,0 @@
{
"esbonio.sphinx.confDir": "${workspaceFolder}/doc",
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/node_modules/*/**": true,
"**/.eggs/**": true,
"**/.hypothesis/**": true,
"**/.tox/**": true,
},
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"python.formatting.provider": "black",
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
}

16
.vscode/tasks.json vendored
View File

@@ -1,16 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "test with tox",
"type": "shell",
"command": "tox",
"group": {
"kind": "test",
"isDefault": true
}
}
]
}

52
AUTHORS
View File

@@ -1,52 +0,0 @@
Ahmed Tahri
akeonly
Alejandro Hernández
Alexander Bougakov
Alex Debiasio
Alfredo Orozco
Asuki Kono
belono
B. Howell
Brian
Christoph Heuel
Cody (Quantified Code Bot)
csoft2k
Curtis // mashedkeyboard
Davis Goglin
Dean Rispin
dependabot[bot]
Dmytro Katyukha
Florent de Labarre
Gerard Marull-Paretas
Hark
Joel Lehtonen
Justin Vieira
kennedy
Kristi
ldos
Lucy Linder
Manuel F Martinez
Mathieu Poussin
Maximilian Wagenbach
Michael Billington
Michael Elsdörfer
mrwunderbar666
Nathan Bookham
Omer Akram
Patrick Kanzler
primax79
Qian Linfeng
Ramon Poca
reck31
Renato Lorenzi
Ricardo Sánchez Alba
Romain Porte
Sam Cheng
Sergio Pulgarin
Stephan Sokolow
Thijs Triemstra
Thomas van den Berg
vendryan
Yaisel Hurtado
ysuolmai
白月秋见心

View File

@@ -1,275 +1,6 @@
*********
Changelog
=========
2023-11-17 - Version 3.0 - "Quietly Confident"
----------------------------------------------
This is the major release of the new version 3.0.
A big thank you to @belono for their many contributions
for the finalization of v3!
The third major release of this library drops support for
Python 2 and requires a Python version of at least 3.8.
The API has been reworked to be more consistent and two
new concepts have been introduced:
`Capabilities` allow the library to know which features
the currently used printer implements and send fitting
commands.
`Magic Encode` is a new feature that uses the
capability information and encodes Unicode automatically
with the correct code page while sending also the
code page change commands.
The license of the project has been changed to MIT
in accordance with its contributors.
The changes listed here are a summary of the changes
of the previous alpha releases. For details please
read the changelog of the alpha releases.
changes
^^^^^^^
- change the project's license to MIT in accordance with the contributors (see python-escpos/python-escpos#171)
- feature: add "capabilities" which are shared with escpos-php, capabilities are stored in
`escpos-printer-db <https://github.com/receipt-print-hq/escpos-printer-db>`_
- feature: the driver tries now to guess the appropriate codepage and sets it automatically (called "magic encode")
- as an alternative you can force the codepage with the old API
- fix the encoding search so that lower encodings are found first
- automatically handle cases where full cut or partial cut is not available
- refactor of the set-method
- preliminary support of POS "line display" printing
- add support for software-based barcode-rendering
- make feed for cut optional
- implemented paper sensor querying command
- Include support for CUPS based printer interfaces
- add Win32Raw-Printer on Windows-platforms
- add and improve Windows support of USB-class
- pickle capabilities for faster startup
contributors
^^^^^^^^^^^^
This is a list of contributors since the last v2 release.
- Ahmed Tahri
- akeonly
- Alexander Bougakov
- AlexandroJaez
- Asuki Kono
- belono
- brendanhowell
- Brian
- Christoph Heuel
- csoft2k
- Curtis // mashedkeyboard
- Dmytro Katyukha
- Foaly
- Gerard Marull-Paretas
- Justin Vieira
- kedare
- kennedy
- Lucy Linder
- Maximilian Wagenbach
- Michael Billington
- Michael Elsdörfer
- mrwunderbar666
- NullYing
- Omer Akram
- Patrick Kanzler
- primax79
- Ramon Poca
- reck31
- Romain Porte
- Sam Cheng
- Scott Rotondo
- Sergio Pulgarin
- Thijs Triemstra
- Yaisel Hurtado
- ysuolmai
and others
2023-05-11 - Version 3.0a9 - "Pride Comes Before A Fall"
--------------------------------------------------------
This release is the 10th alpha release of the new version 3.0.
After three years hiatus, a new release is in work in order to
finally get a version 3.0 out.
changes
^^^^^^^
- Include support for CUPS based printer interfaces
- Move the build tool chain to GitHub
contributors
^^^^^^^^^^^^
- belono
- brendanhowell
- AlexandroJaez
- NullYing
- kedare
- Foaly
- patkan
- and others
2020-05-12 - Version 3.0a8 - "Only Slightly Bent"
-------------------------------------------------
This release is the ninth alpha release of the new version 3.0.
Please be aware that the API is subject to change until v3.0 is
released.
This release drops support for Python 2, requiring at least
version 3.5 of Python.
changes
^^^^^^^
- Drop support for Python 2 and mark in setuptools as only supporting 3.5 and upwards
- remove landscape.io badge
- replace viivakoodi with python-barcode which is maintained
- add configuration for Visual Studio Code
- use pkg_resources for the retrieval of the capabilities.json
contributors
^^^^^^^^^^^^
- Romain Porte
- Patrick Kanzler
2020-05-09 - Version 3.0a7 - "No Fixed Abode"
---------------------------------------------
This release is the eight alpha release of the new version 3.0.
Please be aware that the API is subject to change until v3.0
is released.
This release also marks the point at which the project transitioned
to having only a master-branch (and not an additional development branch).
changes
^^^^^^^
- add Exception for NotImplementedError in detach_kernel_driver
- update installation information
- update and improve documentation
- add error handling to image centering flag
- update and fix tox and CI environment, preparing drop of support for Python 2
contributors
^^^^^^^^^^^^
- Alexander Bougakov
- Brian
- Yaisel Hurtado
- Maximilian Wagenbach
- Patrick Kanzler
2019-06-19 - Version 3.0a6 - "Mistake not..."
---------------------------------------------
This release is the seventh alpha release of the new version 3.0.
Please be aware that the API is subject to change until v3.0 is
released.
changes
^^^^^^^
- fix inclusion of the capabilities-file
- execute CI jobs also on Windows and MacOS-targets
- improve documentation
contributors
^^^^^^^^^^^^
- Patrick Kanzler
2019-06-16 - Version 3.0a5 - "Lightly Seared On The Reality Grill"
------------------------------------------------------------------
This release is the sixth alpha release of the new version 3.0. Please
be aware that the API is subject to change until v3.0 is released.
changes
^^^^^^^
- allow arbitrary USB arguments in USB-class
- add Win32Raw-Printer on Windows-platforms
- add and improve Windows support of USB-class
- use pyyaml safe_load()
- improve doc
- implement _read method of Network printer class
contributors
^^^^^^^^^^^^
- Patrick Kanzler
- Gerard Marull-Paretas
- Ramon Poca
- akeonly
- Omer Akram
- Justin Vieira
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"
----------------------------------------
@@ -302,7 +33,7 @@ changes
- feature: the driver tries now to guess the appropriate codepage and sets it automatically (called "magic encode")
- as an alternative you can force the codepage with the old API
- updated and improved documentation
- changed constructor of main class due to introduction of capabilities
- changed constructor of main class due to introduction of capablities
- changed interface of method `blocktext`, changed behavior of multiple methods, for details refer to the documentation
on `python-escpos.readthedocs.io <https://python-escpos.readthedocs.io>`_
- add support for custom cash drawer sequence
@@ -327,7 +58,7 @@ changes
- fix improper API-use in qrcode()
- change setup.py shebang to make it compatible with virtualenvs.
- add constants for sheet mode and colors
- support changing the line spacing
- support changing the linespacing
contributors
^^^^^^^^^^^^
@@ -381,8 +112,8 @@ changes
- packaging: configured the coverage-analysis codecov.io
- GitHub: improved issues-template
- documentation: add troubleshooting tip to network-interface
- the module, CLI and documentation is now aware of the version of python-escpos
- the CLI does now support basic tab completion
- the module, cli and documentation is now aware of the version of python-escpos
- the cli does now support basic tabcompletion
contributors
^^^^^^^^^^^^
@@ -400,7 +131,7 @@ changes
- refactor complete code in order to be compatible with Python 2 and 3
- modernize packaging
- add testing and CI
- merge various forks into codebase, fixing multiple issues with barcode-, QR-printing, cash-draw and structure
- merge various forks into codebase, fixing multiple issues with barcode-, QR-printing, cashdraw and structure
- improve the documentation
- extend support of barcode-codes to type B
- add function to disable panel-buttons
@@ -471,7 +202,7 @@ contributors
--------------------------
- Issue #5: Fixed vertical tab
- Issue #9: Fixed indentation inconsistency
- Issue #9: Fixed identation inconsistence
2013-03-14 - Version 1.0.1
--------------------------
@@ -482,6 +213,6 @@ contributors
2012-11-15 - Version 1.0
------------------------
- Issue #2: Added Ethernet support
- Issue #2: Added ethernet support
- Issue #3: Added compatibility with libusb-1.0.1
- Issue #4: Fixed typo in escpos.py

View File

@@ -1,83 +1,78 @@
************
Contributing
============
************
:Last Reviewed: 2023-08-10
This project is open to any kind of contribution. You can help with improving the documentation, adding fixes to the
code, providing test cases in code or as a description or just spreading the word. Please feel free to create an
issue or pull request.
In order to reduce the amount of work for everyone please try to adhere to good practice.
This project is open to any kind of contribution.
You can help with improving the documentation, adding fixes to the
code, providing test cases in code or as a description or just
spreading the word.
Please feel free to create an issue or pull request.
In order to reduce the amount of work for everyone please try
to adhere to good practice.
The pull requests and issues will be prefilled with templates. Please fill in your information where applicable.
The pull requests and issues will be prefilled with templates.
Please fill in your information where applicable.
This project uses `semantic versioning <https://semver.org/>`_
and tries to adhere to the proposed rules as well as possible.
Author-list
-----------
This project keeps a list of authors.
This can be auto-generated by calling `./doc/generate-authors.sh`.
When contributing the first time, please include a commit with
the output of this script in place.
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>`_.
This project uses `semantic versioning <http://semver.org/>`_ and tries to adhere to the proposed rules as
well as possible.
Style-Guide
-----------
When writing code please try to stick to these rules.
Black Code Style
^^^^^^^^^^^^^^^^
This project is formatted with the auto formatter `black <https://github.com/psf/black>`_.
Please format your contributions with black, otherwise they will be rejected.
Python 2 and 3
^^^^^^^^^^^^^^
We have rewritten the code in order to maintain compatibility with both Python 2 and Python 3.
In order to ensure that we do not miss any accidental degradation, please add these imports to the top
of every file of code:
.. code-block:: Python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
Furthermore please be aware of the differences between Python 2 and 3. For
example `this guide <https://docs.python.org/3/howto/pyporting.html>`_ is helpful.
Special care has to be taken when dealing with strings and byte-strings. Please note
that the :py:meth:`~escpos.escpos.Escpos._raw`-method only accepts byte-strings.
Often you can achieve compatibility quite easily with a tool from the `six`-package.
PEP8
^^^^
This is not yet consequently done in every piece of code, but please try to ensure
that your code honors PEP8.
The checks by Landscape and QuantifiedCode that run on every PR will provide you with hints.
GIT
^^^
The master-branch contains the main development of the project.
A release to PyPi is marked with a tag corresponding to the version.
Issues are closed when they have been resolved by merging
into the master-branch.
When you have a change to make, begin by creating a new branch
from the HEAD of `python-escpos/master`.
The master-branch contains code that has been released to PyPi. A release is marked with a tag
corresponding to the version. Issues are closed when they have been resolved in the development-branch.
Please try to group your commits into logical units.
If you need to tidy up your branch, you can make use of a
git feature called an 'interactive rebase' before making a pull request.
A small, self-contained change-set is easier to review, and
improves the chance of your code being merged.
Please also make sure that before creating your PR, your branch
is rebased on a recent commit or you merged a recent
commit into your branch.
This way you can ensure that your PR is without merge conflicts.
When you have a change to make, begin by creating a new branch from the HEAD of `python-escpos/development`.
Name your branch to indicate what you are trying to achieve. Good branch names might
be `improve/text-handling`, `feature/enable-color-printing`.
Please try to group your commits into logical units. If you need to tidy up your branch, you can make use of a
git feature called an 'interactive rebase' before making a pull request. A small, self-contained change-set is
easier to review, and improves the chance of your code being merged.
Please also make sure that before creating your PR, your branch is rebased on a recent commit or you merged a recent
commit into your branch. This way you can ensure that your PR is without merge conflicts.
Docstrings
^^^^^^^^^^
This project tries to have a good documentation.
Please add a docstring to every method and class.
Have a look at existing methods and classes for the style.
Please add a docstring to every method and class. Have a look at existing methods and classes for the style.
We use basically standard rst-docstrings for Sphinx.
Test
^^^^
Try to write tests whenever possible.
Our goal for the future is 100% coverage.
You can copy the structure from other testcases.
Please remember to adapt the docstrings.
Try to write tests whenever possible. Our goal for the future is 100% coverage.
We are currently using `nose` but might change in the future.
You can copy the structure from other testcases. Please remember to adapt the docstrings.
Further reading
^^^^^^^^^^^^^^^
For further best practices and hints on contributing please see the
`contribution-guide <https://www.contribution-guide.org/>`_.
Should there be any contradictions between this guide
`contribution-guide <http://www.contribution-guide.org/>`_. Should there be any contradictions between this guide
and the linked one, please stick to this text.
Aside from that feel free to create an issue or write an email if anything is unclear.

23
INSTALL
View File

@@ -1,10 +1,23 @@
python-escpos
=============
This library is available over pypi. So for most of the use-cases it should be sufficient to run
Ensure the library is installed on ${lib_arch}/${python_ver}/site-packages/escpos
```
pip install python-escpos[all] --user # add --pre if you want to install pre-releases
```
On CLi you must run:
# python setup.py build
# sudo python setup.py install
For more information please read the documentation at https://python-escpos.readthedocs.io/en/latest/user/installation.html
On Linux, ensure you belongs to the proper group so you can have access to the printer.
This can be done, by adding yourself to 'dialout' group, this might require to re-login
so the changes make effect.
Then, add the following rule to /etc/udev/rules.d/99-escpos.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="04b8", ATTRS{idProduct}=="0202", MODE="0664", GROUP="dialout"
and restar udev rules.
# sudo service udev restart
Enjoy !!!
And please, don't forget to ALWAYS add Epson.cut() at the end of your printing :)
Manuel F Martinez <manpaz@bashlinux.com>

View File

@@ -1,10 +1,10 @@
include *.rst
include *.txt
include COPYING
include LICENSE
include INSTALL
include tox.ini
include capabilities-data/dist/capabilities.json
include src/escpos/capabilities.json
recursive-include doc *.bat
recursive-include doc *.ico
recursive-include doc *.py

View File

@@ -2,17 +2,34 @@
python-escpos - Python library to manipulate ESC/POS Printers
#############################################################
Description
===========
.. image:: https://travis-ci.org/python-escpos/python-escpos.svg?branch=master
:target: https://travis-ci.org/python-escpos/python-escpos
:alt: Continous Integration
.. image:: https://readthedocs.org/projects/python-escpos/badge/?version=latest
:target: https://python-escpos.readthedocs.io/en/latest/?badge=latest
.. image:: https://www.quantifiedcode.com/api/v1/project/95748b89a3974700800b85e4ed3d32c4/badge.svg
:target: https://www.quantifiedcode.com/app/project/95748b89a3974700800b85e4ed3d32c4
:alt: Code issues
.. image:: https://landscape.io/github/python-escpos/python-escpos/master/landscape.svg?style=flat
:target: https://landscape.io/github/python-escpos/python-escpos/master
:alt: Code Health
.. image:: https://codecov.io/github/python-escpos/python-escpos/coverage.svg?branch=master
:target: https://codecov.io/github/python-escpos/python-escpos?branch=master
:alt: Code Coverage
.. image:: https://readthedocs.org/projects/python-escpos/badge/?version=stable
:target: http://python-escpos.readthedocs.io/en/latest/?badge=stable
:alt: Documentation Status
Description
-----------
Python ESC/POS is a library which lets the user have access to all those printers handled
by ESC/POS commands, as defined by Epson, from a Python application.
The library tries to implement the functions provided by the ESC/POS-command-set and supports sending text, images,
The library tries to implement the functions provided by the ESC/POS-commandset and supports sending text, images,
barcodes and qr-codes to the printer.
Text can be aligned/justified and fonts can be changed by size, type and weight.
@@ -30,11 +47,10 @@ Dependencies
This library makes use of:
* `pyusb <https://github.com/walac/pyusb>`_ for USB-printers
* `Pillow <https://github.com/python-pillow/Pillow>`_ for image printing
* `qrcode <https://github.com/lincolnloop/python-qrcode>`_ for the generation of QR-codes
* `pyserial <https://github.com/pyserial/pyserial>`_ for serial printers
* `python-barcode <https://github.com/WhyNotHugo/python-barcode>`_ for the generation of barcodes
* pyusb for USB-printers
* Pillow for image printing
* qrcode for the generation of QR-codes
* pyserial for serial printers
Documentation and Usage
-----------------------
@@ -49,55 +65,12 @@ The basic usage is:
p = Usb(0x04b8, 0x0202, 0, profile="TM-T88III")
p.text("Hello World\n")
p.image("logo.gif")
p.barcode('4006381333931', 'EAN13', 64, 2, '', '')
p.barcode('1324354657687', 'EAN13', 64, 2, '', '')
p.cut()
Another example based on the Network printer class:
.. code:: python
from escpos.printer import Network
kitchen = Network("192.168.1.100") #Printer IP Address
kitchen.text("Hello World\n")
kitchen.barcode('4006381333931', 'EAN13', 64, 2, '', '')
kitchen.cut()
Another example based on the Serial printer class:
.. code:: python
from escpos.printer import Serial
""" 9600 Baud, 8N1, Flow Control Enabled """
p = Serial(devfile='/dev/tty.usbserial',
baudrate=9600,
bytesize=8,
parity='N',
stopbits=1,
timeout=1.00,
dsrdtr=True)
p.text("Hello World\n")
p.qr("You can readme from your smartphone")
p.cut()
The full project-documentation is available on
`Read the Docs <https://python-escpos.readthedocs.io>`_.
The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_.
Contributing
------------
This project is open for any contribution! Please see
`CONTRIBUTING.rst <https://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.
This project is open for any contribution! Please see CONTRIBUTING.rst for more information.

View File

@@ -3,15 +3,11 @@ codecov:
coverage:
status:
project: off
patch: off
changes: off
project:
default: # status context
target: auto
threshold: "1%"
range: "60...100"
comment:
layout: " diff, flags, files"
behavior: default
require_changes: false # if true: only post the comment if coverage changes
require_base: false # [true :: must have a base report to post]
require_head: true # [true :: must have a head report to post]
comment: off

94
debian/changelog vendored Normal file
View File

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

1
debian/compat vendored Normal file
View File

@@ -0,0 +1 @@
9

55
debian/control vendored Normal file
View File

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

26
debian/copyright vendored Normal file
View File

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

1
debian/docs vendored Normal file
View File

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

6
debian/gbp.conf vendored Normal file
View File

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

28
debian/rules vendored Executable file
View File

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

1
debian/source/format vendored Normal file
View File

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

View File

@@ -9,7 +9,7 @@ BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://sphinx-doc.org/)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
@@ -19,7 +19,7 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext spelling
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@@ -45,7 +45,6 @@ help:
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " spelling to run the spellchecker"
clean:
rm -rf $(BUILDDIR)/*
@@ -176,8 +175,3 @@ pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
spelling:
$(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling
@echo
@echo "Spellchecker finished."

View File

@@ -1,28 +0,0 @@
{% for item in data.encodings %}
{% set encoding = data.encodings[item] %}
{% macro draw_with_underline(text, symbol='-') -%}
{{ escape_rst(text) }}
{{ escape_rst(text) | length * symbol }}
{%- endmacro %}
{{ '.. _encoding-label-' + item + ':' }}
{{ draw_with_underline(encoding.name) }}
{{ escape_rst(encoding.notes) }}
Mapping Information
^^^^^^^^^^^^^^^^^^^
====================== ================================================================
identifier {{ escape_rst(item) }}
Name {{ escape_rst(encoding.name|default('Unknown')) }}
Iconv Name {{ escape_rst(encoding.iconv|default('Unknown')) }}
``python_encode`` Name {{ escape_rst(encoding.python_encode|default('Unknown')) }}
====================== ================================================================
{% if encoding.data is defined %}
{{ draw_with_underline('Code page data', symbol='^') }}
{{ encoding.data }}
{% endif %}
{% endfor %}

View File

@@ -1,83 +0,0 @@
{% for item in data.profiles %}
{% set printer = data.profiles[item] %}
{% macro draw_with_underline(text, symbol='-') -%}
{{ escape_rst(text) }}
{{ escape_rst(text) | length * symbol }}
{%- endmacro %}
{% macro abort(error) %}
{{ None['[ERROR] ' ~ error][0] }}
{% endmacro %}
{% macro fill_line(text, total, symbol=' ') -%}
{%- if total < text|length -%}
{{- abort("Line cannot be filled: must be longer") -}}
{%- endif -%}
{{- text + ((total - text|length ) * symbol ) -}}
{%- endmacro %}
{{ '.. _printer-label-' + item + ':' }}
{{ draw_with_underline(printer.name) }}
{{ escape_rst(printer.notes) }}
You can select this profile in python-escpos with this identifier: ``{{ item }}``.
(Set parameter to `profile='{{ item }}'`.)
Basic information
^^^^^^^^^^^^^^^^^
====================== ================================================================
Name {{ escape_rst(printer.name|default('Unknown')) }}
Vendor {{ escape_rst(printer.vendor|default('Unknown')) }}
Media width (mm) {{ escape_rst(printer.media.width.mm|default('Unknown')|string) }}
Media width (pixels) {{ escape_rst(printer.media.width.pixels|default('Unknown')|string) }}
DPI {{ escape_rst(printer.media.dpi|default('Unknown')|string) }}
====================== ================================================================
Fonts
^^^^^
+------------------+------------------------------+-----------------------+
| ID | Name | Columns |
+==================+==============================+=======================+
{% for id in printer.fonts -%}
| {{ fill_line(escape_rst(id), 16) }} | {{ fill_line(escape_rst(printer.fonts[id].name), 28) }} | {{ fill_line(printer.fonts[id].columns|string, 21) }} |
+------------------+------------------------------+-----------------------+
{% endfor %}
Colors
^^^^^^
+------------------+----------------------------------------------------------------+
| ID | Color |
+==================+================================================================+
{% for id in printer.colors -%}
| {{ fill_line(escape_rst(id), 16) }} | {{ fill_line(escape_rst(printer.colors[id]), 62) }} |
+------------------+----------------------------------------------------------------+
{% endfor %}
Feature support
^^^^^^^^^^^^^^^
+-----------------------------------+----------------------------+
| Feature | Supported |
+===================================+============================+
{% for feature in printer.features -%}
| {{ fill_line(escape_rst(feature), 33) }} | {{ fill_line(escape_rst(printer.features[feature]|string), 26) }} |
+-----------------------------------+----------------------------+
{% endfor %}
Text code pages
^^^^^^^^^^^^^^^
+------------------+----------------------------------------------------------------+
| ID | Encoding |
+==================+================================================================+
{% for id in printer.codePages -%}
| {{ fill_line(escape_rst(id), 16) }} | {{ fill_line(':ref:`encoding-label-'+printer.codePages[id]+'`', 62) }} |
+------------------+----------------------------------------------------------------+
{% endfor %}
{% endfor %}

View File

@@ -12,114 +12,113 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
import sys
from importlib.metadata import version as imp_version
on_rtd = os.getenv("READTHEDOCS") == "True"
import os
on_rtd = os.getenv('READTHEDOCS') == 'True'
if on_rtd:
import escpos
else:
from setuptools_scm import get_version
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath("../src"))
root = os.path.relpath(os.path.join(os.path.dirname(__file__), ".."))
sys.path.insert(0, os.path.abspath('../src'))
root = os.path.relpath(os.path.join(os.path.dirname(__file__), '..'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx_autodoc_typehints",
"sphinx.ext.doctest",
"sphinx.ext.todo",
"sphinx.ext.coverage",
"sphinx.ext.viewcode",
"sphinx.ext.todo",
"sphinx.ext.graphviz",
"sphinx.ext.inheritance_diagram",
"sphinx.ext.imgconverter",
"sphinxarg.ext",
"sphinxcontrib.datatemplates",
"sphinxcontrib.spelling",
'sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinx.ext.todo',
'sphinx.ext.graphviz',
'sphinx.ext.inheritance_diagram',
]
# mock the following modules for autodoc
autodoc_mock_imports = ["qrcode"]
# supress warnings for external images
suppress_warnings = [
"image.nonlocal_uri",
'image.nonlocal_uri',
]
# enable todos
todo_include_todos = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates", "capability_templates"]
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = ".rst"
source_suffix = '.rst'
# The encoding of source files.
# source_encoding = 'utf-8-sig'
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = "index"
master_doc = 'index'
# General information about the project.
project = "python-escpos"
copyright = "2023, python-escpos developers"
project = u'python-escpos'
copyright = u'2016, Manuel F Martinez and others'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
release = imp_version("python-escpos")
if on_rtd:
# The full version, including alpha/beta/rc tags.
release = escpos.__version__
else:
# locally setuptools_scm should work
release = get_version(root=root)
# The short X.Y version.
version = ".".join(release.split(".")[:2])
version = '.'.join(release.split('.')[:2]) # The short X.Y version.
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
#today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ["_build"]
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
# default_role = None
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
#keep_warnings = False
# -- Options for HTML output ----------------------------------------------
@@ -127,142 +126,135 @@ pygments_style = "sphinx"
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
if on_rtd:
html_theme = "sphinx_rtd_theme"
print("recognized execution on RTD")
html_theme = 'default'
else:
try:
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
except ImportError:
print("no sphinx_rtd_theme found, switching to nature")
html_theme = "default"
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
html_favicon = "pyescpos.ico"
html_favicon = 'pyescpos.ico'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
# html_extra_path = []
#html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
#html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
#html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
#html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
#html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = "python-escposdoc"
htmlhelp_basename = 'python-escposdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
latex_engine = "xelatex"
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(
"index",
"python-escpos.tex",
"python-escpos Documentation",
"python-escpos developers",
"manual",
),
('index', 'python-escpos.tex', u'python-escpos Documentation',
u'Manuel F Martinez and others', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
#latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
#latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
#latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
@@ -270,17 +262,12 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(
"index",
"python-escpos",
"python-escpos Documentation",
["python-escpos developers"],
1,
)
('index', 'python-escpos', u'python-escpos Documentation',
[u'Manuel F Martinez and others'], 1)
]
# If true, show URL addresses after external links.
# man_show_urls = False
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
@@ -289,43 +276,19 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
"index",
"python-escpos",
"python-escpos Documentation",
"python-escpos developers",
"python-escpos",
"One line description of project.",
"Miscellaneous",
),
('index', 'python-escpos', u'python-escpos Documentation',
u'Manuel F Martinez and others', 'python-escpos', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
#texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
# texinfo_no_detailmenu = False
# spellchecker
spelling_ignore_pypi_package_names = True
spelling_ignore_wiki_words = True
spelling_ignore_python_builtins = True
spelling_ignore_importable_modules = True
spelling_ignore_contributor_names = True
spelling_word_list_filename = ["spelling_wordlist.txt", "../AUTHORS"]
spelling_show_suggestions = True
spelling_suggestion_limit = 3
spelling_warning = True
spelling_exclude_patterns = [
"**/capabilities.json",
"../../capabilities-data/dist/capabilities.json",
"**/available-encodings.rst",
"**/available-profiles.rst",
"dev/todo.rst",
]
#texinfo_no_detailmenu = False

View File

@@ -1,11 +0,0 @@
Release process
===============
:Last Reviewed: 2023-08-10
* Update authors file
* Update changelog
* Set annotated tag for release and push to public github
* Build wheel
* Load wheel to PyPi
* Prepare project for next release with an empty changelog entry

View File

@@ -1,17 +0,0 @@
.. _developer-manual-repository:
Repository
==========
:Last Reviewed: 2023-09-05
This project uses sub-projects and retrieves its versioning
information from version control.
Therefore it is crucial that you follow these rules when
working with the project (e.g. for packaging a
development version).
* Make sure that the git project is complete. A call to git status for example should succeed.
* Make sure that you have checked out all available sub-projects.
* Proper initialization of submodules can be ensured with ``git submodule update --init --recursive``

View File

@@ -1,15 +0,0 @@
TODO
====
:Last Reviewed: 2023-08-10
Open points and issues of the project are tracked in the GitHub issues.
Some annotations still remain in the code and should be moved over time
into the issue tracker.
Todos in the codebase
~~~~~~~~~~~~~~~~~~~~~
.. todolist::

View File

@@ -1,22 +0,0 @@
#!/bin/sh
GENLIST=$(git shortlog -s -n | cut -f2 | sort -f)
GENLIST_W_MAIL=$(git shortlog -s -e -n | cut -f2 | sort -f)
AUTHORSFILE="$(dirname $0)/../AUTHORS"
TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile"
if [ "$#" -eq 1 ]
then
echo "$GENLIST">$TEMPAUTHORSFILE
echo "\nAuthorsfile in version control:\n"
cat $AUTHORSFILE
echo "\nNew authorsfile:\n"
cat $TEMPAUTHORSFILE
echo "\nUsing diff on files...\n"
diff --suppress-common-lines -b --from-file $AUTHORSFILE $TEMPAUTHORSFILE
echo "Authors with mail addresses:\n"
echo "$GENLIST_W_MAIL"
else
echo "$GENLIST">$AUTHORSFILE
fi

View File

@@ -5,15 +5,8 @@
.. include:: ../README.rst
#######
Content
#######
User Documentation
==================
This chapter describes the central points that
are relevant to the user of this library.
-------
.. toctree::
:maxdepth: 1
@@ -23,46 +16,16 @@ are relevant to the user of this library.
user/methods
user/printers
user/raspi
user/todo
user/usage
user/cli-user
user/barcode
Printer profiles
================
This chapter gives a listing of the available
printer profiles. Details are described in
:ref:`capabilities-profile-intro`.
.. toctree::
:maxdepth: 1
:caption: Printer profiles
printer_profiles/capabilities
printer_profiles/available-profiles
printer_profiles/available-encodings
Developer Documentation
=======================
This chapter summarizes information for
developers of this library.
.. toctree::
:maxdepth: 1
:caption: Developer Documentation
dev/release-process
dev/contributing
dev/repository
dev/changelog
dev/todo
API Documentation
=================
This chapter contains an auto-generated
documentation of the API of this library.
.. toctree::
:maxdepth: 1
@@ -80,9 +43,8 @@ documentation of the API of this library.
api/codepages
api/katakana
##################
Indices and tables
##################
==================
* :ref:`genindex`
* :ref:`modindex`

View File

@@ -56,7 +56,7 @@ if errorlevel 9009 (
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://sphinx-doc.org/
echo.http://sphinx-doc.org/
exit /b 1
)

View File

@@ -1,11 +0,0 @@
Available Encodings
-------------------
:Last Reviewed: 2023-08-10
If you find any issues with the described encodings,
please open an issue in the
`ESC/POS printer database <https://github.com/receipt-print-hq/escpos-printer-db>`_.
The data shown here is directly taken from there.
.. datatemplate:json:: ../../capabilities-data/dist/capabilities.json
:template: capabilities-template-encoding.jinja

View File

@@ -1,18 +0,0 @@
.. _available-profiles:
Available Profiles
------------------
:Last Reviewed: 2023-08-10
The following list describes which printer profiles are
available in this release.
The existence of a profile is a hint, but no guarantee
that this printer actually can be controlled by this library.
If you find any issues with the described capabilities,
please open an issue in the
`ESC/POS printer database <https://github.com/receipt-print-hq/escpos-printer-db>`_.
The data shown here is directly taken from there.
.. datatemplate:json:: ../../capabilities-data/dist/capabilities.json
:template: capabilities-template.jinja

View File

@@ -1,26 +0,0 @@
.. _capabilities-profile-intro:
Capabilities
------------
:Last Reviewed: 2023-08-10
Since the used command set often differs between printers,
a model for supporting different printers is implemented.
This feature is called `capabilities`.
The `capabilities`-feature allows this library to know
which features are supported.
If no further information is specified, python-escpos will
try to automatically use features based on the supplied information.
In order to use the `capabilities`-database, the printer instance
simply has to be created with the parameter `profile` set to the
relevant identifier.
The identifier can be found in :ref:`available-profiles`.
This documentation describes the profiles in the database file that
is bundled with this release.
If another configuration is to be used, this chapter can be followed
for information on how to side-load another `capabilities`-database:
:ref:`advanced-usage-change-capabilities-profile`.

View File

@@ -2,15 +2,6 @@ pyusb
Pillow>=2.0
qrcode>=4.0
pyserial
sphinx-rtd-theme==1.3.0
setuptools
sphinx-rtd-theme
setuptools-scm
docutils>=0.12
sphinxcontrib-spelling>=7.2.0
python-barcode>=0.11.0,<1
importlib-metadata
importlib_resources
sphinxcontrib.datatemplates
sphinx-argparse
sphinx-autodoc-typehints
pycups

View File

@@ -1,128 +0,0 @@
ESC
Esc
POS
Pos
Escpos
Escpos
escpos
bitImageColumn
bitImageRaster
ep
pc
cp
csoft
Frédéric
Headcrash
Krispy
primax
Tahri
reck
mrwunderbar
zouppen
kedare
Foaly
brendanhowell
der
fvdsn
Marull
Paretas
Kakistocrat
Billington
patkan
Romain
Bougakov
Yaisel
Hurtado
Wagenbach
Poca
Akram
Vieira
Christoph
Heuel
Thijs
Triemstra
Linder
Romain
Pulgarin
Romain
Cheng
Dmytro
Katyukha
Elsdörfer
Asuki
Kono
López
mashedkeyboard
Thijs
Triemstra
Elsdörfer
Renato
Lorenzi
Bookham
Goglin
Christoph
Heuel
Qian
Lehtonen
Kanzler
Rotondo
barcode
barcodes
baudrate
Bashlinux
capabilities
cashdraw
charcode
changelog
cheque
codebase
codecov
codepages
config
del
dev
dialout
docstrings
ean
Ean
encodable
fff
fullimage
io
json
latin
libusb
lp
lsusb
natively
php
pre
prefilled
printcap
programmatically
py
pypy
pyyaml
px
qrcode
Raspbian
rebase
rebased
resetted
rst
submodule
submodules
src
testcases
th
Todo
traceback
udev
usb
usec
virtualenvs
whitespaces
xml

View File

@@ -1,29 +1,17 @@
Printing Barcodes
-----------------
:Last Reviewed: 2016-07-31
:Last Reviewed: 2023-08-10
Many printers implement barcode printing natively.
These hardware rendered barcodes are fast but the supported
formats are limited by the printer itself and different between models.
However, almost all printers support printing images, so barcode
rendering can be performed externally by software and then sent
to the printer as an image.
As a drawback, this operation is much slower and the user needs
to know and choose the image implementation method supported by
the printer's command-set.
Most ESC/POS-printers implement barcode-printing.
The barcode-commandset is implemented in the barcode-method.
For a list of compatible barcodes you should check the manual of your printer.
As a rule of thumb: even older Epson-models support most 1D-barcodes.
To be sure just try some implementations and have a look at the notices below.
barcode-method
~~~~~~~~~~~~~~
Since version 3.0, the ``barcode`` method unifies the previous
``barcode`` (hardware) and ``soft_barcode`` (software) methods.
It is able to choose automatically the best printer implementation
for barcode printing based on the capabilities of the printer
and the type of barcode desired.
To achieve this, it relies on the information contained in the
escpos-printer-db profiles.
The chosen profile needs to match the capabilities of the printer
as closely as possible.
The barcode-method is rather low-level and orients itself on the implementation of ESC/POS.
In the future this class could be supplemented by a high-level class that helps the user generating the payload.
.. py:currentmodule:: escpos.escpos
@@ -43,5 +31,4 @@ For alphanumeric CODE128 you have to preface your payload with `{B`.
# print CODE128 012ABCDabcd
p.barcode("{B012ABCDabcd", "CODE128", function_type="B")
A very good description on CODE128 is also on
`Wikipedia <https://en.wikipedia.org/wiki/Code_128>`_.
A very good description on CODE128 is also on `Wikipedia <https://en.wikipedia.org/wiki/Code_128>`_.

View File

@@ -1,10 +0,0 @@
CLI
===
:Last Reviewed: 2023-09-25
Documentation of the command line interface, callable with ``python-escpos``.
.. argparse::
:ref: escpos.cli.generate_parser
:prog: python-escpos

View File

@@ -1,53 +1,25 @@
.. _user_installation:
************
Installation
============
************
:Last Reviewed: 2023-08-10
:Last Reviewed: 2016-07-23
Installation with PIP
---------------------
Installation should be rather straight-forward. python-escpos is on PyPi,
so you can simply enter:
Installation should be rather straight-forward. python-escpos is on PyPi, so you can simply enter:
::
pip install python-escpos[all]
pip install python-escpos
This should install all necessary dependencies. Apart from that
python-escpos is for some versions also available as a Debian package.
If you want to always benefit from the newest stable releases you should
always install from PyPi.
If you use the ``--pre`` parameter for ``pip``, you will get the latest
pre-release.
The following installation options exist:
* `all`: install all packages available for this platform
* `usb`: install packages required for USB printers
* `serial`: install packages required for serial printers
* `win32`: install packages required for win32 printing (only Windows)
* `cups`: install packages required for CUPS printing
Other installation methods
--------------------------
Officially, no other installation methods are supplied.
If you want to install nevertheless from another source,
please make sure that you have received the correct package
and that the capabilities data is properly integrated.
When packaging from source please read the developer
information in :ref:`developer-manual-repository`.
If your packaging method breaks the resource system from setuptools,
it might be necessary to supply the path to the capabilities file:
:ref:`advanced-usage-change-capabilities-profile`.
This should install all necessary dependencies. Apart from that python-escpos should also be
available as a Debian package. If you want to always benefit from the newest stable releases you should probably
install from PyPi.
Setup udev for USB-Printers
---------------------------
1. Get the *Product ID* and *Vendor ID* from the lsusb command
``# lsusb Bus 002 Device 001: ID 1a2b:1a2b Device name``.
(Or whichever way your system supplies to get the PID and VID.)
``# lsusb Bus 002 Device 001: ID 1a2b:1a2b Device name``
2. Create a udev rule to let users belonging to *dialout* group use the
printer. You can create the file
@@ -63,8 +35,7 @@ Setup udev for USB-Printers
Enabling tab-completion in CLI
------------------------------
python-escpos has a CLI with tab-completion.
This is realized with ``argcomplete``.
python-escpos has a CLI with tab-completion. This is realised with ``argcomplete``.
In order for this to work you have to enable tab-completion, which is described in
the `manual of argcomplete <https://argcomplete.readthedocs.io>`__.

View File

@@ -1,12 +1,12 @@
*******
Methods
=======
:Last Reviewed: 2023-08-10
*******
:Last Reviewed: 2017-01-25
Escpos class
------------
The core part of the API of this library is the Escpos class.
The core part of this libraries API is the Escpos class.
You use it by instantiating a :doc:`printer <printers>` which is a child of Escpos.
The following methods are available:

View File

@@ -1,18 +1,15 @@
********
Printers
========
********
:Last Reviewed: 2017-01-25
:Last Reviewed: 2023-08-23
As of now there are 8 different types of printer implementations.
As of now there are 5 different type of printer implementations.
USB
---
The USB-class uses pyusb and libusb to communicate with USB-based
printers.
.. note::
This driver is not suited for USB-to-Serial-adapters
and similar devices, but only for those implementing native USB.
printers. Note that this driver is not suited for USB-to-Serial-adapters
and similiar devices, but only for those implementing native USB.
.. autoclass:: escpos.printer.Usb
:members:
@@ -47,24 +44,20 @@ This driver is based on the socket class.
Troubleshooting
^^^^^^^^^^^^^^^
Problems with a network-attached printer can have numerous causes.
Make sure that your device has a proper IP address.
Often you can check the IP address by triggering the self-test of the device.
As a next step try to send text manually to the device.
You could use for example:
Problems with a network-attached printer can have numerous causes. Make sure that your device has a proper IP address.
Often you can check the IP address by triggering the self-test of the device. As a next step try to send text
manually to the device. You could use for example:
::
echo "OK\n" | nc IPADDRESS 9100
# the port number is often 9100
As a last resort try to reset the interface of the printer.
This should be described in its manual.
As a last resort try to reset the interface of the printer. This should be described in its manual.
File
----
This printer "prints" just into a file-handle.
Especially on \*nix-systems this comes very handy.
This printer "prints" just into a file-handle. Especially on \*nix-systems this comes very handy.
.. autoclass:: escpos.printer.File
:members:
@@ -74,50 +67,11 @@ Especially on \*nix-systems this comes very handy.
Dummy
-----
The Dummy-printer is mainly for testing- and debugging-purposes.
It stores all of the "output" as raw ESC/POS in a string and returns that.
The Dummy-printer is mainly for testing- and debugging-purposes. It stores
all of the "output" as raw ESC/POS in a string and returns that.
.. autoclass:: escpos.printer.Dummy
:members:
:member-order: bysource
:noindex:
CUPS
----
This driver uses `pycups` in order to communicate with a CUPS server.
Supports both local and remote CUPS printers and servers.
The printer must be properly configured in CUPS administration.
The connector generates a print job that is added to the CUPS queue.
.. autoclass:: escpos.printer.CupsPrinter
:members:
:member-order: bysource
:noindex:
LP
----
This driver uses the UNIX command `lp` in order to communicate with a CUPS server.
Supports local and remote CUPS printers.
The printer must be properly configured in CUPS administration.
The connector spawns a new sub-process where the command lp is executed.
No dependencies required, but somehow the print queue will affect some
print job such as barcode.
.. autoclass:: escpos.printer.LP
:members:
:special-members:
:member-order: bysource
:noindex:
Win32Raw
--------
This driver uses a native WIN32 interface of Windows in order to print.
Please refer to the code for documentation as this driver is currently
not included in the documentation build.
.. autoclass:: escpos.printer.Win32Raw
:members:
:special-members:
:member-order: bysource
:noindex:

View File

@@ -1,26 +1,42 @@
************
Raspberry Pi
============
************
:Last Reviewed: 2023-08-10
:Last Reviewed: 2017-01-05
.. warning:: You should **never** directly connect an printer with RS232-interface
(serial port) directly to a Raspberry PI or similar interface
(e.g. those simple USB-sticks without encasing).
Those interfaces are based on 5V- or 3,3V-logic
(the latter in the case of Raspberry PI).
Classical RS232 uses 12V-logic and would **thus destroy your interface**.
Connect both systems with an appropriate *level shifter*.
This instructions were tested on Raspbian Jessie.
.. warning:: You should **never** directly connect an printer with RS232-interface (serial port) directly to
a Raspberry PI or similar interface (e.g. those simple USB-sticks without encasing). Those interfaces are
based on 5V- or 3,3V-logic (the latter in the case of Raspberry PI). Classical RS232 uses 12V-logic and would
**thus destroy your interface**. Connect both systems with an appropriate *level shifter*.
Dependencies
------------
First, install the packages available on Raspbian.
::
sudo apt-get install python3 python3-setuptools python3-pip libjpeg8-dev
Installation
------------
The installation should be performed as described in :ref:`user_installation`.
You can install by using pip3.
::
sudo pip3 install --upgrade pip
sudo pip3 install python-escpos
Run
---
You can run this software as on any other Linux system.
You need sudo and python3 to run your program.
Attach your printer and test it with the example code in the project's set of examples.
You can find that in the
`project-repository <https://github.com/python-escpos/python-escpos>`__.
::
sudo python3 your-program.py
Now you can attach your printer and and test it with the example code in the project's set of examples.
You can find that in the `project-repository <https://github.com/python-escpos/python-escpos>`__.
For more details on this check the :doc:`installation-manual <installation>`.

43
doc/user/todo.rst Normal file
View File

@@ -0,0 +1,43 @@
****
TODO
****
Introduction
------------
python-escpos is the initial idea, from here we can start to build a
robust library to get most of the ESC/POS printers working with this
library.
Eventually, this library must be able to cover almost all the defined
models detailed in the ESC/POS Command Specification Manual.
Details
-------
What things are planned to work on?
Testing
~~~~~~~
* Test on many printers as possible (USB, Serial, Network)
* automate testing
Design
~~~~~~
* Add all those sequences which are not common, but part of the ESC/POS
Command Specifications.
* Port to Python 3
* Windows compatibility (hidapi instead libusb?)
* PDF417 support
* use something similar to the `capabilities` in escpos-php
Todos in the codebase
~~~~~~~~~~~~~~~~~~~~~
.. todolist::

View File

@@ -1,7 +1,6 @@
*****
Usage
=====
:Last Reviewed: 2023-08-10
*****
Define your printer
-------------------
@@ -44,20 +43,18 @@ to have and the second yields the "Output Endpoint" address.
::
p = printer.Usb(0x04b8,0x0202)
Epson = printer.Usb(0x04b8,0x0202)
By default the "Interface" number is "0" and the "Output Endpoint"
address is "0x01". If you have other values then you can define them on
your instance. So, assuming that we have another printer, CT-S2000,
manufactured by Citizen (with "Vendor ID" of 2730 and "Product ID" of 0fff)
where in\_ep is on 0x81 and out\_ep=0x02, then the printer definition should
look like:
your instance. So, assuming that we have another printer where in\_ep is
on 0x81 and out\_ep=0x02, then the printer definition should look like:
**Generic USB Printer initialization**
::
p = printer.Usb(0x2730, 0x0fff, 0, 0x81, 0x02)
Generic = printer.Usb(0x1a2b,0x1a2b,0,0x81,0x02)
Network printer
^^^^^^^^^^^^^^^
@@ -69,7 +66,7 @@ IP by DHCP or you set it manually.
::
p = printer.Network("192.168.1.99")
Epson = printer.Network("192.168.1.99")
Serial printer
^^^^^^^^^^^^^^
@@ -83,10 +80,7 @@ to.
::
p = printer.Serial("/dev/tty0")
# on a Windows OS serial devices are typically accessible as COM
p = printer.Serial("COM1")
Epson = printer.Serial("/dev/tty0")
Other printers
^^^^^^^^^^^^^^
@@ -98,7 +92,7 @@ passing the device node name.
::
p = 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.
@@ -113,22 +107,17 @@ on a USB interface.
from escpos import *
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
p = printer.Usb(0x04b8,0x0202)
Epson = printer.Usb(0x04b8,0x0202)
# Print text
p.text("Hello World\n")
Epson.text("Hello World\n")
# Print image
p.image("logo.gif")
Epson.image("logo.gif")
# Print QR Code
p.qr("You can readme from your smartphone")
Epson.qr("You can readme from your smartphone")
# Print barcode
p.barcode('4006381333931','EAN13',64,2,'','')
Epson.barcode('1324354657687','EAN13',64,2,'','')
# Cut paper
p.cut()
Standard python constraints on libraries apply. This means especially
that you should not name the script in which you implement these lines
should not be named ``escpos`` as this would collide with the name of
the library.
Epson.cut()
Configuration File
------------------
@@ -146,7 +135,7 @@ And for linux::
$HOME/.config/python-escpos/config.yaml
If you are not sure, run::
If you aren't sure, run::
from escpos import config
c = config.Config()
@@ -168,12 +157,10 @@ The printer section
The ``printer`` configuration section defines a default printer to create.
The only required parameter is ``type``. The value of this has to be one of the
The only required paramter is ``type``. The value of this should be one of the
printers defined in :doc:`/user/printers`.
The rest of the given parameters will be passed on to the initialization of the printer class.
Use these to overwrite the default values as specified in :doc:`/user/printers`.
This implies that the parameters have to match the parameter-names of the respective printer class.
The rest of the parameters are whatever you want to pass to the printer.
An example file printer::
@@ -184,34 +171,23 @@ An example file printer::
And for a network printer::
printer:
type: Network
type: network
host: 127.0.0.1
port: 9000
An USB-printer could be defined by::
printer:
type: Usb
idVendor: 0x1234
idProduct: 0x5678
in_ep: 0x66
out_ep: 0x01
Printing text right
-------------------
Python-escpos is designed to accept unicode.
Python-escpos is designed to accept unicode. So make sure that you use ``u'strings'`` or import ``unicode_literals``
from ``__future__`` if you are on Python 2. On Python 3 you should be fine.
For normal usage you can simply pass your text to the printers ``text()``-function. It will automatically guess
the right codepage and then send the encoded data to the printer. If this feature does not work, please try to
isolate the error and then create an issue on the GitHub project page.
isolate the error and then create an issue on the Github project page.
If you want or need to you can manually set the codepage.
For this please use the ``charcode()``-function.
You can set any key-value that is in ``CHARCODE``.
If something is wrong, an ``CharCodeError`` will be raised.
After you have manually set the codepage the printer won't change it anymore.
You can revert to normal behavior by setting charcode to ``AUTO``.
If you want or need to you can manually set the codepage. For this please use the ``charcode()``-function. You can set
any key-value that is in ``CHARCODE``. If something is wrong, an ``CharCodeError`` will be raised.
After you have manually set the codepage the printer won't change it anymore. You can revert to normal behaviour
by setting charcode to ``AUTO``.
Advanced Usage: Print from binary blob
--------------------------------------
@@ -240,8 +216,6 @@ Here you can download an example, that will print a set of common barcodes:
* :download:`barcode.bin </download/barcode.bin>` by `@mike42 <https://github.com/mike42>`_
.. _advanced-usage-change-capabilities-profile:
Advanced Usage: change capabilities-profile
-------------------------------------------
@@ -298,18 +272,4 @@ This way you could also store the code in a file and print it later.
You could then for example print the code from another process than your main-program and thus reduce the waiting time.
(Of course this will not make the printer print faster.)
Troubleshooting
---------------
This section gathers various hints on troubleshooting.
Print with STAR TSP100 family
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Printer of the STAR TSP100 family do not have a native ESC/POS mode, which
is why you will not be able to directly print with this library to the printer.
More information on this topic can be found in the online documentation of
`Star Micronics <https://www.starmicronics.com/help-center/knowledge-base/configure-tsp100-series-printers-esc-pos-mode/>`_
and the `discussion in the python-escpos project <https://github.com/python-escpos/python-escpos/issues/410>`_.

View File

@@ -1,11 +0,0 @@
"""Example for printing barcodes."""
from escpos.printer import Usb
# Adapt to your needs
p = Usb(0x0416, 0x5011, profile="TM-T88II")
# Print software and then hardware barcode with the same content
p.barcode("123456", "CODE39", width=2, force_software=True)
p.text("\n")
p.text("\n")
p.barcode("123456", "CODE39")

View File

@@ -1,29 +1,21 @@
"""Prints code page tables."""
"""Prints code page tables.
"""
import sys
from __future__ import print_function
import six
import sys
from escpos import printer
from escpos.constants import (
CODEPAGE_CHANGE,
CTL_CR,
CTL_FF,
CTL_HT,
CTL_LF,
CTL_VT,
ESC,
)
from escpos.constants import CODEPAGE_CHANGE, ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT
def main():
"""Init printer and print codepage tables."""
dummy = printer.Dummy()
dummy.hw("init")
dummy.hw('init')
for codepage in sys.argv[1:] or ["USA"]:
for codepage in sys.argv[1:] or ['USA']:
dummy.set(height=2, width=2)
dummy._raw(codepage + "\n\n\n")
print_codepage(dummy, codepage)
@@ -35,7 +27,6 @@ def main():
def print_codepage(printer, codepage):
"""Print a codepage."""
if codepage.isdigit():
codepage = int(codepage)
printer._raw(CODEPAGE_CHANGE + six.int2byte(codepage))
@@ -46,14 +37,14 @@ def print_codepage(printer, codepage):
sep = ""
# Table header
printer.set(font="b")
printer.set(text_type='B')
printer._raw(" {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16)))))
printer.set()
# The table
for x in range(0, 16):
# First column
printer.set(font="b")
printer.set(text_type='B')
printer._raw("{} ".format(hex(x)[2:]))
printer.set()
@@ -61,12 +52,12 @@ def print_codepage(printer, codepage):
byte = six.int2byte(x * 16 + y)
if byte in (ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT):
byte = " "
byte = ' '
printer._raw(byte)
printer._raw(sep)
printer._raw("\n")
printer._raw('\n')
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -1,28 +0,0 @@
# Use the official Python image as the base image
FROM python:3.9-slim
# Set the working directory
WORKDIR /app
# Copy the requirements file
COPY requirements.txt .
#Install the libcups library
RUN apt-get update -y && apt-get install libcups2-dev -y
# Install the Python packages
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install python-escpos --pre
# Install Git
RUN apt-get update && \
apt-get install -y git && \
rm -rf /var/lib/apt/lists/*
# Copy the Flask app
COPY app.py .
# Expose the port the Flask app will run on
EXPOSE 8080
# Run the Flask app
CMD ["python", "app.py"]

View File

@@ -1,6 +0,0 @@
Simple example on how to use it inside a web service
```
docker build . -t escpos-web
docker run --network=host -p 9999:9999 escpos
```

View File

@@ -1,22 +0,0 @@
"""Example for a flask application."""
from flask import Flask
from escpos.printer import CupsPrinter
# Initialize Flask app
app = Flask(__name__)
@app.route("/", methods=["GET"])
def do_print():
"""Print."""
# p = Usb(0x04b8, 0x0e28, 0)
p = CupsPrinter(host="localhost", port=631, printer_name="TM-T20III")
p.text("Hello World\n")
p.cut()
p.close()
return "OK"
if __name__ == "__main__":
app.run(debug=False, host="0.0.0.0", port=9999)

View File

@@ -1,20 +0,0 @@
appdirs==1.4.4
argcomplete==3.0.8
blinker==1.6.2
click==8.1.3
Flask==2.3.2
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.2
Pillow==10.0.1
pycups==2.0.1
pypng==0.20220715.0
pyserial==3.5
python-barcode==0.14.0
python-escpos==3.0a9
pyusb==1.2.1
PyYAML==6.0
qrcode==7.4.2
six==1.16.0
typing_extensions==4.5.0
Werkzeug==3.0.1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -1,10 +0,0 @@
# Climacons by Adam Whitcroft
75 climatically categorised pictographs for web and UI design by [@adamwhitcroft](https://www.twitter.com/#!/adamwhitcroft).
Visit the [Climacons](https://adamwhitcroft.com/climacons/) website for more information.
Visit [Adam Whitcroft on GitHub](https://github.com/AdamWhitcroft)
## License
You are free to use any of the Climacons Icons (the "icons") in any personal or commercial work without obligation of payment (monetary or otherwise) or attribution, however a credit for the work would be appreciated. **Do not** redistribute or sell and **do not** claim creative credit. Intellectual property rights are not transferred with the download of the icons.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,21 +0,0 @@
"""Print example QR codes."""
import sys
from escpos.printer import Usb
def usage():
"""Print information on usage."""
print("usage: qr_code.py <content>")
if __name__ == "__main__":
if len(sys.argv) != 2:
usage()
sys.exit(1)
content = sys.argv[1]
# Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890")
p.qr(content, center=True)

View File

@@ -1,9 +0,0 @@
"""Example file for software barcodes."""
from escpos.printer import Usb
# Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890")
# Some software barcodes
p.barcode("Hello", "code128", width=2, force_software="bitImageRaster")
p.barcode("1234", "code39", width=2, force_software=True)

View File

@@ -1,131 +0,0 @@
#!/usr/bin/python
"""Weather forecast example.
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 https://adamwhitcroft.com/climacons/
Check out his github: https://github.com/AdamWhitcroft/climacons
"""
import calendar
import json
import os
import time
import urllib
from datetime import datetime
from escpos.printer import Usb
"""Set 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):
"""Get right icon for forecast."""
icon = data["daily"]["data"][idx]["icon"]
image = GRAPHICS_PATH + icon + ".png"
return image
def forecast(idx):
"""Dump one forecast line to the printer."""
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("\u2013", "-").encode("utf-8"))
printer.text("\n \n")
def icon():
"""Get icon."""
icon = data["currently"]["icon"]
image = GRAPHICS_PATH + icon + ".png"
return image
deg = " C" # Degree symbol on thermal printer, need to find a better way to use a proper degree symbol
# if you want Fahrenheit change units= to 'us'
url = (
"https://api.darksky.net/forecast/"
+ API_KEY
+ "/"
+ LAT
+ ","
+ LONG
+ "?exclude=[alerts,minutely,hourly,flags]&units=si"
) # change last bit to 'us' for Fahrenheit
response = urllib.urlopen(url)
data = json.loads(response.read())
printer.print_and_feed(n=1)
printer.control("LF")
printer.set(font="a", height=2, align="center", bold=True, double_height=True)
printer.text("Weather Forecast")
printer.text("\n")
printer.set(align="center")
# Print current conditions
printer.set(font="a", height=2, align="center", bold=True, double_height=False)
printer.text("Current conditions: \n")
printer.image(icon())
printer.text("\n")
printer.set(font="a", height=2, align="left", bold=False, double_height=False)
temp = data["currently"]["temperature"]
cond = data["currently"]["summary"]
printer.text(temp)
printer.text(" ")
printer.text(deg)
printer.text(" ")
printer.text("\n")
printer.text("Sky: " + cond)
printer.text("\n")
printer.text("\n")
# Print forecast
printer.set(font="a", height=2, align="center", bold=True, double_height=False)
printer.text("Forecast: \n")
forecast(0)
forecast(1)
printer.cut()
printer.control("LF")

View File

@@ -1,27 +0,0 @@
[tool.black]
extend-exclude = 'capabilities-data'
[tool.isort]
profile = "black"
[tool.pytest.ini_options]
minversion = "6.0"
addopts = "--doctest-modules --cov escpos --cov-report=xml"
testpaths = [
"test",
"src",
"src/escpos",
"escpos",
]
[[tool.mypy.overrides]]
module = ["pytest",
"jaconv",
"scripttest",
"barcode.*",
"qrcode",
"usb.*",
"cups",
"win32print"
]
ignore_missing_imports = true

7
readthedocs.yml Normal file
View File

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

View File

@@ -1,80 +1,12 @@
[metadata]
name = python-escpos
url = https://github.com/python-escpos/python-escpos
description = Python library to manipulate ESC/POS Printers
long_description = file: README.rst
license = MIT
license_file = LICENSE
author = python-escpos developers
author_email = dev@pkanzler.de
maintainer = Patrick Kanzler
maintainer_email = dev@pkanzler.de
keywords = ESC/POS, thermoprinter, voucher printer, printing, receipt
classifiers =
Development Status :: 4 - Beta
Environment :: Console
Intended Audience :: Developers
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: Implementation :: CPython
Topic :: Software Development :: Libraries :: Python Modules
Topic :: Office/Business :: Financial :: Point-Of-Sale
project_urls =
Bug Tracker = https://github.com/python-escpos/python-escpos/issues
Documentation = https://python-escpos.readthedocs.io/en/latest/
Release Notes = https://github.com/python-escpos/python-escpos/releases
[nosetests]
verbosity=3
with-doctest=1
[options]
python_requires = >=3.8
zip_safe = false
include_package_data = true
install_requires =
Pillow>=2.0
qrcode>=4.0
python-barcode>=0.9.1,<1
setuptools
six
appdirs
PyYAML
argparse
argcomplete
importlib_resources
setup_requires = setuptools_scm
tests_require =
jaconv
tox>=4.11
pytest>=7.4
pytest-cov
pytest-mock
scripttest
mock
hypothesis>=6.83
flake8
sphinxcontrib-spelling>=8.0.0
[options.extras_require]
usb =
pyusb>=1.0.0
serial =
pyserial
cups =
pycups; platform_system!='Windows'
win32 =
pywin32; platform_system=='Windows'
all =
pyusb>=1.0.0
pyserial
pycups; platform_system!='Windows'
pywin32; platform_system=='Windows'
[bdist_wheel]
# This flag says that the code is written to work on both Python 2 and Python 3.
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
extend-ignore = E203, W503
# future-imports = absolute_import, division, print_function, unicode_literals # we are not there yet

126
setup.py
View File

@@ -1,10 +1,10 @@
#!/usr/bin/env python
"""Setup script for python package."""
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")
@@ -15,31 +15,127 @@ sys.path.insert(0, src_dir)
def read(fname):
"""Read file from same path as setup.py."""
"""read file from same path as setup.py"""
return open(os.path.join(os.path.dirname(__file__), fname)).read()
setuptools_scm_template = """\
#!/usr/bin/python
# -*- coding: utf-8 -*-
\"\"\"Version identifier.
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")]
file generated by setuptools_scm
don't change, don't track in version control
\"\"\"
def initialize_options(self):
"""initialize the user-options"""
test_command.initialize_options(self)
self.tox_args = None
def finalize_options(self):
"""finalize user-options"""
test_command.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
"""run tox and pass on user-options"""
# import here, cause outside the eggs aren't loaded
import tox
import shlex
args = self.tox_args
if args:
args = shlex.split(self.tox_args)
errno = tox.cmdline(args=args)
sys.exit(errno)
setuptools_scm_template = """\
# coding: utf-8
# file generated by setuptools_scm
# don't change, don't track in version control
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
version = '{version}'
"""
setup(
name='python-escpos',
use_scm_version={
"write_to": "src/escpos/version.py",
"write_to_template": setuptools_scm_template,
'write_to': 'src/escpos/version.py',
'write_to_template': setuptools_scm_template,
},
platforms="any",
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',
author_email='manpaz@bashlinux.com',
maintainer='Patrick Kanzler',
maintainer_email='dev@pkanzler.de',
keywords=[
'ESC/POS',
'thermoprinter',
'voucher printer',
'printing',
'receipt,',
],
platforms='any',
package_dir={"": "src"},
packages=find_packages(where="src", exclude=["tests", "tests.*"]),
package_data={"escpos": ["capabilities.json"]},
entry_points={"console_scripts": ["python-escpos = escpos.cli:main"]},
package_data={'': ['COPYING', 'src/escpos/capabilities.json']},
include_package_data=True,
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Office/Business :: Financial :: Point-Of-Sale',
],
install_requires=[
'pyusb>=1.0.0',
'Pillow>=2.0',
'qrcode>=4.0',
'pyserial',
'six',
'appdirs',
'pyyaml',
'argparse',
'argcomplete',
'future'
],
setup_requires=[
'setuptools_scm',
],
tests_require=[
'jaconv',
'tox',
'pytest',
'pytest-cov',
'pytest-mock',
'nose',
'scripttest',
'mock',
'hypothesis',
'flake8'
],
cmdclass={'test': Tox},
entry_points={
'console_scripts': [
'python-escpos = escpos.cli:main'
]
},
)

View File

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

View File

@@ -1,198 +1,118 @@
"""Handler for capabilities data."""
import atexit
import logging
import pickle
import platform
import re
import time
from contextlib import ExitStack
from os import environ, path
from tempfile import mkdtemp
from typing import Any, Dict, Optional, Type
import importlib_resources
import six
from os import environ, path
import yaml
logging.basicConfig()
logger = logging.getLogger(__name__)
pickle_dir = environ.get("ESCPOS_CAPABILITIES_PICKLE_DIR", mkdtemp())
pickle_path = path.join(
pickle_dir, "{v}.capabilities.pickle".format(v=platform.python_version())
)
# get a temporary file from importlib_resources if no file is specified in env
file_manager = ExitStack()
atexit.register(file_manager.close)
ref = importlib_resources.files(__name__) / "capabilities.json"
capabilities_path = environ.get(
"ESCPOS_CAPABILITIES_FILE",
file_manager.enter_context(importlib_resources.as_file(ref)),
)
# 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)
if 'ESCPOS_CAPABILITIES_FILE' in environ:
file_path = environ['ESCPOS_CAPABILITIES_FILE']
else:
logger.debug("Capabilities pickle file not found: %s", pickle_path)
full_load = True
file_path = path.join(path.dirname(__file__), 'capabilities.json')
if full_load:
logger.debug("Loading and pickling capabilities")
with open(capabilities_path) as cp, open(pickle_path, "wb") as pp:
CAPABILITIES = yaml.safe_load(cp)
if not CAPABILITIES:
# yaml could not be loaded
print(
f"Capabilities yaml from {capabilities_path} could not be loaded.\n"
"This python package seems to be broken. If it has been installed "
"from official sources, please report an issue on GitHub.\n"
"Currently loaded capabilities:\n"
f"{CAPABILITIES}"
)
CAPABILITIES = {
"profiles": {
"default": {
"name": "BrokenDefault",
"notes": "The integrated capabilities file could not be found and has been replaced.",
"codePages": {"0": "Broken"},
"features": {},
},
},
"encodings": {
"Broken": {
"name": "Broken",
"notes": "The configuration is broken.",
}
},
}
print(
"Created a minimal backup profile, "
"many functionalities of the library will not work:\n"
f"{CAPABILITIES}"
)
pickle.dump(CAPABILITIES, pp, protocol=2)
with open(file_path) as f:
CAPABILITIES = yaml.load(f)
logger.debug("Finished loading capabilities took %.2fs", time.time() - t0)
PROFILES = CAPABILITIES['profiles']
class NotSupported(Exception):
"""Raised if a requested feature is not supported by the printer profile."""
"""Raised if a requested feature is not suppored by the
printer profile.
"""
pass
BARCODE_B = "barcodeB"
BARCODE_B = 'barcodeB'
class BaseProfile(object):
"""This represents a printer profile.
"""This respresents a printer profile.
A printer profile knows about the number of columns, supported
features, colors and more.
"""
profile_data: Dict[str, Any] = {}
profile_data = {}
def __getattr__(self, name):
"""Get a data element from the profile."""
return self.profile_data[name]
def get_font(self, font) -> int:
"""Return the escpos index for `font`.
Makes sure that the requested `font` is valid.
def get_font(self, font):
"""Return the escpos index for `font`. Makes sure that
the requested `font` is valid.
"""
font = {"a": 0, "b": 1}.get(font, font)
font = {'a': 0, 'b': 1}.get(font, font)
if not six.text_type(font) in self.fonts:
raise NotSupported(
'"{}" is not a valid font in the current profile'.format(font)
)
'"{}" is not a valid font in the current profile'.format(font))
return font
def get_columns(self, font):
"""Return the number of columns for the given font."""
""" Return the number of columns for the given font.
"""
font = self.get_font(font)
return self.fonts[six.text_type(font)]["columns"]
return self.fonts[six.text_type(font)]['columns']
def supports(self, feature):
"""Return true/false for the given feature."""
"""Return true/false for the given feature.
"""
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()}
def get_profile(name: Optional[str] = None, **kwargs):
"""Get a profile by name.
If no name is given, return the default profile.
def get_profile(name=None, **kwargs):
"""Get the profile by name; if no name is given, return the
default profile.
"""
if isinstance(name, Profile):
return name
clazz = get_profile_class(name or "default")
clazz = get_profile_class(name or 'default')
return clazz(**kwargs)
CLASS_CACHE = {}
def get_profile_class(name: str) -> Type[BaseProfile]:
"""Load a profile class.
For the given profile name, load the data from the external
def get_profile_class(name):
"""For the given profile name, load the data from the external
database, then generate dynamically a class.
"""
if name not in CLASS_CACHE:
profiles: Dict[str, Any] = CAPABILITIES["profiles"]
profile_data = profiles[name]
profile_data = PROFILES[name]
profile_name = clean(name)
class_name = "{}{}Profile".format(profile_name[0].upper(), profile_name[1:])
new_class = type(class_name, (BaseProfile,), {"profile_data": profile_data})
class_name = '{}{}Profile'.format(
profile_name[0].upper(), profile_name[1:])
new_class = type(class_name, (BaseProfile,), {'profile_data': profile_data})
CLASS_CACHE[name] = new_class
return CLASS_CACHE[name]
def clean(s):
"""Clean profile name."""
# Remove invalid characters
s = re.sub("[^0-9a-zA-Z_]", "", s)
s = re.sub('[^0-9a-zA-Z_]', '', s)
# Remove leading characters until we find a letter or underscore
s = re.sub("^[^a-zA-Z_]+", "", s)
s = re.sub('^[^a-zA-Z_]+', '', s)
return str(s)
# mute the mypy type issue with this dynamic base class function for now (: Any)
ProfileBaseClass: Any = get_profile_class("default")
class Profile(ProfileBaseClass):
"""Profile class for user usage.
For users, who want to provide their own profile.
class Profile(get_profile_class('default')):
"""
For users, who want to provide their profile
"""
def __init__(self, columns=None, features=None):
"""Initialize profile."""
super(Profile, self).__init__()
self.columns = columns
self.features = features or {}
def get_columns(self, font):
"""Get column count of printer."""
if self.columns is not None:
return self.columns

View File

@@ -1,44 +1,41 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
"""CLI.
""" CLI
This module acts as a command line interface for python-escpos. It mirrors
closely the available ESCPOS commands while adding a couple extra ones for convenience.
closely the available ESCPOS commands while adding a couple extra ones for convience.
It requires you to have a configuration file. See documentation for details.
"""
import argparse
import platform
from typing import Any, Dict, List
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import argparse
try:
import argcomplete
except ImportError:
# this CLI works nevertheless without argcomplete
pass # noqa
import sys
import six
from . import config
from . import printer as escpos_printer_module
from . import version
# Must be defined before it's used in DEMO_FUNCTIONS
def str_to_bool(string):
"""Convert string to bool.
Used as a type in argparse so that we get back a proper
bool instead of always True.
""" Used as a type in argparse so that we get back a proper
bool instead of always True
"""
return string.lower() in ("y", "yes", "1", "true")
return string.lower() in ('y', 'yes', '1', 'true')
# A list of functions that work better with a newline to be sent after them.
REQUIRES_NEWLINE = ("qr", "barcode", "text", "block_text")
REQUIRES_NEWLINE = ('qr', 'barcode', 'text', 'block_text')
# Used in demo method
@@ -46,46 +43,40 @@ REQUIRES_NEWLINE = ("qr", "barcode", "text", "block_text")
# manual translation is done in the case of barcodes_a -> barcode.
# Value: A list of dictionaries to pass to the escpos function as arguments.
DEMO_FUNCTIONS = {
"text": [
{
"txt": "Hello, World!\n",
}
'text': [
{'txt': 'Hello, World!\n', }
],
"qr": [
{"content": "This tests a QR code"},
{"content": "https://en.wikipedia.org/"},
'qr': [
{'content': 'This tests a QR code'},
{'content': 'https://en.wikipedia.org/'}
],
"barcodes_a": [
{"bc": "UPC-A", "code": "13243546576"},
{"bc": "UPC-E", "code": "132435"},
{"bc": "EAN13", "code": "4006381333931"},
{"bc": "EAN8", "code": "1324354"},
{"bc": "CODE39", "code": "TEST"},
{"bc": "ITF", "code": "55867492279103"},
{"bc": "NW7", "code": "A00000000A"},
'barcodes_a': [
{'bc': 'UPC-A', 'code': '13243546576'},
{'bc': 'UPC-E', 'code': '132435'},
{'bc': 'EAN13', 'code': '1324354657687'},
{'bc': 'EAN8', 'code': '1324354'},
{'bc': 'CODE39', 'code': 'TEST'},
{'bc': 'ITF', 'code': '55867492279103'},
{'bc': 'NW7', 'code': 'A00000000A'},
],
"barcodes_b": [
{"bc": "UPC-A", "code": "13243546576", "function_type": "B"},
{"bc": "UPC-E", "code": "132435", "function_type": "B"},
{"bc": "EAN13", "code": "4006381333931", "function_type": "B"},
{"bc": "EAN8", "code": "1324354", "function_type": "B"},
{"bc": "CODE39", "code": "TEST", "function_type": "B"},
{"bc": "ITF", "code": "55867492279103", "function_type": "B"},
{"bc": "NW7", "code": "A00000000A", "function_type": "B"},
{"bc": "CODE93", "code": "A00000000A", "function_type": "B"},
{"bc": "CODE93", "code": "1324354657687", "function_type": "B"},
{"bc": "CODE128A", "code": "TEST", "function_type": "B"},
{"bc": "CODE128B", "code": "TEST", "function_type": "B"},
{"bc": "CODE128C", "code": "TEST", "function_type": "B"},
{"bc": "GS1-128", "code": "00123456780000000001", "function_type": "B"},
{
"bc": "GS1 DataBar Omnidirectional",
"code": "0000000000000",
"function_type": "B",
},
{"bc": "GS1 DataBar Truncated", "code": "0000000000000", "function_type": "B"},
{"bc": "GS1 DataBar Limited", "code": "0000000000000", "function_type": "B"},
{"bc": "GS1 DataBar Expanded", "code": "00AAAAAAA", "function_type": "B"},
'barcodes_b': [
{'bc': 'UPC-A', 'code': '13243546576', 'function_type': 'B'},
{'bc': 'UPC-E', 'code': '132435', 'function_type': 'B'},
{'bc': 'EAN13', 'code': '1324354657687', 'function_type': 'B'},
{'bc': 'EAN8', 'code': '1324354', 'function_type': 'B'},
{'bc': 'CODE39', 'code': 'TEST', 'function_type': 'B'},
{'bc': 'ITF', 'code': '55867492279103', 'function_type': 'B'},
{'bc': 'NW7', 'code': 'A00000000A', 'function_type': 'B'},
{'bc': 'CODE93', 'code': 'A00000000A', 'function_type': 'B'},
{'bc': 'CODE93', 'code': '1324354657687', 'function_type': 'B'},
{'bc': 'CODE128A', 'code': 'TEST', 'function_type': 'B'},
{'bc': 'CODE128B', 'code': 'TEST', 'function_type': 'B'},
{'bc': 'CODE128C', 'code': 'TEST', 'function_type': 'B'},
{'bc': 'GS1-128', 'code': '00123456780000000001', 'function_type': 'B'},
{'bc': 'GS1 DataBar Omnidirectional', 'code': '0000000000000', 'function_type': 'B'},
{'bc': 'GS1 DataBar Truncated', 'code': '0000000000000', 'function_type': 'B'},
{'bc': 'GS1 DataBar Limited', 'code': '0000000000000', 'function_type': 'B'},
{'bc': 'GS1 DataBar Expanded', 'code': '00AAAAAAA', 'function_type': 'B'},
],
}
@@ -95,484 +86,435 @@ DEMO_FUNCTIONS = {
# parser: A dict of args for command_parsers.add_parser
# defaults: A dict of args for subparser.set_defaults
# arguments: A list of dicts of args for subparser.add_argument
ESCPOS_COMMANDS: List[Dict[str, Any]] = [
ESCPOS_COMMANDS = [
{
"parser": {
"name": "qr",
"help": "Print a QR code",
'parser': {
'name': 'qr',
'help': 'Print a QR code',
},
"defaults": {
"func": "qr",
'defaults': {
'func': 'qr',
},
"arguments": [
'arguments': [
{
"option_strings": ("--content",),
"help": "Text to print as a qr code",
"required": True,
'option_strings': ('--content',),
'help': 'Text to print as a qr code',
'required': True,
},
{
"option_strings": ("--size",),
"help": "QR code size (1-16) [default:3]",
"required": False,
"type": int,
},
],
},
{
"parser": {
"name": "barcode",
"help": "Print a barcode",
},
"defaults": {
"func": "barcode",
},
"arguments": [
{
"option_strings": ("--code",),
"help": "Barcode data to print",
"required": True,
},
{
"option_strings": ("--bc",),
"help": "Barcode format",
"required": True,
},
{
"option_strings": ("--height",),
"help": "Barcode height in px",
"type": int,
},
{
"option_strings": ("--width",),
"help": "Barcode width",
"type": int,
},
{
"option_strings": ("--pos",),
"help": "Label position",
"choices": ["BELOW", "ABOVE", "BOTH", "OFF"],
},
{
"option_strings": ("--font",),
"help": "Label font",
"choices": ["A", "B"],
},
{
"option_strings": ("--align_ct",),
"help": "Align barcode center",
"type": str_to_bool,
},
{
"option_strings": ("--function_type",),
"help": "ESCPOS function type",
"choices": ["A", "B"],
},
{
"option_strings": ("--force_software",),
"help": "Force render and print barcode as an image",
"choices": ["graphics", "bitImageColumn", "bitImageRaster"],
},
],
},
{
"parser": {
"name": "text",
"help": "Print plain text",
},
"defaults": {
"func": "text",
},
"arguments": [
{
"option_strings": ("--txt",),
"help": "Plain text to print",
"required": True,
'option_strings': ('--size',),
'help': 'QR code size (1-16) [default:3]',
'required': False,
'type': int,
}
],
},
{
"parser": {
"name": "block_text",
"help": "Print wrapped text",
'parser': {
'name': 'barcode',
'help': 'Print a barcode',
},
"defaults": {
"func": "block_text",
'defaults': {
'func': 'barcode',
},
"arguments": [
'arguments': [
{
"option_strings": ("--txt",),
"help": "block_text to print",
"required": True,
'option_strings': ('--code',),
'help': 'Barcode data to print',
'required': True,
},
{
"option_strings": ("--columns",),
"help": "Number of columns",
"type": int,
'option_strings': ('--bc',),
'help': 'Barcode format',
'required': True,
},
{
'option_strings': ('--height',),
'help': 'Barcode height in px',
'type': int,
},
{
'option_strings': ('--width',),
'help': 'Barcode width',
'type': int,
},
{
'option_strings': ('--pos',),
'help': 'Label position',
'choices': ['BELOW', 'ABOVE', 'BOTH', 'OFF'],
},
{
'option_strings': ('--font',),
'help': 'Label font',
'choices': ['A', 'B'],
},
{
'option_strings': ('--align_ct',),
'help': 'Align barcode center',
'type': str_to_bool,
},
{
'option_strings': ('--function_type',),
'help': 'ESCPOS function type',
'choices': ['A', 'B'],
},
],
},
{
"parser": {
"name": "cut",
"help": "Cut the paper",
'parser': {
'name': 'text',
'help': 'Print plain text',
},
"defaults": {
"func": "cut",
'defaults': {
'func': 'text',
},
"arguments": [
'arguments': [
{
"option_strings": ("--mode",),
"help": "Type of cut",
"choices": ["FULL", "PART"],
'option_strings': ('--txt',),
'help': 'Plain text to print',
'required': True,
}
],
},
{
'parser': {
'name': 'block_text',
'help': 'Print wrapped text',
},
'defaults': {
'func': 'block_text',
},
'arguments': [
{
'option_strings': ('--txt',),
'help': 'block_text to print',
'required': True,
},
{
'option_strings': ('--columns',),
'help': 'Number of columns',
'type': int,
},
],
},
{
"parser": {
"name": "cashdraw",
"help": "Kick the cash drawer",
'parser': {
'name': 'cut',
'help': 'Cut the paper',
},
"defaults": {
"func": "cashdraw",
'defaults': {
'func': 'cut',
},
"arguments": [
'arguments': [
{
"option_strings": ("--pin",),
"help": "Which PIN to kick",
"choices": [2, 5],
'option_strings': ('--mode',),
'help': 'Type of cut',
'choices': ['FULL', 'PART'],
},
],
},
{
"parser": {
"name": "image",
"help": "Print an image",
'parser': {
'name': 'cashdraw',
'help': 'Kick the cash drawer',
},
"defaults": {
"func": "image",
'defaults': {
'func': 'cashdraw',
},
"arguments": [
'arguments': [
{
"option_strings": ("--img_source",),
"help": "Path to image",
"required": True,
},
{
"option_strings": ("--impl",),
"help": "Implementation to use",
"choices": ["bitImageRaster", "bitImageColumn", "graphics"],
},
{
"option_strings": ("--high_density_horizontal",),
"help": "Image density (horizontal)",
"type": str_to_bool,
},
{
"option_strings": ("--high_density_vertical",),
"help": "Image density (vertical)",
"type": str_to_bool,
'option_strings': ('--pin',),
'help': 'Which PIN to kick',
'choices': [2, 5],
},
],
},
{
"parser": {
"name": "fullimage",
"help": "Print a fullimage",
'parser': {
'name': 'image',
'help': 'Print an image',
},
"defaults": {
"func": "fullimage",
'defaults': {
'func': 'image',
},
"arguments": [
'arguments': [
{
"option_strings": ("--img",),
"help": "Path to img",
"required": True,
'option_strings': ('--img_source',),
'help': 'Path to image',
'required': True,
},
{
"option_strings": ("--max_height",),
"help": "Max height of image in px",
"type": int,
'option_strings': ('--impl',),
'help': 'Implementation to use',
'choices': ['bitImageRaster', 'bitImageColumn', 'graphics'],
},
{
"option_strings": ("--width",),
"help": "Max width of image in px",
"type": int,
'option_strings': ('--high_density_horizontal',),
'help': 'Image density (horizontal)',
'type': str_to_bool,
},
{
"option_strings": ("--histeq",),
"help": "Equalize the histogram",
"type": str_to_bool,
'option_strings': ('--high_density_vertical',),
'help': 'Image density (vertical)',
'type': str_to_bool,
},
],
},
{
"option_strings": ("--bandsize",),
"help": "Size of bands to divide into when printing",
"type": int,
'parser': {
'name': 'fullimage',
'help': 'Print a fullimage',
},
'defaults': {
'func': 'fullimage',
},
'arguments': [
{
'option_strings': ('--img',),
'help': 'Path to img',
'required': True,
},
{
'option_strings': ('--max_height',),
'help': 'Max height of image in px',
'type': int,
},
{
'option_strings': ('--width',),
'help': 'Max width of image in px',
'type': int,
},
{
'option_strings': ('--histeq',),
'help': 'Equalize the histrogram',
'type': str_to_bool,
},
{
'option_strings': ('--bandsize',),
'help': 'Size of bands to divide into when printing',
'type': int,
},
],
},
{
"parser": {
"name": "charcode",
"help": "Set character code table",
'parser': {
'name': 'charcode',
'help': 'Set character code table',
},
"defaults": {
"func": "charcode",
'defaults': {
'func': 'charcode',
},
"arguments": [
'arguments': [
{
"option_strings": ("--code",),
"help": "Character code",
"required": True,
'option_strings': ('--code',),
'help': 'Character code',
'required': True,
},
],
},
{
"parser": {
"name": "set",
"help": "Set text properties",
'parser': {
'name': 'set',
'help': 'Set text properties',
},
"defaults": {
"func": "set",
'defaults': {
'func': 'set',
},
"arguments": [
'arguments': [
{
"option_strings": ("--align",),
"help": "Horizontal alignment",
"choices": ["left", "center", "right"],
'option_strings': ('--align',),
'help': 'Horizontal alignment',
'choices': ['left', 'center', 'right'],
},
{
"option_strings": ("--font",),
"help": "Font choice",
"choices": ["left", "center", "right"],
'option_strings': ('--font',),
'help': 'Font choice',
'choices': ['left', 'center', 'right'],
},
{
"option_strings": ("--text_type",),
"help": "Text properties",
"choices": ["B", "U", "U2", "BU", "BU2", "NORMAL"],
'option_strings': ('--text_type',),
'help': 'Text properties',
'choices': ['B', 'U', 'U2', 'BU', 'BU2', 'NORMAL'],
},
{
"option_strings": ("--width",),
"help": "Width multiplier",
"type": int,
'option_strings': ('--width',),
'help': 'Width multiplier',
'type': int,
},
{
"option_strings": ("--height",),
"help": "Height multiplier",
"type": int,
'option_strings': ('--height',),
'help': 'Height multiplier',
'type': int,
},
{
"option_strings": ("--density",),
"help": "Print density",
"type": int,
'option_strings': ('--density',),
'help': 'Print density',
'type': int,
},
{
"option_strings": ("--invert",),
"help": "White on black printing",
"type": str_to_bool,
'option_strings': ('--invert',),
'help': 'White on black printing',
'type': str_to_bool,
},
{
"option_strings": ("--smooth",),
"help": "Text smoothing. Effective on >: 4x4 text",
"type": str_to_bool,
'option_strings': ('--smooth',),
'help': 'Text smoothing. Effective on >: 4x4 text',
'type': str_to_bool,
},
{
"option_strings": ("--flip",),
"help": "Text smoothing. Effective on >: 4x4 text",
"type": str_to_bool,
'option_strings': ('--flip',),
'help': 'Text smoothing. Effective on >: 4x4 text',
'type': str_to_bool,
},
],
},
{
"parser": {
"name": "hw",
"help": "Hardware operations",
'parser': {
'name': 'hw',
'help': 'Hardware operations',
},
"defaults": {
"func": "hw",
'defaults': {
'func': 'hw',
},
"arguments": [
'arguments': [
{
"option_strings": ("--hw",),
"help": "Operation",
"choices": ["INIT", "SELECT", "RESET"],
"required": True,
'option_strings': ('--hw',),
'help': 'Operation',
'choices': ['INIT', 'SELECT', 'RESET'],
'required': True,
},
],
},
{
"parser": {
"name": "control",
"help": "Control sequences",
'parser': {
'name': 'control',
'help': 'Control sequences',
},
"defaults": {
"func": "control",
'defaults': {
'func': 'control',
},
"arguments": [
'arguments': [
{
"option_strings": ("--ctl",),
"help": "Control sequence",
"choices": ["LF", "FF", "CR", "HT", "VT"],
"required": True,
'option_strings': ('--ctl',),
'help': 'Control sequence',
'choices': ['LF', 'FF', 'CR', 'HT', 'VT'],
'required': True,
},
{
"option_strings": ("--pos",),
"help": "Horizontal tab position (1-4)",
"type": int,
'option_strings': ('--pos',),
'help': 'Horizontal tab position (1-4)',
'type': int,
},
],
},
{
"parser": {
"name": "panel_buttons",
"help": "Controls panel buttons",
'parser': {
'name': 'panel_buttons',
'help': 'Controls panel buttons',
},
"defaults": {
"func": "panel_buttons",
'defaults': {
'func': 'panel_buttons',
},
"arguments": [
'arguments': [
{
"option_strings": ("--enable",),
"help": "Feed button enabled",
"type": str_to_bool,
"required": True,
'option_strings': ('--enable',),
'help': 'Feed button enabled',
'type': str_to_bool,
'required': True,
},
],
},
{
"parser": {
"name": "raw",
"help": "Raw data",
'parser': {
'name': 'raw',
'help': 'Raw data',
},
"defaults": {
"func": "_raw",
'defaults': {
'func': '_raw',
},
"arguments": [
'arguments': [
{
"option_strings": ("--msg",),
"help": "Raw data to send",
"required": True,
'option_strings': ('--msg',),
'help': 'Raw data to send',
'required': True,
},
],
},
]
def print_extended_information() -> None:
"""Print diagnostic information for bug reports."""
print(f"* python-escpos version: `{version.version}`")
print(
f"* python version: `{platform.python_implementation()} v{platform.python_version()}`"
)
print(f"* platform: `{platform.platform()}`")
print(
f"* printer driver `USB` is usable: `{escpos_printer_module.Usb.is_usable()}`"
)
print(
f"* printer driver `File` is usable: `{escpos_printer_module.File.is_usable()}`"
)
print(
f"* printer driver `Network` is usable: `{escpos_printer_module.Network.is_usable()}`"
)
print(
f"* printer driver `Serial` is usable: `{escpos_printer_module.Serial.is_usable()}`"
)
print(f"* printer driver `LP` is usable: `{escpos_printer_module.LP.is_usable()}`")
print(
f"* printer driver `Dummy` is usable: `{escpos_printer_module.Dummy.is_usable()}`"
)
print(
f"* printer driver `CupsPrinter` is usable: `{escpos_printer_module.CupsPrinter.is_usable()}`"
)
print(
f"* printer driver `Win32Raw` is usable: `{escpos_printer_module.Win32Raw.is_usable()}`"
)
def main():
"""
Handles loading of configuration and creating and processing of command
line arguments. Called when run from a CLI.
"""
def generate_parser() -> argparse.ArgumentParser:
"""Generate an argparse parser."""
parser = argparse.ArgumentParser(
description="CLI for python-escpos",
epilog="Printer configuration is defined in the python-escpos config"
"file. See documentation for details.",
description='CLI for python-escpos',
epilog='Printer configuration is defined in the python-escpos config'
'file. See documentation for details.',
)
parser.register("type", "bool", str_to_bool)
parser.register('type', 'bool', str_to_bool)
# Allow config file location to be passed
parser.add_argument(
"-c",
"--config",
help="Alternate path to the configuration file",
'-c', '--config',
help='Alternate path to the configuration file',
)
# Everything interesting runs off of a subparser so we can use the format
# cli [subparser] -args
command_subparsers = parser.add_subparsers(
title="ESCPOS Command",
dest="parser",
title='ESCPOS Command',
dest='parser',
)
# fix inconsistencies in the behaviour of some versions of argparse
command_subparsers.required = False # force 'required' testing
# Build the ESCPOS command arguments
for command in ESCPOS_COMMANDS:
parser_command = command_subparsers.add_parser(**command["parser"])
parser_command.set_defaults(**command["defaults"])
for argument in command["arguments"]:
option_strings = argument.pop("option_strings")
parser_command = command_subparsers.add_parser(**command['parser'])
parser_command.set_defaults(**command['defaults'])
for argument in command['arguments']:
option_strings = argument.pop('option_strings')
parser_command.add_argument(*option_strings, **argument)
# Build any custom arguments
parser_command_demo = command_subparsers.add_parser(
"demo", help="Demonstrates various functions"
)
parser_command_demo.set_defaults(func="demo")
parser_command_demo = command_subparsers.add_parser('demo',
help='Demonstrates various functions')
parser_command_demo.set_defaults(func='demo')
demo_group = parser_command_demo.add_mutually_exclusive_group()
demo_group.add_argument(
"--barcodes-a",
help="Print demo barcodes for function type A",
action="store_true",
'--barcodes-a',
help='Print demo barcodes for function type A',
action='store_true',
)
demo_group.add_argument(
"--barcodes-b",
help="Print demo barcodes for function type B",
action="store_true",
'--barcodes-b',
help='Print demo barcodes for function type B',
action='store_true',
)
demo_group.add_argument(
"--qr",
help="Print some demo QR codes",
action="store_true",
'--qr',
help='Print some demo QR codes',
action='store_true',
)
demo_group.add_argument(
"--text",
help="Print some demo text",
action="store_true",
'--text',
help='Print some demo text',
action='store_true',
)
parser_command_version = command_subparsers.add_parser(
"version", help="Print the version information of python-escpos"
)
parser_command_version = command_subparsers.add_parser('version',
help='Print the version of python-escpos')
parser_command_version.set_defaults(version=True)
parser_command_version_extended = command_subparsers.add_parser(
"version_extended",
help="Print the extended version information of python-escpos (for bug reports)",
)
parser_command_version_extended.set_defaults(version_extended=True)
return parser
def main():
"""Handle main entry point of CLI script.
Handles loading of configuration and creating and processing of command
line arguments. Called when run from a CLI.
"""
parser = generate_parser()
# hook in argcomplete
if "argcomplete" in globals():
if 'argcomplete' in globals():
argcomplete.autocomplete(parser)
# Get only arguments actually passed
@@ -580,23 +522,16 @@ def main():
if not args_dict:
parser.print_help()
sys.exit()
command_arguments = dict(
[k, v] for k, v in six.iteritems(args_dict) if v is not None
)
command_arguments = dict([k, v] for k, v in six.iteritems(args_dict) if v is not None)
# If version should be printed, do this, then exit
print_version = command_arguments.pop("version", None)
print_version = command_arguments.pop('version', None)
if print_version:
print(version.version)
sys.exit()
print_version_extended = command_arguments.pop("version_extended", None)
if print_version_extended:
print_extended_information()
sys.exit()
# If there was a config path passed, grab it
config_path = command_arguments.pop("config", None)
config_path = command_arguments.pop('config', None)
# Load the configuration and defined printer
saved_config = config.Config()
@@ -604,12 +539,12 @@ def main():
printer = saved_config.printer()
if not printer:
raise Exception("No printers loaded from config")
raise Exception('No printers loaded from config')
target_command = command_arguments.pop("func")
target_command = command_arguments.pop('func')
# remove helper-argument 'parser' from dict
command_arguments.pop("parser", None)
command_arguments.pop('parser', None)
if hasattr(printer, target_command):
# print command with args
@@ -617,14 +552,13 @@ def main():
if target_command in REQUIRES_NEWLINE:
printer.text("\n")
else:
command_arguments["printer"] = printer
command_arguments['printer'] = printer
globals()[target_command](**command_arguments)
def demo(printer, **kwargs):
"""Print demos.
Called when CLI is passed `demo`. This function
"""
Prints specificed demos. Called when CLI is passed `demo`. This function
uses the DEMO_FUNCTIONS dictionary.
:param printer: A printer from escpos.printer
@@ -634,14 +568,14 @@ def demo(printer, **kwargs):
for demo_choice in kwargs.keys():
command = getattr(
printer,
demo_choice.replace("barcodes_a", "barcode").replace(
"barcodes_b", "barcode"
),
demo_choice
.replace('barcodes_a', 'barcode')
.replace('barcodes_b', 'barcode')
)
for params in DEMO_FUNCTIONS[demo_choice]:
command(**params)
printer.cut()
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -1,32 +1,24 @@
"""Helper module for codepage handling."""
from .capabilities import CAPABILITIES
class CodePageManager:
"""Holds information about all the code pages.
Information as defined in escpos-printer-db.
"""Holds information about all the code pages (as defined
in escpos-printer-db).
"""
def __init__(self, data):
"""Initialize codepage manager."""
self.data = data
def get_all(self):
"""Get list of all codepages."""
return self.data.values()
@staticmethod
def get_encoding_name(encoding):
"""Get encoding name.
.. todo:: Resolve the encoding alias.
"""
# TODO resolve the encoding alias
return encoding.upper()
def get_encoding(self, encoding):
"""Return the encoding data."""
return self.data[encoding]
CodePages = CodePageManager(CAPABILITIES["encodings"])
CodePages = CodePageManager(CAPABILITIES['encodings'])

View File

@@ -1,27 +1,33 @@
"""ESC/POS configuration manager.
""" ESC/POS configuration manager.
This module contains the implentations of abstract base class :py:class:`Config`.
This module contains the implementations of abstract base class :py:class:`Config`.
"""
import os
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import appdirs
import yaml
from . import exceptions, printer
from . import printer
from . import exceptions
class Config(object):
"""Configuration handler class.
""" Configuration handler class.
This class loads configuration from a default or specified directory. It
This class loads configuration from a default or specificed directory. It
can create your defined printer and return it to you.
"""
_app_name = "python-escpos"
_config_file = "config.yaml"
_app_name = 'python-escpos'
_config_file = 'config.yaml'
def __init__(self):
"""Initialize configuration.
""" Initialize configuration.
Remember to add anything that needs to be reset between configurations
to self._reset_config
@@ -33,7 +39,7 @@ class Config(object):
self._printer_config = None
def _reset_config(self):
"""Clear the loaded configuration.
""" Clear the loaded configuration.
If we are loading a changed config, we don't want to have leftover
data.
@@ -45,41 +51,42 @@ class Config(object):
self._printer_config = None
def load(self, config_path=None):
"""Load and parse the configuration file using pyyaml.
""" Load and parse the configuration file using pyyaml
:param config_path: An optional file path, file handle, or byte string
for the configuration file.
"""
self._reset_config()
if not config_path:
config_path = os.path.join(
appdirs.user_config_dir(self._app_name), self._config_file
appdirs.user_config_dir(self._app_name),
self._config_file
)
try:
# First check if it's file like. If it is, pyyaml can load it.
# I'm checking type instead of catching exceptions to keep the
# exception handling simple
if hasattr(config_path, "read"):
if hasattr(config_path, 'read'):
config = yaml.safe_load(config_path)
else:
# If it isn't, it's a path. We have to open it first, otherwise
# pyyaml will try to read it as yaml
with open(config_path, "rb") as config_file:
with open(config_path, 'rb') as config_file:
config = yaml.safe_load(config_file)
except EnvironmentError:
raise exceptions.ConfigNotFoundError(
"Couldn't read config at {config_path}".format(
raise exceptions.ConfigNotFoundError('Couldn\'t read config at {config_path}'.format(
config_path=str(config_path),
)
)
))
except yaml.YAMLError:
raise exceptions.ConfigSyntaxError("Error parsing YAML")
raise exceptions.ConfigSyntaxError('Error parsing YAML')
if "printer" in config:
self._printer_config = config["printer"]
self._printer_name = self._printer_config.pop("type").title()
if 'printer' in config:
self._printer_config = config['printer']
self._printer_name = self._printer_config.pop('type').title()
if not self._printer_name or not hasattr(printer, self._printer_name):
raise exceptions.ConfigSyntaxError(
@@ -91,18 +98,17 @@ class Config(object):
self._has_loaded = True
def printer(self):
"""Return a printer that was defined in the config.
""" Returns a printer that was defined in the config, or throws an
exception.
Throw an exception on error.
This method loads the default config if one has not been already loaded.
This method loads the default config if one hasn't beeen already loaded.
"""
if not self._has_loaded:
self.load()
if not self._printer_name:
raise exceptions.ConfigSectionMissingError("printer")
raise exceptions.ConfigSectionMissingError('printer')
if not self._printer:
# We could catch init errors and make them a ConfigSyntaxError,

View File

@@ -1,307 +1,218 @@
# -*- coding: utf-8 -*-
""" Set of ESC/POS Commands (Constants)
This module contains constants that are described in the Esc/Pos-documentation.
Since there is no definitive and unified specification for all Esc/Pos-like printers the constants could later be
This module contains constants that are described in the esc/pos-documentation.
Since there is no definitive and unified specification for all esc/pos-like printers the constants could later be
moved to `capabilities` as in `escpos-php by @mike42 <https://github.com/mike42/escpos-php>`_.
:author: python-escpos developers
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
:license: MIT
"""
from typing import Dict
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import six
from .types import ConstTxtStyleClass
# Control characters
# as labelled in https://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf
NUL: bytes = b"\x00"
EOT: bytes = b"\x04"
ENQ: bytes = b"\x05"
DLE: bytes = b"\x10"
DC4: bytes = b"\x14"
CAN: bytes = b"\x18"
ESC: bytes = b"\x1b"
FS: bytes = b"\x1c"
GS: bytes = b"\x1d"
# as labelled in http://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf
NUL = b'\x00'
EOT = b'\x04'
ENQ = b'\x05'
DLE = b'\x10'
DC4 = b'\x14'
CAN = b'\x18'
ESC = b'\x1b'
FS = b'\x1c'
GS = b'\x1d'
# Feed control sequences
CTL_LF: bytes = b"\n" #: Print and line feed
CTL_FF: bytes = b"\f" #: Form feed
CTL_CR: bytes = b"\r" #: Carriage return
CTL_HT: bytes = b"\t" #: Horizontal tab
CTL_SET_HT: bytes = ESC + b"\x44" #: Set horizontal tab positions
CTL_VT: bytes = b"\v" #: Vertical tab
CTL_LF = b'\n' # Print and line feed
CTL_FF = b'\f' # Form feed
CTL_CR = b'\r' # Carriage return
CTL_HT = b'\t' # Horizontal tab
CTL_SET_HT = ESC + b'\x44' # Set horizontal tab positions
CTL_VT = b'\v' # Vertical tab
# Printer hardware
HW_INIT: bytes = ESC + b"@" # Clear data in buffer and reset modes
HW_SELECT: bytes = ESC + b"=\x01" # Printer select
HW_INIT = ESC + b'@' # Clear data in buffer and reset modes
HW_SELECT = ESC + b'=\x01' # Printer select
HW_RESET: bytes = ESC + b"\x3f\x0a\x00" # Reset printer hardware
# (TODO: Where is this specified?)
HW_RESET = ESC + b'\x3f\x0a\x00' # Reset printer hardware
# (TODO: Where is this specified?)
# Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>)
_CASH_DRAWER = (
lambda m, t1="", t2="": ESC + b"p" + m + six.int2byte(t1) + six.int2byte(t2)
)
#: decimal cash drawer kick sequence
CD_KICK_DEC_SEQUENCE = (
lambda esc, p, m, t1=50, t2=50: six.int2byte(esc)
+ six.int2byte(p)
+ six.int2byte(m)
+ six.int2byte(t1)
+ six.int2byte(t2)
)
#: Sends a pulse to pin 2 []
CD_KICK_2: bytes = _CASH_DRAWER(b"\x00", 50, 50)
#: Sends a pulse to pin 5 []
CD_KICK_5: bytes = _CASH_DRAWER(b"\x01", 50, 50)
_CASH_DRAWER = lambda m, t1='', t2='': ESC + b'p' + m + six.int2byte(t1) + six.int2byte(t2)
CD_KICK_DEC_SEQUENCE = lambda esc, p, m, t1=50, t2=50: six.int2byte(esc) + six.int2byte(p) + six.int2byte(m) + six.int2byte(t1) + six.int2byte(t2)
CD_KICK_2 = _CASH_DRAWER(b'\x00', 50, 50) # Sends a pulse to pin 2 []
CD_KICK_5 = _CASH_DRAWER(b'\x01', 50, 50) # Sends a pulse to pin 5 []
# Paper Cutter
_CUT_PAPER = lambda m: GS + b"V" + m
PAPER_FULL_CUT: bytes = _CUT_PAPER(b"\x00") #: Full cut paper
PAPER_PART_CUT: bytes = _CUT_PAPER(b"\x01") #: Partial cut paper
_CUT_PAPER = lambda m: GS + b'V' + m
PAPER_FULL_CUT = _CUT_PAPER(b'\x00') # Full cut paper
PAPER_PART_CUT = _CUT_PAPER(b'\x01') # Partial cut paper
# Beep (please note that the actual beep sequence may differ between devices)
BEEP: bytes = b"\x07"
# Internal buzzer (only supported printers)
BUZZER: bytes = ESC + b"\x42"
# Beep
BEEP = b'\x07'
# Panel buttons (e.g. the FEED button)
_PANEL_BUTTON = lambda n: ESC + b"c5" + six.int2byte(n)
PANEL_BUTTON_ON: bytes = _PANEL_BUTTON(0) # enable all panel buttons
PANEL_BUTTON_OFF: bytes = _PANEL_BUTTON(1) # disable all panel buttons
# Line display printing
LINE_DISPLAY_OPEN: bytes = ESC + b"\x3d\x02"
LINE_DISPLAY_CLEAR: bytes = ESC + b"\x40"
LINE_DISPLAY_CLOSE: bytes = ESC + b"\x3d\x01"
_PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n)
PANEL_BUTTON_ON = _PANEL_BUTTON(0) # enable all panel buttons
PANEL_BUTTON_OFF = _PANEL_BUTTON(1) # disable all panel buttons
# Sheet modes
SHEET_SLIP_MODE: bytes = ESC + b"\x63\x30\x04" # slip paper
SHEET_ROLL_MODE: bytes = ESC + b"\x63\x30\x01" # paper roll
# Slip specific codes
SLIP_EJECT: bytes = ESC + b"\x4b\xc0" # Eject the slip or cheque
SLIP_SELECT: bytes = FS # Select the slip station as default station
SLIP_SET_WAIT_TIME: bytes = (
ESC + b"\x1b\x66"
) # Set timeout waiting for a slip/cheque to be inserted
SLIP_PRINT_AND_EJECT: bytes = (
b"\x0c" # Print the buffer and eject (after waiting for the paper to be inserted)
)
SHEET_SLIP_MODE = ESC + b'\x63\x30\x04' # slip paper
SHEET_ROLL_MODE = ESC + b'\x63\x30\x01' # paper roll
# Text format
# TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll
# Printers" and tidy up this stuff too.
TXT_SIZE: bytes = GS + b"!"
TXT_NORMAL: bytes = ESC + b"!\x00" # Normal text
#: text style dictionary for :py:meth:`escpos.escpos.Escpos.set`
TXT_STYLE: ConstTxtStyleClass = {
"bold": {
False: ESC + b"\x45\x00", # Bold font OFF
True: ESC + b"\x45\x01", # Bold font ON
},
"underline": {
0: ESC + b"\x2d\x00", # Underline font OFF
1: ESC + b"\x2d\x01", # Underline font 1-dot ON
2: ESC + b"\x2d\x02", # Underline font 2-dot ON
},
"size": {
"normal": TXT_NORMAL + ESC + b"!\x00", # Normal text
"2h": TXT_NORMAL + ESC + b"!\x10", # Double height text
"2w": TXT_NORMAL + ESC + b"!\x20", # Double width text
"2x": TXT_NORMAL + ESC + b"!\x30", # Quad area text
},
"font": {
"a": ESC + b"\x4d\x00", # Font type A
"b": ESC + b"\x4d\x00", # Font type B
},
"align": {
"left": ESC + b"\x61\x00", # Left justification
"center": ESC + b"\x61\x01", # Centering
"right": ESC + b"\x61\x02", # Right justification
},
"invert": {
True: GS + b"\x42\x01", # Inverse Printing ON
False: GS + b"\x42\x00", # Inverse Printing OFF
},
"color": {
"black": ESC + b"\x72\x00", # Default Color
"red": ESC + b"\x72\x01", # Alternative Color, Usually Red
},
"flip": {True: ESC + b"\x7b\x01", False: ESC + b"\x7b\x00"}, # Flip ON # Flip OFF
"density": {
0: GS + b"\x7c\x00", # Printing Density -50%
1: GS + b"\x7c\x01", # Printing Density -37.5%
2: GS + b"\x7c\x02", # Printing Density -25%
3: GS + b"\x7c\x03", # Printing Density -12.5%
4: GS + b"\x7c\x04", # Printing Density 0%
5: GS + b"\x7c\x08", # Printing Density +50%
6: GS + b"\x7c\x07", # Printing Density +37.5%
7: GS + b"\x7c\x06", # Printing Density +25%
8: GS + b"\x7c\x05", # Printing Density +12.5%
},
"smooth": {
True: GS + b"\x62\x01", # Smooth ON
False: GS + b"\x62\x00", # Smooth OFF
},
"height": { # Custom text height
1: 0x00,
2: 0x01,
3: 0x02,
4: 0x03,
5: 0x04,
6: 0x05,
7: 0x06,
8: 0x07,
},
"width": { # Custom text width
1: 0x00,
TXT_FLIP_ON = ESC + b'\x7b\x01'
TXT_FLIP_OFF = ESC + b'\x7b\x00'
TXT_SMOOTH_ON = GS + b'\x62\x01'
TXT_SMOOTH_OFF = GS + b'\x62\x00'
TXT_SIZE = GS + b'!'
TXT_WIDTH = {1: 0x00,
2: 0x10,
3: 0x20,
4: 0x30,
5: 0x40,
6: 0x50,
7: 0x60,
8: 0x70,
},
}
8: 0x70}
TXT_HEIGHT = {1: 0x00,
2: 0x01,
3: 0x02,
4: 0x03,
5: 0x04,
6: 0x05,
7: 0x06,
8: 0x07}
TXT_NORMAL = ESC + b'!\x00' # Normal text
TXT_2HEIGHT = ESC + b'!\x10' # Double height text
TXT_2WIDTH = ESC + b'!\x20' # Double width text
TXT_4SQUARE = ESC + b'!\x30' # Quad area text
TXT_UNDERL_OFF = ESC + b'\x2d\x00' # Underline font OFF
TXT_UNDERL_ON = ESC + b'\x2d\x01' # Underline font 1-dot ON
TXT_UNDERL2_ON = ESC + b'\x2d\x02' # Underline font 2-dot ON
TXT_BOLD_OFF = ESC + b'\x45\x00' # Bold font OFF
TXT_BOLD_ON = ESC + b'\x45\x01' # Bold font ON
TXT_ALIGN_LT = ESC + b'\x61\x00' # Left justification
TXT_ALIGN_CT = ESC + b'\x61\x01' # Centering
TXT_ALIGN_RT = ESC + b'\x61\x02' # Right justification
TXT_INVERT_ON = GS + b'\x42\x01' # Inverse Printing ON
TXT_INVERT_OFF = GS + b'\x42\x00' # Inverse Printing OFF
# Fonts
SET_FONT = lambda n: ESC + b"\x4d" + n
TXT_FONT_A: bytes = SET_FONT(b"\x00") #: Font type A
TXT_FONT_B: bytes = SET_FONT(b"\x01") #: Font type B
SET_FONT = lambda n: ESC + b'\x4d' + n
TXT_FONT_A = SET_FONT(b'\x00') # Font type A
TXT_FONT_B = SET_FONT(b'\x01') # Font type B
# Text colors
TXT_COLOR_BLACK = ESC + b'\x72\x00' # Default Color
TXT_COLOR_RED = ESC + b'\x72\x01' # Alternative Color (Usually Red)
# Spacing
LINESPACING_RESET = ESC + b"2"
LINESPACING_FUNCS: Dict[int, bytes] = {
60: ESC + b"A", # line_spacing/60 of an inch, 0 <= line_spacing <= 85
360: ESC + b"+", # line_spacing/360 of an inch, 0 <= line_spacing <= 255
180: ESC + b"3", # line_spacing/180 of an inch, 0 <= line_spacing <= 255
LINESPACING_RESET = ESC + b'2'
LINESPACING_FUNCS = {
60: ESC + b'A', # line_spacing/60 of an inch, 0 <= line_spacing <= 85
360: ESC + b'+', # line_spacing/360 of an inch, 0 <= line_spacing <= 255
180: ESC + b'3', # line_spacing/180 of an inch, 0 <= line_spacing <= 255
}
#: Prefix to change the codepage. You need to attach a byte to indicate
#: the codepage to use. We use escpos-printer-db as the data source.
CODEPAGE_CHANGE: bytes = ESC + b"\x74"
# Prefix to change the codepage. You need to attach a byte to indicate
# the codepage to use. We use escpos-printer-db as the data source.
CODEPAGE_CHANGE = ESC + b'\x74'
# Barcode format
_SET_BARCODE_TXT_POS = lambda n: GS + b"H" + n
BARCODE_TXT_OFF: bytes = _SET_BARCODE_TXT_POS(b"\x00") #: HRI barcode chars OFF
BARCODE_TXT_ABV: bytes = _SET_BARCODE_TXT_POS(b"\x01") #: HRI barcode chars above
BARCODE_TXT_BLW: bytes = _SET_BARCODE_TXT_POS(b"\x02") #: HRI barcode chars below
BARCODE_TXT_BTH: bytes = _SET_BARCODE_TXT_POS(b"\x03") #: HRI both above and below
_SET_BARCODE_TXT_POS = lambda n: GS + b'H' + n
BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS(b'\x00') # HRI barcode chars OFF
BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS(b'\x01') # HRI barcode chars above
BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS(b'\x02') # HRI barcode chars below
BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS(b'\x03') # HRI both above and below
_SET_HRI_FONT = lambda n: GS + b"f" + n
BARCODE_FONT_A: bytes = _SET_HRI_FONT(b"\x00") #: Font type A for HRI barcode chars
BARCODE_FONT_B: bytes = _SET_HRI_FONT(b"\x01") #: Font type B for HRI barcode chars
_SET_HRI_FONT = lambda n: GS + b'f' + n
BARCODE_FONT_A = _SET_HRI_FONT(b'\x00') # Font type A for HRI barcode chars
BARCODE_FONT_B = _SET_HRI_FONT(b'\x01') # Font type B for HRI barcode chars
BARCODE_HEIGHT: bytes = GS + b"h" #: Barcode Height [1-255]
BARCODE_WIDTH: bytes = GS + b"w" #: Barcode Width [2-6]
BARCODE_HEIGHT = GS + b'h' # Barcode Height [1-255]
BARCODE_WIDTH = GS + b'w' # Barcode Width [2-6]
# NOTE: This isn't actually an ESC/POS command. It's the common prefix to the
# two "print bar code" commands:
# - Type A: "GS k <type as integer> <data> NUL"
# - TYPE B: "GS k <type as letter> <data length> <data>"
# The latter command supports more barcode types
_SET_BARCODE_TYPE = lambda m: GS + b"k" + six.int2byte(m)
_SET_BARCODE_TYPE = lambda m: GS + b'k' + six.int2byte(m)
#: Barcodes for printing function type A
BARCODE_TYPE_A: Dict[str, bytes] = {
"UPC-A": _SET_BARCODE_TYPE(0),
"UPC-E": _SET_BARCODE_TYPE(1),
"EAN13": _SET_BARCODE_TYPE(2),
"EAN8": _SET_BARCODE_TYPE(3),
"CODE39": _SET_BARCODE_TYPE(4),
"ITF": _SET_BARCODE_TYPE(5),
"NW7": _SET_BARCODE_TYPE(6),
"CODABAR": _SET_BARCODE_TYPE(6), # Same as NW7
# Barcodes for printing function type A
BARCODE_TYPE_A = {
'UPC-A': _SET_BARCODE_TYPE(0),
'UPC-E': _SET_BARCODE_TYPE(1),
'EAN13': _SET_BARCODE_TYPE(2),
'EAN8': _SET_BARCODE_TYPE(3),
'CODE39': _SET_BARCODE_TYPE(4),
'ITF': _SET_BARCODE_TYPE(5),
'NW7': _SET_BARCODE_TYPE(6),
'CODABAR': _SET_BARCODE_TYPE(6), # Same as NW7
}
#: Barcodes for printing function type B
#: The first 8 are the same barcodes as type A
BARCODE_TYPE_B: Dict[str, bytes] = {
"UPC-A": _SET_BARCODE_TYPE(65),
"UPC-E": _SET_BARCODE_TYPE(66),
"EAN13": _SET_BARCODE_TYPE(67),
"EAN8": _SET_BARCODE_TYPE(68),
"CODE39": _SET_BARCODE_TYPE(69),
"ITF": _SET_BARCODE_TYPE(70),
"NW7": _SET_BARCODE_TYPE(71),
"CODABAR": _SET_BARCODE_TYPE(71), # Same as NW7
"CODE93": _SET_BARCODE_TYPE(72),
"CODE128": _SET_BARCODE_TYPE(73),
"GS1-128": _SET_BARCODE_TYPE(74),
"GS1 DATABAR OMNIDIRECTIONAL": _SET_BARCODE_TYPE(75),
"GS1 DATABAR TRUNCATED": _SET_BARCODE_TYPE(76),
"GS1 DATABAR LIMITED": _SET_BARCODE_TYPE(77),
"GS1 DATABAR EXPANDED": _SET_BARCODE_TYPE(78),
# Barcodes for printing function type B
# The first 8 are the same barcodes as type A
BARCODE_TYPE_B = {
'UPC-A': _SET_BARCODE_TYPE(65),
'UPC-E': _SET_BARCODE_TYPE(66),
'EAN13': _SET_BARCODE_TYPE(67),
'EAN8': _SET_BARCODE_TYPE(68),
'CODE39': _SET_BARCODE_TYPE(69),
'ITF': _SET_BARCODE_TYPE(70),
'NW7': _SET_BARCODE_TYPE(71),
'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7
'CODE93': _SET_BARCODE_TYPE(72),
'CODE128': _SET_BARCODE_TYPE(73),
'GS1-128': _SET_BARCODE_TYPE(74),
'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75),
'GS1 DATABAR TRUNCATED': _SET_BARCODE_TYPE(76),
'GS1 DATABAR LIMITED': _SET_BARCODE_TYPE(77),
'GS1 DATABAR EXPANDED': _SET_BARCODE_TYPE(78),
}
#: supported barcode formats
BARCODE_FORMATS = {
"UPC-A": ([(11, 12)], r"^[0-9]{11,12}$"),
"UPC-E": ([(7, 8), (11, 12)], r"^([0-9]{7,8}|[0-9]{11,12})$"),
"EAN13": ([(12, 13)], r"^[0-9]{12,13}$"),
"EAN8": ([(7, 8)], r"^[0-9]{7,8}$"),
"CODE39": ([(1, 255)], r"^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"),
"ITF": ([(2, 255)], r"^([0-9]{2})+$"),
"NW7": ([(1, 255)], r"^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"),
"CODABAR": ([(1, 255)], r"^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), # Same as NW7
"CODE93": ([(1, 255)], r"^[\x00-\x7F]+$"),
"CODE128": ([(2, 255)], r"^\{[A-C][\x00-\x7F]+$"),
"GS1-128": ([(2, 255)], r"^\{[A-C][\x00-\x7F]+$"), # same as CODE128
"GS1 DATABAR OMNIDIRECTIONAL": ([(13, 13)], r"^[0-9]{13}$"),
"GS1 DATABAR TRUNCATED": (
[(13, 13)],
r"^[0-9]{13}$",
), # same as GS1 omnidirectional
"GS1 DATABAR LIMITED": ([(13, 13)], r"^[01][0-9]{12}$"),
"GS1 DATABAR EXPANDED": (
[(2, 255)],
r"^\([0-9][A-Za-z0-9 \!\"\%\&'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$",
),
}
BARCODE_TYPES: Dict[str, Dict[str, bytes]] = {
"A": BARCODE_TYPE_A,
"B": BARCODE_TYPE_B,
BARCODE_TYPES = {
'A': BARCODE_TYPE_A,
'B': BARCODE_TYPE_B,
}
# QRCode error correction levels
QR_ECLEVEL_L: int = 0
QR_ECLEVEL_M: int = 1
QR_ECLEVEL_Q: int = 2
QR_ECLEVEL_H: int = 3
QR_ECLEVEL_L = 0
QR_ECLEVEL_M = 1
QR_ECLEVEL_Q = 2
QR_ECLEVEL_H = 3
# QRcode models
QR_MODEL_1: int = 1
QR_MODEL_2: int = 2
QR_MICRO: int = 3
QR_MODEL_1 = 1
QR_MODEL_2 = 2
QR_MICRO = 3
# Image format
# NOTE: _PRINT_RASTER_IMG is the obsolete ESC/POS "print raster bit image"
# command. The constants include a fragment of the data's header.
_PRINT_RASTER_IMG = lambda data: GS + b"v0" + data
S_RASTER_N: bytes = _PRINT_RASTER_IMG(b"\x00") # Set raster image normal size
S_RASTER_2W: bytes = _PRINT_RASTER_IMG(b"\x01") # Set raster image double width
S_RASTER_2H: bytes = _PRINT_RASTER_IMG(b"\x02") # Set raster image double height
S_RASTER_Q: bytes = _PRINT_RASTER_IMG(b"\x03") # Set raster image quadruple
_PRINT_RASTER_IMG = lambda data: GS + b'v0' + data
S_RASTER_N = _PRINT_RASTER_IMG(b'\x00') # Set raster image normal size
S_RASTER_2W = _PRINT_RASTER_IMG(b'\x01') # Set raster image double width
S_RASTER_2H = _PRINT_RASTER_IMG(b'\x02') # Set raster image double height
S_RASTER_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple
# Status Command
RT_STATUS: bytes = DLE + EOT
RT_STATUS_ONLINE: bytes = RT_STATUS + b"\x01"
RT_STATUS_PAPER: bytes = RT_STATUS + b"\x04"
RT_MASK_ONLINE: int = 8
RT_MASK_PAPER: int = 18
RT_MASK_LOWPAPER: int = 30
RT_MASK_NOPAPER: int = 114
# Printing Density
PD_N50 = GS + b'\x7c\x00' # Printing Density -50%
PD_N37 = GS + b'\x7c\x01' # Printing Density -37.5%
PD_N25 = GS + b'\x7c\x02' # Printing Density -25%
PD_N12 = GS + b'\x7c\x03' # Printing Density -12.5%
PD_0 = GS + b'\x7c\x04' # Printing Density 0%
PD_P50 = GS + b'\x7c\x08' # Printing Density +50%
PD_P37 = GS + b'\x7c\x07' # Printing Density +37.5%
PD_P25 = GS + b'\x7c\x06' # Printing Density +25%
PD_P12 = GS + b'\x7c\x05' # Printing Density +12.5%

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
"""ESC/POS Exceptions classes.
""" ESC/POS Exceptions classes
Result/Exit codes:
@@ -8,37 +8,31 @@ Result/Exit codes:
- `20` = Barcode size values are out of range :py:exc:`~escpos.exceptions.BarcodeSizeError`
- `30` = Barcode text not supplied :py:exc:`~escpos.exceptions.BarcodeCodeError`
- `40` = Image height is too large :py:exc:`~escpos.exceptions.ImageSizeError`
- `41` = Image width is too large :py:exc:`~escpos.exceptions.ImageWidthError`
- `50` = No string supplied to be printed :py:exc:`~escpos.exceptions.TextError`
- `60` = Invalid pin to send Cash Drawer pulse :py:exc:`~escpos.exceptions.CashDrawerError`
- `70` = Invalid number of tab positions :py:exc:`~escpos.exceptions.TabPosError`
- `80` = Invalid char code :py:exc:`~escpos.exceptions.CharCodeError`
- `90` = Device not found :py:exc:`~escpos.exceptions.DeviceNotFoundError`
- `91` = USB device not found :py:exc:`~escpos.exceptions.USBNotFoundError`
- `90` = USB device not found :py:exc:`~escpos.exceptions.USBNotFoundError`
- `100` = Set variable out of range :py:exc:`~escpos.exceptions.SetVariableError`
- `200` = Configuration not found :py:exc:`~escpos.exceptions.ConfigNotFoundError`
- `210` = Configuration syntax error :py:exc:`~escpos.exceptions.ConfigSyntaxError`
- `220` = Configuration section not found :py:exc:`~escpos.exceptions.ConfigSectionMissingError`
:author: python-escpos developers
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
class Error(Exception):
"""Base class for ESC/POS errors.
inheritance:
.. inheritance-diagram:: escpos.exceptions.Error
:parts: 1
"""
""" Base class for ESC/POS errors """
def __init__(self, msg, status=None):
"""Initialize Error object."""
Exception.__init__(self)
self.msg = msg
self.resultcode = 1
@@ -46,378 +40,202 @@ class Error(Exception):
self.resultcode = status
def __str__(self):
"""Return string representation of Error."""
return self.msg
class BarcodeTypeError(Error):
"""No Barcode type defined.
""" No Barcode type defined.
This exception indicates that no known barcode-type has been entered. The barcode-type has to be
one of those specified in :py:meth:`escpos.escpos.Escpos.barcode`.
The returned error code is `10`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.BarcodeTypeError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize BarcodeTypeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 10
def __str__(self):
"""Return string representation of BarcodeTypeError."""
return "No Barcode type is defined ({msg})".format(msg=self.msg)
class BarcodeSizeError(Error):
"""Barcode size is out of range.
""" Barcode size is out of range.
This exception indicates that the values for the barcode size are out of range.
The size of the barcode has to be in the range that is specified in :py:meth:`escpos.escpos.Escpos.barcode`.
The resulting return code is `20`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.BarcodeSizeError
:parts: 1
The resulting returncode is `20`.
"""
def __init__(self, msg=""):
"""Initialize BarcodeSizeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 20
def __str__(self):
"""Return string representation of BarcodeSizeError."""
return "Barcode size is out of range ({msg})".format(msg=self.msg)
class BarcodeCodeError(Error):
"""No Barcode code was supplied, or it is incorrect.
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 return code for this exception is `30`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.BarcodeCodeError
:parts: 1
""" No Barcode code was supplied.
No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode`.
The returncode for this exception is `30`.
"""
def __init__(self, msg=""):
"""Initialize BarcodeCodeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 30
def __str__(self):
"""Return string representation of BarcodeCodeError."""
return "No Barcode code was supplied ({msg})".format(msg=self.msg)
class ImageSizeError(Error):
"""Image height is longer than 255px and can't be printed.
The return code for this exception is `40`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.ImageSizeError
:parts: 1
""" Image height is longer than 255px and can't be printed.
The returncode for this exception is `40`.
"""
def __init__(self, msg=""):
"""Initialize ImageSizeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 40
def __str__(self):
"""Return string representation of ImageSizeError."""
return "Image height is longer than 255px and can't be printed ({msg})".format(
msg=self.msg
)
class ImageWidthError(Error):
"""Image width is too large.
The return code for this exception is `41`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.ImageWidthError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize ImageWidthError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 41
def __str__(self):
"""Return string representation of ImageWidthError."""
return "Image width is too large ({msg})".format(msg=self.msg)
return "Image height is longer than 255px and can't be printed ({msg})".format(msg=self.msg)
class TextError(Error):
"""Text string must be supplied to the `text()` method.
""" Text string must be supplied to the `text()` method.
This exception is raised when an empty string is passed to :py:meth:`escpos.escpos.Escpos.text`.
The return code for this exception is `50`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.TextError
:parts: 1
The returncode for this exception is `50`.
"""
def __init__(self, msg=""):
"""Initialize TextError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 50
def __str__(self):
"""Return string representation of TextError."""
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):
"""Valid pin must be set in order to send pulse.
""" Valid pin must be set in order to send pulse.
A valid pin number has to be passed onto the method :py:meth:`escpos.escpos.Escpos.cashdraw`.
The return code for this exception is `60`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.CashDrawerError
:parts: 1
The returncode for this exception is `60`.
"""
def __init__(self, msg=""):
"""Initialize CashDrawerError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 60
def __str__(self):
"""Return string representation of CashDrawerError."""
return "Valid pin must be set to send pulse ({msg})".format(msg=self.msg)
class TabPosError(Error):
"""Tab position is invalid.
Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values.
Both values multiplied must not exceed 255, since it is the maximum tab value.
""" Valid tab positions must be in the range 0 to 16.
This exception is raised by :py:meth:`escpos.escpos.Escpos.control`.
The return code for this exception is `70`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.TabPosError
:parts: 1
The returncode for this exception is `70`.
"""
def __init__(self, msg=""):
"""Initialize TabPosError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 70
def __str__(self):
"""Return string representation of TabPosError."""
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):
"""Valid char code must be set.
""" Valid char code must be set.
The supplied charcode-name in :py:meth:`escpos.escpos.Escpos.charcode` is unknown.
The return code for this exception is `80`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.CharCodeError
:parts: 1
Ths returncode for this exception is `80`.
"""
def __init__(self, msg=""):
"""Initialize CharCodeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 80
def __str__(self):
"""Return string representation of CharCodeError."""
return "Valid char code must be set ({msg})".format(msg=self.msg)
class DeviceNotFoundError(Error):
"""Device was not found.
The device seems to be not accessible.
The return code for this exception is `90`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.Error
:parts: 1
class USBNotFoundError(Error):
""" Device wasn't found (probably not plugged in)
The USB device seems to be not plugged in.
Ths returncode for this exception is `90`.
"""
def __init__(self, msg=""):
"""Initialize DeviceNotFoundError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 90
def __str__(self):
"""Return string representation of DeviceNotFoundError."""
return f"Device not found ({self.msg})"
class USBNotFoundError(DeviceNotFoundError):
"""USB device was not found (probably not plugged in).
The USB device seems to be not plugged in.
The return code for this exception is `91`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.USBNotFoundError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize USBNotFoundError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 91
def __str__(self):
"""Return string representation of USBNotFoundError."""
return f"USB device not found ({self.msg})"
return "USB device not found ({msg})".format(msg=self.msg)
class SetVariableError(Error):
"""A set method variable was out of range.
""" A set method variable was out of range
Check set variables against minimum and maximum values
The return code for this exception is `100`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.SetVariableError
:parts: 1
Ths returncode for this exception is `100`.
"""
def __init__(self, msg=""):
"""Initialize SetVariableError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 100
def __str__(self):
"""Return string representation of SetVariableError."""
return "Set variable out of range ({msg})".format(msg=self.msg)
# Configuration errors
class ConfigNotFoundError(Error):
"""The configuration file was not found.
""" The configuration file was not found
The default or passed configuration file could not be read
The return code for this exception is `200`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.ConfigNotFoundError
:parts: 1
Ths returncode for this exception is `200`.
"""
def __init__(self, msg=""):
"""Initialize ConfigNotFoundError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 200
def __str__(self):
"""Return string representation of ConfigNotFoundError."""
return "Configuration not found ({msg})".format(msg=self.msg)
class ConfigSyntaxError(Error):
"""The configuration file is invalid.
""" The configuration file is invalid
The syntax is incorrect
The return code for this exception is `210`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.ConfigSyntaxError
:parts: 1
Ths returncode for this exception is `210`.
"""
def __init__(self, msg=""):
"""Initialize ConfigSyntaxError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 210
def __str__(self):
"""Return string representation of ConfigSyntaxError."""
return "Configuration syntax is invalid ({msg})".format(msg=self.msg)
class ConfigSectionMissingError(Error):
"""The configuration file is missing a section.
The part of the config asked for does not exist in the loaded configuration
The return code for this exception is `220`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.ConfigSectionMissingError
:parts: 1
""" The configuration file is missing a section
The part of the config asked for doesn't exist in the loaded configuration
Ths returncode for this exception is `220`.
"""
def __init__(self, msg=""):
"""Initialize ConfigSectionMissingError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 220
def __str__(self):
"""Return string representation of ConfigSectionMissingError."""
return "Configuration section is missing ({msg})".format(msg=self.msg)

View File

@@ -1,4 +1,4 @@
"""Image format handling class.
""" Image format handling class
This module contains the image format handler :py:class:`EscposImage`.
@@ -8,9 +8,12 @@ This module contains the image format handler :py:class:`EscposImage`.
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import math
from PIL import Image, ImageOps
@@ -23,7 +26,8 @@ class EscposImage(object):
"""
def __init__(self, img_source):
"""Load in an image.
"""
Load in an image
:param img_source: PIL.Image, or filename to load one from.
"""
@@ -37,7 +41,7 @@ class EscposImage(object):
# Convert to white RGB background, paste over white background
# to strip alpha.
img_original = img_original.convert("RGBA")
img_original = img_original.convert('RGBA')
im = Image.new("RGB", img_original.size, (255, 255, 255))
im.paste(img_original, mask=img_original.split()[3])
# Convert down to greyscale
@@ -49,23 +53,30 @@ class EscposImage(object):
@property
def width(self):
"""Return width of image in pixels."""
"""
Width of image in pixels
"""
width_pixels, _ = self._im.size
return width_pixels
@property
def width_bytes(self):
"""Return width of image if you use 8 pixels per byte and 0-pad at the end."""
"""
Width of image if you use 8 pixels per byte and 0-pad at the end.
"""
return (self.width + 7) >> 3
@property
def height(self):
"""Height of image in pixels."""
"""
Height of image in pixels
"""
_, height_pixels = self._im.size
return height_pixels
def to_column_format(self, high_density_vertical=True):
"""Extract slices of an image as equal-sized blobs of column-format data.
"""
Extract slices of an image as equal-sized blobs of column-format data.
:param high_density_vertical: Printed line height in dots
"""
@@ -78,20 +89,23 @@ class EscposImage(object):
box = (left, top, left + line_height, top + height_pixels)
im_slice = im.transform((line_height, height_pixels), Image.EXTENT, box)
im_bytes = im_slice.tobytes()
yield (im_bytes)
yield(im_bytes)
left += line_height
def to_raster_format(self):
"""Convert image to raster-format binary."""
"""
Convert image to raster-format binary
"""
return self._im.tobytes()
def split(self, fragment_height):
"""Split an image into multiple fragments after fragment_height pixels.
"""
Split an image into multiple fragments after fragment_height pixels
:param fragment_height: height of fragment
:return: list of PIL objects
"""
passes = int(math.ceil(self.height / fragment_height))
passes = int(math.ceil(self.height/fragment_height))
fragments = []
for n in range(0, passes):
left = 0
@@ -101,19 +115,3 @@ class EscposImage(object):
box = (left, upper, right, lower)
fragments.append(self.img_original.crop(box))
return fragments
def center(self, max_width):
"""Center image in place.
:param: Maximum width in order to deduce x offset for centering
:return: None
"""
old_width, height = self._im.size
new_size = (max_width, height)
new_im = Image.new("1", new_size)
paste_x = int((max_width - old_width) / 2)
new_im.paste(self._im, (paste_x, 0))
self._im = new_im

View File

@@ -4,6 +4,11 @@
I doubt that this currently works correctly.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
try:
import jaconv
@@ -34,68 +39,69 @@ def encode_katakana(text):
TXT_ENC_KATAKANA_MAP = {
# Maps UTF-8 Katakana symbols to KATAKANA Page Codes
# TODO: has this really to be hardcoded?
# Half-Width Katakanas
"": b"\xa1",
"": b"\xa2",
"": b"\xa3",
"": b"\xa4",
"": b"\xa5",
"": b"\xa6",
"": b"\xa7",
"": b"\xa8",
"": b"\xa9",
"": b"\xaa",
"": b"\xab",
"": b"\xac",
"": b"\xad",
"": b"\xae",
"": b"\xaf",
"": b"\xb0",
"": b"\xb1",
"": b"\xb2",
"": b"\xb3",
"": b"\xb4",
"": b"\xb5",
"": b"\xb6",
"": b"\xb7",
"": b"\xb8",
"": b"\xb9",
"": b"\xba",
"": b"\xbb",
"": b"\xbc",
"": b"\xbd",
"": b"\xbe",
"ソ": b"\xbf",
"": b"\xc0",
"": b"\xc1",
"": b"\xc2",
"": b"\xc3",
"": b"\xc4",
"": b"\xc5",
"": b"\xc6",
"": b"\xc7",
"": b"\xc8",
"": b"\xc9",
"": b"\xca",
"": b"\xcb",
"": b"\xcc",
"": b"\xcd",
"": b"\xce",
"": b"\xcf",
"": b"\xd0",
"": b"\xd1",
"": b"\xd2",
"": b"\xd3",
"": b"\xd4",
"": b"\xd5",
"": b"\xd6",
"": b"\xd7",
"": b"\xd8",
"": b"\xd9",
"": b"\xda",
"": b"\xdb",
"": b"\xdc",
"": b"\xdd",
"": b"\xde",
"": b"\xdf",
'': b'\xa1',
'': b'\xa2',
'': b'\xa3',
'': b'\xa4',
'': b'\xa5',
'': b'\xa6',
'': b'\xa7',
'': b'\xa8',
'': b'\xa9',
'': b'\xaa',
'': b'\xab',
'': b'\xac',
'': b'\xad',
'': b'\xae',
'': b'\xaf',
'': b'\xb0',
'': b'\xb1',
'': b'\xb2',
'': b'\xb3',
'': b'\xb4',
'': b'\xb5',
'': b'\xb6',
'': b'\xb7',
'': b'\xb8',
'': b'\xb9',
'': b'\xba',
'': b'\xbb',
'': b'\xbc',
'': b'\xbd',
'': b'\xbe',
'ソ': b'\xbf',
'': b'\xc0',
'': b'\xc1',
'': b'\xc2',
'': b'\xc3',
'': b'\xc4',
'': b'\xc5',
'': b'\xc6',
'': b'\xc7',
'': b'\xc8',
'': b'\xc9',
'': b'\xca',
'': b'\xcb',
'': b'\xcc',
'': b'\xcd',
'': b'\xce',
'': b'\xcf',
'': b'\xd0',
'': b'\xd1',
'': b'\xd2',
'': b'\xd3',
'': b'\xd4',
'': b'\xd5',
'': b'\xd6',
'': b'\xd7',
'': b'\xd8',
'': b'\xd9',
'': b'\xda',
'': b'\xdb',
'': b'\xdc',
'': b'\xdd',
'': b'\xde',
'': b'\xdf',
}

View File

@@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""Magic Encode.
""" Magic Encode
This module tries to convert an UTF-8 string to an encoded string for the printer.
It uses trial and error in order to guess the right codepage.
@@ -12,78 +12,78 @@ The code is based on the encoding-code in py-xml-escpos by @fvdsn.
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import re
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):
"""Take available code spaces and pick the right one for a given character.
"""Takes a list of available code spaces. Picks the right one for a
given character.
Note: To determine the code page, it needs to do the conversion, and
thus already knows what the final byte in the target encoding would
be. Nevertheless, the API of this class does not return the byte.
be. Nevertheless, the API of this class doesn't return the byte.
The caller use to do the character conversion itself.
$ python -m timeit -s "{u'ö':'a'}.get(u'ö')"
100000000 loops, best of 3: 0.0133 usec per loop
$ python -m timeit -s "u'ö'.encode('latin1')"
100000000 loops, best of 3: 0.0141 usec per loop
"""
def __init__(self, codepage_map):
"""Initialize encoder."""
self.codepages = codepage_map
self.available_encodings = set(codepage_map.keys())
self.available_characters = {}
self.used_encodings = set()
def get_sequence(self, encoding):
"""Get a sequence."""
return int(self.codepages[encoding])
def get_encoding_name(self, encoding):
"""Return a canonical encoding name.
Given an encoding provided by the user, will return a
"""Given an encoding provided by the user, will return a
canonical encoding name; and also validate that the encoding
is supported.
.. todo:: Support encoding aliases: pc437 instead of cp437.
TODO: Support encoding aliases: pc437 instead of cp437.
"""
encoding = CodePages.get_encoding_name(encoding)
if encoding not in self.codepages:
raise ValueError(
(
raise ValueError((
'Encoding "{}" cannot be used for the current profile. '
"Valid encodings are: {}"
).format(encoding, ",".join(self.codepages.keys()))
)
'Valid encodings are: {}'
).format(encoding, ','.join(self.codepages.keys())))
return encoding
@staticmethod
def _get_codepage_char_list(encoding):
"""Get codepage character list.
"""Get codepage character list
Gets characters 128-255 for a given code page, as an array.
:param encoding: The name of the encoding. This must appear in the CodePage list
"""
codepage = CodePages.get_encoding(encoding)
if "data" in codepage:
encodable_chars = list("".join(codepage["data"]))
assert len(encodable_chars) == 128
if 'data' in codepage:
encodable_chars = list("".join(codepage['data']))
assert(len(encodable_chars) == 128)
return encodable_chars
elif "python_encode" in codepage:
encodable_chars = [" "] * 128
elif 'python_encode' in codepage:
encodable_chars = [u" "] * 128
for i in range(0, 128):
codepoint = i + 128
try:
encodable_chars[i] = bytes([codepoint]).decode(
codepage["python_encode"]
)
encodable_chars[i] = bytes([codepoint]).decode(codepage['python_encode'])
except UnicodeDecodeError:
# Non-encodable character, just skip it
pass
@@ -91,7 +91,7 @@ class Encoder(object):
raise LookupError("Can't find a known encoding for {}".format(encoding))
def _get_codepage_char_map(self, encoding):
"""Get codepage character map.
""" Get codepage character map
Process an encoding and return a map of UTF-characters to code points
in this encoding.
@@ -104,14 +104,12 @@ class Encoder(object):
if encoding in self.available_characters:
return self.available_characters[encoding]
codepage_char_list = self._get_codepage_char_list(encoding)
codepage_char_map = dict(
(utf8, i + 128) for (i, utf8) in enumerate(codepage_char_list)
)
codepage_char_map = dict((utf8, i + 128) for (i, utf8) in enumerate(codepage_char_list))
self.available_characters[encoding] = codepage_char_map
return codepage_char_map
def can_encode(self, encoding, char):
"""Determine if a character is encodable in the given code page.
"""Determine if a character is encodeable in the given code page.
:param encoding: The name of the encoding.
:param char: The character to attempt to encode.
@@ -129,7 +127,7 @@ class Encoder(object):
@staticmethod
def _encode_char(char, charmap, defaultchar):
"""Encode a single character with the given encoding map.
""" Encode a single character with the given encoding map
:param char: char to encode
:param charmap: dictionary for mapping characters in this code page
@@ -140,33 +138,31 @@ class Encoder(object):
return charmap[char]
return ord(defaultchar)
def encode(self, text, encoding, defaultchar="?"):
"""Encode text under the given encoding.
def encode(self, text, encoding, defaultchar='?'):
""" Encode text under the given encoding
:param text: Text to encode
:param encoding: Encoding name to use (must be defined in capabilities)
:param defaultchar: Fallback for non-encodable characters
"""
codepage_char_map = self._get_codepage_char_map(encoding)
output_bytes = bytes(
[self._encode_char(char, codepage_char_map, defaultchar) for char in text]
)
output_bytes = bytes([self._encode_char(char, codepage_char_map, defaultchar) for char in text])
return output_bytes
def __encoding_sort_func(self, item):
key, index = item
used = key in self.used_encodings
return (not used, index)
return (
key in self.used_encodings,
index
)
def find_suitable_encoding(self, char):
"""Search in a specific order for a suitable encoding.
It is the following order:
"""The order of our search is a specific one:
1. code pages that we already tried before; there is a good
chance they might work again, reducing the search space,
and by re-using already used encodings we might also
reduce the number of codepage change instruction we have
reduce the number of codepage change instructiosn we have
to send. Still, any performance gains will presumably be
fairly minor.
@@ -176,7 +172,9 @@ class Encoder(object):
that the code page we pick for this character is actually
supported.
"""
sorted_encodings = sorted(self.codepages.items(), key=self.__encoding_sort_func)
sorted_encodings = sorted(
self.codepages.items(),
key=self.__encoding_sort_func)
for encoding, _ in sorted_encodings:
if self.can_encode(encoding, char):
@@ -186,9 +184,7 @@ class Encoder(object):
def split_writable_text(encoder, text, encoding):
"""Split up the writable text.
Splits off as many characters from the beginning of text as
"""Splits off as many characters from the begnning of text as
are writable with "encoding". Returns a 2-tuple (writable, rest).
"""
if not encoding:
@@ -203,9 +199,7 @@ def split_writable_text(encoder, text, encoding):
class MagicEncode(object):
"""Help switching to the right code page.
A helper that helps us to automatically switch to the right
"""A helper that helps us to automatically switch to the right
code page to encode any given Unicode character.
This will consider the printers supported codepages, according
@@ -215,11 +209,9 @@ class MagicEncode(object):
If the printer does not support a suitable code page, it can
insert an error character.
"""
def __init__(
self, driver, encoding=None, disabled=False, defaultsymbol="?", encoder=None
):
"""Initialize magic encode.
def __init__(self, driver, encoding=None, disabled=False,
defaultsymbol='?', encoder=None):
"""
:param driver:
:param encoding: If you know the current encoding of the printer
@@ -231,7 +223,7 @@ class MagicEncode(object):
:param encoder:
"""
if disabled and not encoding:
raise Error("If you disable magic encode, you need to define an encoding!")
raise Error('If you disable magic encode, you need to define an encoding!')
self.driver = driver
self.encoder = encoder or Encoder(driver.profile.get_code_pages())
@@ -241,7 +233,7 @@ class MagicEncode(object):
self.disabled = disabled
def force_encoding(self, encoding):
"""Set a fixed encoding. The change is emitted right away.
"""Sets a fixed encoding. The change is emitted right away.
From now one, this buffer will switch the code page anymore.
However, it will still keep track of the current code page.
@@ -253,15 +245,13 @@ class MagicEncode(object):
self.disabled = True
def write(self, text):
"""Write the text, automatically switching encodings."""
"""Write the text, automatically switching encodings.
"""
if self.disabled:
self.write_with_encoding(self.encoding, text)
return
if re.findall(r"[\u4e00-\u9fa5]", text):
self.driver._raw(text.encode("GB18030"))
return
# See how far we can go into the text with the current encoding
to_write, text = split_writable_text(self.encoder, text, self.encoding)
if to_write:
@@ -282,30 +272,25 @@ class MagicEncode(object):
self.write_with_encoding(encoding, to_write)
def _handle_character_failed(self, char):
"""Write a default symbol.
Called when no codepage was found to render a character.
"""Called when no codepage was found to render a character.
"""
# Writing the default symbol via write() allows us to avoid
# unnecesary codepage switches.
self.write(self.defaultsymbol)
def write_with_encoding(self, encoding, text):
"""Write the text and inject necessary codepage switches."""
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)
)
)
))
# We always know the current code page; if the new codepage
# is different, emit a change command.
if encoding != self.encoding:
self.encoding = encoding
self.driver._raw(
CODEPAGE_CHANGE + six.int2byte(self.encoder.get_sequence(encoding))
)
CODEPAGE_CHANGE +
six.int2byte(self.encoder.get_sequence(encoding)))
if text:
self.driver._raw(self.encoder.encode(text, encoding))

306
src/escpos/printer.py Normal file
View File

@@ -0,0 +1,306 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
""" This module contains the implementations of abstract base class :py:class:`Escpos`.
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import usb.core
import usb.util
import serial
import socket
from .escpos import Escpos
from .exceptions import USBNotFoundError
class Usb(Escpos):
""" USB printer
This class describes a printer that natively speaks USB.
inheritance:
.. inheritance-diagram:: escpos.printer.Usb
:parts: 1
"""
def __init__(self, idVendor, idProduct, timeout=0, in_ep=0x82, out_ep=0x01, *args, **kwargs): # noqa: N803
"""
:param idVendor: Vendor ID
:param idProduct: Product ID
:param timeout: Is the time limit of the USB operation. Default without timeout.
:param in_ep: Input end point
:param out_ep: Output end point
"""
Escpos.__init__(self, *args, **kwargs)
self.idVendor = idVendor
self.idProduct = idProduct
self.timeout = timeout
self.in_ep = in_ep
self.out_ep = out_ep
self.open()
def open(self):
""" Search device on USB tree and set it as escpos device """
self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct)
if self.device is None:
raise USBNotFoundError("Device not found or cable not plugged in.")
check_driver = None
try:
check_driver = self.device.is_kernel_driver_active(0)
except NotImplementedError:
pass
if check_driver is None or check_driver:
try:
self.device.detach_kernel_driver(0)
except usb.core.USBError as e:
if check_driver is not None:
print("Could not detatch kernel driver: {0}".format(str(e)))
try:
self.device.set_configuration()
self.device.reset()
except usb.core.USBError as e:
print("Could not set configuration: {0}".format(str(e)))
def _raw(self, msg):
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.write(self.out_ep, msg, self.timeout)
def close(self):
""" Release USB interface """
if self.device:
usb.util.dispose_resources(self.device)
self.device = None
class Serial(Escpos):
""" Serial printer
This class describes a printer that is connected by serial interface.
inheritance:
.. inheritance-diagram:: escpos.printer.Serial
:parts: 1
"""
def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1,
parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE,
xonxoff=False, dsrdtr=True, *args, **kwargs):
"""
:param devfile: Device file under dev filesystem
:param baudrate: Baud rate for serial transmission
:param bytesize: Serial buffer size
:param timeout: Read/Write timeout
:param parity: Parity checking
:param stopbits: Number of stop bits
:param xonxoff: Software flow control
:param dsrdtr: Hardware flow control (False to enable RTS/CTS)
"""
Escpos.__init__(self, *args, **kwargs)
self.devfile = devfile
self.baudrate = baudrate
self.bytesize = bytesize
self.timeout = timeout
self.parity = parity
self.stopbits = stopbits
self.xonxoff = xonxoff
self.dsrdtr = dsrdtr
self.open()
def open(self):
""" Setup serial port and set is as escpos device """
self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate,
bytesize=self.bytesize, parity=self.parity,
stopbits=self.stopbits, timeout=self.timeout,
xonxoff=self.xonxoff, dsrdtr=self.dsrdtr)
if self.device is not None:
print("Serial printer enabled")
else:
print("Unable to open serial printer on: {0}".format(str(self.devfile)))
def _raw(self, msg):
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.write(msg)
def close(self):
""" Close Serial interface """
if self.device is not None:
self.device.flush()
self.device.close()
class Network(Escpos):
""" Network printer
This class is used to attach to a networked printer. You can also use this in order to attach to a printer that
is forwarded with ``socat``.
If you have a local printer on parallel port ``/dev/usb/lp0`` then you could start ``socat`` with:
.. code-block:: none
socat -u TCP4-LISTEN:4242,reuseaddr,fork OPEN:/dev/usb/lp0
Then you should be able to attach to port ``4242`` with this class.
Otherwise the normal usecase would be to have a printer with ethernet interface. This type of printer should
work the same with this class. For the address of the printer check its manuals.
inheritance:
.. inheritance-diagram:: escpos.printer.Network
:parts: 1
"""
def __init__(self, host, port=9100, timeout=60, *args, **kwargs):
"""
:param host: Printer's hostname or IP address
:param port: Port to write to
:param timeout: timeout in seconds for the socket-library
"""
Escpos.__init__(self, *args, **kwargs)
self.host = host
self.port = port
self.timeout = timeout
self.open()
def open(self):
""" Open TCP socket with ``socket``-library and set it as escpos device """
self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.device.settimeout(self.timeout)
self.device.connect((self.host, self.port))
if self.device is None:
print("Could not open socket for {0}".format(self.host))
def _raw(self, msg):
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.sendall(msg)
def close(self):
""" Close TCP connection """
if self.device is not None:
self.device.shutdown(socket.SHUT_RDWR)
self.device.close()
class File(Escpos):
""" Generic file printer
This class is used for parallel port printer or other printers that are directly attached to the filesystem.
Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable
and produce arbitrary errors.
inheritance:
.. inheritance-diagram:: escpos.printer.File
:parts: 1
"""
def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs):
"""
:param devfile : Device file under dev filesystem
:param auto_flush: automatically call flush after every call of _raw()
"""
Escpos.__init__(self, *args, **kwargs)
self.devfile = devfile
self.auto_flush = auto_flush
self.open()
def open(self):
""" Open system file """
self.device = open(self.devfile, "wb")
if self.device is None:
print("Could not open the specified file {0}".format(self.devfile))
def flush(self):
""" Flush printing content """
self.device.flush()
def _raw(self, msg):
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.write(msg)
if self.auto_flush:
self.flush()
def close(self):
""" Close system file """
if self.device is not None:
self.device.flush()
self.device.close()
class Dummy(Escpos):
""" Dummy printer
This class is used for saving commands to a variable, for use in situations where
there is no need to send commands to an actual printer. This includes
generating print jobs for later use, or testing output.
inheritance:
.. inheritance-diagram:: escpos.printer.Dummy
:parts: 1
"""
def __init__(self, *args, **kwargs):
"""
"""
Escpos.__init__(self, *args, **kwargs)
self._output_list = []
def _raw(self, msg):
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self._output_list.append(msg)
@property
def output(self):
""" Get the data that was sent to this printer """
return b''.join(self._output_list)
def close(self):
pass

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
"""printer implementations."""
from .cups import CupsPrinter
from .dummy import Dummy
from .file import File
from .lp import LP
from .network import Network
from .serial import Serial
from .usb import Usb
from .win32raw import Win32Raw
__all__ = [
"Usb",
"File",
"Network",
"Serial",
"LP",
"Dummy",
"CupsPrinter",
"Win32Raw",
]

View File

@@ -1,222 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the CupsPrinter printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import functools
import logging
import tempfile
from typing import Literal, Optional, Type, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError
#: keeps track if the pycups dependency could be loaded (:py:class:`escpos.printer.CupsPrinter`)
_DEP_PYCUPS = False
try:
import cups
_DEP_PYCUPS = True
# Store server defaults before further configuration
DEFAULT_HOST = cups.getServer()
DEFAULT_PORT = cups.getPort()
except ImportError:
pass
# TODO: dev build mode that let's the wrapper bypass?
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
usable = False
if _DEP_PYCUPS:
usable = True
return usable
def dependency_pycups(func):
"""Indicate dependency on pycups."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Throw a RuntimeError if pycups is not imported."""
if not is_usable():
raise RuntimeError(
"Printing with PyCups requires the pycups library to"
"be installed. Please refer to the documentation on"
"what to install and install the dependencies for pycups."
)
return func(*args, **kwargs)
return wrapper
class CupsPrinter(Escpos):
"""Simple CUPS printer connector.
.. note::
Requires ``pycups`` which in turn needs the cups development library package:
- Ubuntu/Debian: ``libcups2-dev``
- OpenSuse/Fedora: ``cups-devel``
inheritance:
.. inheritance-diagram:: escpos.printer.CupsPrinter
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
@dependency_pycups
def __init__(self, printer_name: str = "", *args, **kwargs):
"""Class constructor for CupsPrinter.
:param printer_name: CUPS printer name (Optional)
:param host: CUPS server host/ip (Optional)
:type host: str
:param port: CUPS server port (Optional)
:type port: int
"""
Escpos.__init__(self, *args, **kwargs)
self.host, self.port = args or (
kwargs.get("host", DEFAULT_HOST),
kwargs.get("port", DEFAULT_PORT),
)
self.tmpfile = tempfile.NamedTemporaryFile(delete=True)
self.printer_name = printer_name
self.job_name = ""
self.pending_job = False
self._device: Union[
Literal[False], Literal[None], Type[cups.Connection]
] = False
@property
def printers(self) -> dict:
"""Available CUPS printers."""
if self.device:
return self.device.getPrinters()
return {}
def open(
self, job_name: str = "python-escpos", raise_not_found: bool = True
) -> None:
"""Set up a new print job and target the printer.
A call to this method is required to send new jobs to
the CUPS connection after close.
Defaults to default CUPS printer.
Creates a new temporary file buffer.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
"""
if self._device:
self.close()
cups.setServer(self.host)
cups.setPort(self.port)
self.job_name = job_name
if self.tmpfile.closed:
self.tmpfile = tempfile.NamedTemporaryFile(delete=True)
try:
# Open device
self.device: Optional[Type[cups.Connection]] = cups.Connection()
if self.device:
# Name validation, set default if no given name
self.printer_name = self.printer_name or self.device.getDefault()
assert self.printer_name in self.printers, "Incorrect printer name"
except (RuntimeError, AssertionError) as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Unable to start a print job for the printer {self.printer_name}:"
+ f"\n{e}"
)
else:
logging.error(
"CupsPrinter printing %s not available", self.printer_name
)
return
logging.info("CupsPrinter printer enabled")
def _raw(self, msg):
"""Append any command sent in raw format to temporary file.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.pending_job = True
try:
self.tmpfile.write(msg)
except TypeError:
self.pending_job = False
raise TypeError("Bytes required. Printer job not opened")
def send(self):
"""Send the print job to the printer."""
if self.pending_job:
# Rewind tempfile
self.tmpfile.seek(0)
# Print temporary file via CUPS printer.
self.device.printFile(
self.printer_name,
self.tmpfile.name,
self.job_name,
{"document-format": cups.CUPS_FORMAT_RAW},
)
self._clear()
def _clear(self):
"""Finish the print job.
Remove temporary file.
"""
self.tmpfile.close()
self.pending_job = False
def _read(self):
"""Return a single-item array with the accepting state of the print queue.
states: idle = [3], printing a job = [4], stopped = [5]
"""
printer = self.printers.get(self.printer_name, {})
state = printer.get("printer-state")
if not state:
return []
return [state]
def close(self):
"""Close CUPS connection.
Send pending job to the printer if needed.
"""
if not self._device:
return
if self.pending_job:
self.send()
logging.info("Closing CUPS connection to printer %s", self.printer_name)
self._device = False

View File

@@ -1,70 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the CupsPrinter printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
from ..escpos import Escpos
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
return True
class Dummy(Escpos):
"""Dummy printer.
This class is used for saving commands to a variable, for use in situations where
there is no need to send commands to an actual printer. This includes
generating print jobs for later use, or testing output.
inheritance:
.. inheritance-diagram:: escpos.printer.Dummy
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
def __init__(self, *args, **kwargs):
"""Init with empty output list."""
Escpos.__init__(self, *args, **kwargs)
self._output_list = []
def _raw(self, msg):
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self._output_list.append(msg)
@property
def output(self):
"""Get the data that was sent to this printer."""
return b"".join(self._output_list)
def 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):
"""Close not implemented for Dummy printer."""
pass

View File

@@ -1,109 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the File printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import logging
from typing import IO, Literal, Optional, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
return True
class File(Escpos):
"""Generic file printer.
This class is used for parallel port printer or other printers that are directly attached to the filesystem.
Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable
and produce arbitrary errors.
inheritance:
.. inheritance-diagram:: escpos.printer.File
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
def __init__(self, devfile: str = "", auto_flush: bool = True, *args, **kwargs):
"""Initialize file printer with device file.
:param devfile: Device file under dev filesystem
:param auto_flush: automatically call flush after every call of _raw()
"""
Escpos.__init__(self, *args, **kwargs)
self.devfile = devfile
self.auto_flush = auto_flush
self._device: Union[Literal[False], Literal[None], IO[bytes]] = False
def open(self, raise_not_found: bool = True) -> None:
"""Open system file.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
"""
if self._device:
self.close()
try:
# Open device
self.device: Optional[IO[bytes]] = open(self.devfile, "wb")
except OSError as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Could not open the specified file {self.devfile}:\n{e}"
)
else:
logging.error("File printer %s not found", self.devfile)
return
logging.info("File printer enabled")
def flush(self) -> None:
"""Flush printing content."""
if self.device:
self.device.flush()
def _raw(self, msg):
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.write(msg)
if self.auto_flush:
self.flush()
def close(self) -> None:
"""Close system file."""
if not self._device:
return
logging.info("Closing File connection to printer %s", self.devfile)
if not self.auto_flush:
self.flush()
self._device.close()
self._device = False

View File

@@ -1,197 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the LP printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import functools
import logging
import subprocess
import sys
from typing import Literal, Optional, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
usable = False
if not sys.platform.startswith("win"):
usable = True
return usable
def dependency_linux_lp(func):
"""Indicate dependency on non Windows."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Throw a RuntimeError if not on a non-Windows system."""
if not is_usable():
raise RuntimeError(
"This printer driver depends on LP which is not"
"available on Windows systems."
)
return func(*args, **kwargs)
return wrapper
class LP(Escpos):
"""Simple UNIX lp command raw printing.
Thanks to `Oyami-Srk comment <https://github.com/python-escpos/python-escpos/pull/348#issuecomment-549558316>`_.
inheritance:
.. inheritance-diagram:: escpos.printer.LP
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
@dependency_linux_lp
def __init__(self, printer_name: str = "", *args, **kwargs):
"""LP class constructor.
:param printer_name: CUPS printer name (Optional)
:param auto_flush: Automatic flush after every _raw() (Optional)
:type auto_flush: bool (Defaults False)
"""
Escpos.__init__(self, *args, **kwargs)
self.printer_name = printer_name
self.auto_flush = kwargs.get("auto_flush", False)
self._flushed = False
self._device: Union[Literal[False], Literal[None], subprocess.Popen] = False
@property
def printers(self) -> dict:
"""Available CUPS printers."""
p_names = subprocess.run(
["lpstat", "-e"], # Get printer names
capture_output=True,
text=True,
)
p_devs = subprocess.run(
["lpstat", "-v"], # Get attached devices
capture_output=True,
text=True,
)
# List and trim output lines
names = [name for name in p_names.stdout.split("\n") if name]
devs = [dev for dev in p_devs.stdout.split("\n") if dev]
# return a dict of {printer name: attached device} pairs
return {name: dev.split()[-1] for name in names for dev in devs if name in dev}
def _get_system_default_printer(self) -> str:
"""Return the system's default printer name."""
p_name = subprocess.run(
["lpstat", "-d"],
capture_output=True,
text=True,
)
name = p_name.stdout.split()[-1]
if name not in self.printers:
return ""
return name
def open(
self,
job_name: str = "python-escpos",
raise_not_found: bool = True,
_close_opened: bool = True,
) -> None:
"""Invoke _lp_ in a new subprocess and wait for commands.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
"""
if self._device and _close_opened:
self.close()
self._is_closing = False
self.job_name = job_name
try:
# Name validation, set default if no given name
self.printer_name = self.printer_name or self._get_system_default_printer()
assert self.printer_name in self.printers, "Incorrect printer name"
# Open device
self.device: Optional[subprocess.Popen] = subprocess.Popen(
["lp", "-d", self.printer_name, "-t", self.job_name, "-o", "raw"],
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except (AssertionError, subprocess.SubprocessError) as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Unable to start a print job for the printer {self.printer_name}:"
+ f"\n{e}"
)
else:
logging.error("LP printing %s not available", self.printer_name)
return
logging.info("LP printer enabled")
def close(self) -> None:
"""Stop the subprocess."""
if not self._device:
return
logging.info("Closing LP connection to printer %s", self.printer_name)
self._is_closing = True
if not self.auto_flush:
self.flush()
self._device.terminate()
self._device = False
def flush(self) -> None:
"""End line and wait for new commands."""
if not self.device or not self.device.stdin:
return
if self._flushed:
return
if self.device.stdin.writable():
self.device.stdin.write(b"\n")
if self.device.stdin.closed is False:
self.device.stdin.close()
self.device.wait()
self._flushed = True
if not self._is_closing:
self.open(_close_opened=False)
def _raw(self, msg):
"""Write raw command(s) to the printer.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
if self.device.stdin.writable():
self.device.stdin.write(msg)
else:
raise subprocess.SubprocessError("Not a valid pipe for lp process")
self._flushed = False
if self.auto_flush:
self.flush()

View File

@@ -1,135 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the Network printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import logging
import socket
from typing import Literal, Optional, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
return True
class Network(Escpos):
"""Network printer.
This class is used to attach to a networked printer.
You can also use this in order to attach to a printer that
is forwarded with ``socat``.
If you have a local printer on parallel port ``/dev/usb/lp0``
then you could start ``socat`` with:
.. code-block:: none
socat -u TCP4-LISTEN:4242,reuseaddr,fork OPEN:/dev/usb/lp0
Then you should be able to attach to port ``4242`` with this class.
Otherwise the normal use case would be to have a printer with
Ethernet interface.
This type of printer should work the same with this class.
For the address of the printer check its manuals.
inheritance:
.. inheritance-diagram:: escpos.printer.Network
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
def __init__(
self,
host: str = "",
port: int = 9100,
timeout: Union[int, float] = 60,
*args,
**kwargs,
):
"""Initialize network printer.
:param host: Printer's host name or IP address
:param port: Port to write to
:param timeout: timeout in seconds for the socket-library
"""
Escpos.__init__(self, *args, **kwargs)
self.host = host
self.port = port
self.timeout = timeout
self._device: Union[Literal[False], Literal[None], socket.socket] = False
def open(self, raise_not_found: bool = True) -> None:
"""Open TCP socket with ``socket``-library and set it as escpos device.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
"""
if self._device:
self.close()
try:
# Open device
self.device: Optional[socket.socket] = socket.socket(
socket.AF_INET, socket.SOCK_STREAM
)
self.device.settimeout(self.timeout)
self.device.connect((self.host, self.port))
except OSError as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Could not open socket for {self.host}:\n{e}"
)
else:
logging.error("Network device %s not found", self.host)
return
logging.info("Network printer enabled")
def _raw(self, msg):
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.sendall(msg)
def _read(self):
"""Read data from the TCP socket."""
return self.device.recv(16)
def close(self) -> None:
"""Close TCP connection."""
if not self._device:
return
logging.info("Closing Network connection to printer %s", self.host)
try:
self._device.shutdown(socket.SHUT_RDWR)
except socket.error:
pass
self._device.close()
self._device = False

View File

@@ -1,178 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the Serial printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import functools
import logging
from typing import Literal, Optional, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError
#: keeps track if the pyserial dependency could be loaded (:py:class:`escpos.printer.Serial`)
_DEP_PYSERIAL = False
try:
import serial
_DEP_PYSERIAL = True
except ImportError:
pass
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
usable = False
if _DEP_PYSERIAL:
usable = True
return usable
def dependency_pyserial(func):
"""Indicate dependency on pyserial."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Throw a RuntimeError if pyserial not installed."""
if not is_usable():
raise RuntimeError(
"Printing with Serial requires the pyserial library to"
"be installed. Please refer to the documentation on"
"what to install and install the dependencies for pyserial."
)
return func(*args, **kwargs)
return wrapper
class Serial(Escpos):
"""Serial printer.
This class describes a printer that is connected by serial interface.
inheritance:
.. inheritance-diagram:: escpos.printer.Serial
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
@dependency_pyserial
def __init__(
self,
devfile: str = "",
baudrate: int = 9600,
bytesize: int = 8,
timeout: Union[int, float] = 1,
parity: Optional[str] = None,
stopbits: Optional[int] = None,
xonxoff: bool = False,
dsrdtr: bool = True,
*args,
**kwargs,
):
"""Initialize serial printer.
:param devfile: Device file under dev filesystem
:param baudrate: Baud rate for serial transmission
:param bytesize: Serial buffer size
:param timeout: Read/Write timeout
:param parity: Parity checking
:param stopbits: Number of stop bits
:param xonxoff: Software flow control
:param dsrdtr: Hardware flow control (False to enable RTS/CTS)
"""
Escpos.__init__(self, *args, **kwargs)
self.devfile = devfile
self.baudrate = baudrate
self.bytesize = bytesize
self.timeout = timeout
if parity:
self.parity = parity
else:
self.parity = serial.PARITY_NONE
if stopbits:
self.stopbits = stopbits
else:
self.stopbits = serial.STOPBITS_ONE
self.xonxoff = xonxoff
self.dsrdtr = dsrdtr
self._device: Union[Literal[False], Literal[None], serial.Serial] = False
@dependency_pyserial
def open(self, raise_not_found: bool = True) -> None:
"""Set up serial port and set is as escpos device.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
"""
if self._device:
if self.device and self.device.is_open:
self.close()
try:
# Open device
self.device: Optional[serial.Serial] = serial.Serial(
port=self.devfile,
baudrate=self.baudrate,
bytesize=self.bytesize,
parity=self.parity,
stopbits=self.stopbits,
timeout=self.timeout,
xonxoff=self.xonxoff,
dsrdtr=self.dsrdtr,
)
except (ValueError, serial.SerialException) as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Unable to open serial printer on {self.devfile}:\n{e}"
)
else:
logging.error("Serial device %s not found", self.devfile)
return
logging.info("Serial printer enabled")
def _raw(self, msg):
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.write(msg)
def _read(self):
"""Read the data buffer and return it to the caller."""
return self.device.read(16)
def close(self) -> None:
"""Close Serial interface."""
if not self._device:
return
logging.info("Closing Serial connection to printer %s", self.devfile)
if self._device and self._device.is_open:
self._device.flush()
self._device.close()
self._device = False

View File

@@ -1,205 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the USB printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import functools
import logging
from typing import Dict, Literal, Optional, Type, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError, USBNotFoundError
#: keeps track if the usb dependency could be loaded (:py:class:`escpos.printer.Usb`)
_DEP_USB = False
try:
import usb.core
import usb.util
_DEP_USB = True
except ImportError:
pass
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
usable = False
if _DEP_USB:
usable = True
return usable
def dependency_usb(func):
"""Indicate dependency on usb."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Throw a RuntimeError if usb not installed."""
if not is_usable():
raise RuntimeError(
"Printing with USB connection requires a usb library to"
"be installed. Please refer to the documentation on"
"what to install and install the dependencies for USB."
)
return func(*args, **kwargs)
return wrapper
class Usb(Escpos):
"""USB printer.
This class describes a printer that natively speaks USB.
inheritance:
.. inheritance-diagram:: escpos.printer.Usb
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
def __init__(
self,
idVendor: str = "",
idProduct: str = "",
usb_args: Dict[str, str] = {},
timeout: Union[int, float] = 0,
in_ep: int = 0x82,
out_ep: int = 0x01,
*args,
**kwargs,
):
"""Initialize USB printer.
:param idVendor: Vendor ID
:param idProduct: Product ID
:param usb_args: Optional USB arguments (e.g. custom_match)
:param timeout: Is the time limit of the USB operation. Default without timeout.
:param in_ep: Input end point
:param out_ep: Output end point
"""
Escpos.__init__(self, *args, **kwargs)
self.timeout = timeout
self.in_ep = in_ep
self.out_ep = out_ep
self.usb_args = usb_args or {}
if idVendor:
self.usb_args["idVendor"] = idVendor
if idProduct:
self.usb_args["idProduct"] = idProduct
self._device: Union[
Literal[False], Literal[None], Type[usb.core.Device]
] = False
@dependency_usb
def open(self, raise_not_found: bool = True) -> None:
"""Search device on USB tree and set it as escpos device.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
:raises: :py:exc:`~escpos.exceptions.USBNotFoundError`
"""
if self._device:
self.close()
# Open device
try:
self.device: Optional[Type[usb.core.Device]] = usb.core.find(
**self.usb_args
)
assert self.device, USBNotFoundError(
f"Device {tuple(self.usb_args.values())} not found"
+ " or cable not plugged in."
)
self._check_driver()
self._configure_usb()
except (AssertionError, usb.core.USBError) as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Unable to open USB printer on {tuple(self.usb_args.values())}:"
+ f"\n{e}"
)
else:
logging.error("USB device %s not found", tuple(self.usb_args.values()))
return
logging.info("USB printer enabled")
def _check_driver(self) -> None:
"""Check the driver.
pyusb has three backends: libusb0, libusb1 and openusb but
only libusb1 backend implements the methods is_kernel_driver_active()
and detach_kernel_driver().
This helps enable this library to work on Windows.
"""
if self.device and self.device.backend.__module__.endswith("libusb1"):
check_driver: Optional[bool] = None
try:
check_driver = self.device.is_kernel_driver_active(0)
except NotImplementedError:
pass
if check_driver is None or check_driver:
try:
self.device.detach_kernel_driver(0)
except NotImplementedError:
pass
except usb.core.USBError as e:
if check_driver is not None:
logging.error("Could not detatch kernel driver: %s", str(e))
def _configure_usb(self) -> None:
"""Configure USB."""
if not self.device:
return
try:
self.device.set_configuration()
self.device.reset()
except usb.core.USBError as e:
logging.error("Could not set configuration: %s", str(e))
def _raw(self, msg):
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.write(self.out_ep, msg, self.timeout)
def _read(self):
"""Read a data buffer and return it to the caller."""
return self.device.read(self.in_ep, 16)
@dependency_usb
def close(self) -> None:
"""Release USB interface."""
if not self._device:
return
logging.info(
"Closing Usb connection to printer %s", tuple(self.usb_args.values())
)
usb.util.dispose_resources(self._device)
self._device = False

View File

@@ -1,161 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the Win32Raw printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import functools
import logging
from typing import Any, Literal, Optional, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError
#: keeps track if the win32print dependency could be loaded (:py:class:`escpos.printer.Win32Raw`)
_DEP_WIN32PRINT = False
try:
import pywintypes
import win32print
_DEP_WIN32PRINT = True
PyPrinterHANDLE: Any = win32print.OpenPrinter
except ImportError:
pass
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
usable = False
if _DEP_WIN32PRINT:
usable = True
return usable
def dependency_win32print(func):
"""Indicate dependency on win32print."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Throw a RuntimeError if win32print not installed."""
if not is_usable():
raise RuntimeError(
"Printing with Win32Raw requires a win32print library to"
"be installed. Please refer to the documentation on"
"what to install and install the dependencies for win32print."
)
return func(*args, **kwargs)
return wrapper
class Win32Raw(Escpos):
"""Printer binding for win32 API.
Uses the module pywin32 for printing.
inheritance:
.. inheritance-diagram:: escpos.printer.Win32Raw
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
@dependency_win32print
def __init__(self, printer_name: str = "", *args, **kwargs):
"""Initialize default printer."""
Escpos.__init__(self, *args, **kwargs)
self.printer_name = printer_name
self.job_name = ""
self._device: Union[
Literal[False],
Literal[None],
"PyPrinterHANDLE",
] = False
@property
def printers(self) -> dict:
"""Available Windows printers."""
return {
printer["pPrinterName"]: printer
for printer in win32print.EnumPrinters(win32print.PRINTER_ENUM_NAME, "", 4)
}
def open(
self, job_name: str = "python-escpos", raise_not_found: bool = True
) -> None:
"""Open connection to default printer.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
"""
if self._device:
self.close()
self.job_name = job_name
try:
# Name validation, set default if no given name
self.printer_name = self.printer_name or win32print.GetDefaultPrinter()
assert self.printer_name in self.printers, "Incorrect printer name"
# Open device
self.device: Optional["PyPrinterHANDLE"] = win32print.OpenPrinter(
self.printer_name
)
if self.device:
self.current_job = win32print.StartDocPrinter(
self.device, 1, (job_name, "", "RAW")
)
win32print.StartPagePrinter(self.device)
except (AssertionError, pywintypes.error) as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Unable to start a print job for the printer {self.printer_name}:"
+ f"\n{e}"
)
else:
logging.error("Win32Raw printing %s not available", self.printer_name)
return
logging.info("Win32Raw printer enabled")
def close(self) -> None:
"""Close connection to default printer."""
if self._device is False or self._device is None: # Literal False | None
return
logging.info("Closing Win32Raw connection to printer %s", self.printer_name)
win32print.EndPagePrinter(self._device)
win32print.EndDocPrinter(self._device)
win32print.ClosePrinter(self._device)
self._device = False
def _raw(self, msg) -> None:
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
if self.printer_name is None:
raise DeviceNotFoundError("Printer not found")
if not self.device:
raise DeviceNotFoundError("Printer job not opened")
win32print.WritePrinter(self.device, msg)

View File

@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
"""Custom types."""
from typing import Dict, TypedDict
class ConstTxtStyleClass(TypedDict):
"""Describe type of :py:data:`escpos.constants.TXT_STYLES`."""
bold: Dict[bool, bytes]
underline: Dict[int, bytes]
size: Dict[str, bytes]
font: Dict[str, bytes]
align: Dict[str, bytes]
invert: Dict[bool, bytes]
color: Dict[str, bytes]
flip: Dict[bool, bytes]
density: Dict[int, bytes]
smooth: Dict[bool, bytes]
height: Dict[int, int]
width: Dict[int, int]

Some files were not shown because too many files have changed in this diff Show More