Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
af29fcca77 | ||
![]() |
f8b269d859 | ||
![]() |
c259263f26 | ||
![]() |
27c843935f | ||
![]() |
f3da6a9725 | ||
![]() |
b64b534394 | ||
![]() |
81426ab6dc | ||
![]() |
df1193ab35 | ||
![]() |
b494c9a4bd | ||
![]() |
f8a2174108 | ||
![]() |
1f57b04974 | ||
![]() |
c7080165a7 | ||
![]() |
cf0cf127fe |
1
AUTHORS
@@ -15,6 +15,7 @@ ldos
|
|||||||
Manuel F Martinez
|
Manuel F Martinez
|
||||||
Michael Billington
|
Michael Billington
|
||||||
Michael Elsdörfer
|
Michael Elsdörfer
|
||||||
|
mrwunderbar666
|
||||||
Nathan Bookham
|
Nathan Bookham
|
||||||
Patrick Kanzler
|
Patrick Kanzler
|
||||||
Qian Linfeng
|
Qian Linfeng
|
||||||
|
@@ -2,6 +2,37 @@
|
|||||||
Changelog
|
Changelog
|
||||||
*********
|
*********
|
||||||
|
|
||||||
|
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"
|
2017-03-29 - Version 3.0a1 - "Headcrash"
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
This release is the second alpha release of the new version 3.0. Please
|
This release is the second alpha release of the new version 3.0. Please
|
||||||
|
@@ -6,10 +6,6 @@ python-escpos - Python library to manipulate ESC/POS Printers
|
|||||||
:target: https://travis-ci.org/python-escpos/python-escpos
|
:target: https://travis-ci.org/python-escpos/python-escpos
|
||||||
:alt: Continous Integration
|
:alt: Continous Integration
|
||||||
|
|
||||||
.. image:: https://www.quantifiedcode.com/api/v1/project/95748b89a3974700800b85e4ed3d32c4/badge.svg
|
|
||||||
:target: https://www.quantifiedcode.com/app/project/95748b89a3974700800b85e4ed3d32c4
|
|
||||||
:alt: Code issues
|
|
||||||
|
|
||||||
.. image:: https://landscape.io/github/python-escpos/python-escpos/master/landscape.svg?style=flat
|
.. image:: https://landscape.io/github/python-escpos/python-escpos/master/landscape.svg?style=flat
|
||||||
:target: https://landscape.io/github/python-escpos/python-escpos/master
|
:target: https://landscape.io/github/python-escpos/python-escpos/master
|
||||||
:alt: Code Health
|
:alt: Code Health
|
||||||
|
@@ -5,3 +5,4 @@ pyserial
|
|||||||
sphinx-rtd-theme
|
sphinx-rtd-theme
|
||||||
setuptools-scm
|
setuptools-scm
|
||||||
docutils>=0.12
|
docutils>=0.12
|
||||||
|
viivakoodi
|
||||||
|
11
examples/barcodes.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from escpos.printer import Usb
|
||||||
|
|
||||||
|
|
||||||
|
# Adapt to your needs
|
||||||
|
p = Usb(0x0416, 0x5011, profile="POS-5890")
|
||||||
|
|
||||||
|
# Print software and then hardware barcode with the same content
|
||||||
|
p.soft_barcode('code39', '123456')
|
||||||
|
p.text('\n')
|
||||||
|
p.text('\n')
|
||||||
|
p.barcode('123456', 'CODE39')
|
BIN
examples/graphics/climacons/clear-day.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
examples/graphics/climacons/clear-night.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
examples/graphics/climacons/cloudy.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
examples/graphics/climacons/fog.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
examples/graphics/climacons/partly-cloudy-day.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
examples/graphics/climacons/partly-cloudy-night.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
examples/graphics/climacons/rain.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
10
examples/graphics/climacons/readme.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Climacons by Adam Whitcroft
|
||||||
|
|
||||||
|
75 climatically categorised pictographs for web and UI design by [@adamwhitcroft](http://www.twitter.com/#!/adamwhitcroft).
|
||||||
|
|
||||||
|
Visit the [Climacons](http://adamwhitcroft.com/climacons/) website for more information.
|
||||||
|
|
||||||
|
Visit [Adam Whitcroft on GitHub](https://github.com/AdamWhitcroft)
|
||||||
|
|
||||||
|
## License
|
||||||
|
You are free to use any of the Climacons Icons (the "icons") in any personal or commercial work without obligation of payment (monetary or otherwise) or attribution, however a credit for the work would be appreciated. **Do not** redistribute or sell and **do not** claim creative credit. Intellectual property rights are not transferred with the download of the icons.
|
BIN
examples/graphics/climacons/sleet.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
examples/graphics/climacons/snow.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
examples/graphics/climacons/wind.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
127
examples/weather.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
|
||||||
|
# Adapted script from Adafruit
|
||||||
|
# Weather forecast for Raspberry Pi w/Adafruit Mini Thermal Printer.
|
||||||
|
# Retrieves data from DarkSky.net's API, prints current conditions and
|
||||||
|
# forecasts for next two days.
|
||||||
|
# Weather example using nice bitmaps.
|
||||||
|
# Written by Adafruit Industries. MIT license.
|
||||||
|
# Adapted and enhanced for escpos library by MrWunderbar666
|
||||||
|
|
||||||
|
# Icons taken from http://adamwhitcroft.com/climacons/
|
||||||
|
# Check out his github: https://github.com/AdamWhitcroft/climacons
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
from datetime import datetime
|
||||||
|
import calendar
|
||||||
|
import urllib
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
from escpos.printer import Usb
|
||||||
|
|
||||||
|
""" Setting up the main pathing """
|
||||||
|
this_dir, this_filename = os.path.split(__file__)
|
||||||
|
GRAPHICS_PATH = os.path.join(this_dir, "graphics/climacons/")
|
||||||
|
|
||||||
|
# Adapt to your needs
|
||||||
|
printer = Usb(0x0416, 0x5011, profile="POS-5890")
|
||||||
|
|
||||||
|
# You can get your API Key on www.darksky.net and register a dev account.
|
||||||
|
# Technically you can use any other weather service, of course :)
|
||||||
|
API_KEY = "YOUR API KEY"
|
||||||
|
|
||||||
|
LAT = "22.345490" # Your Location
|
||||||
|
LONG = "114.189945" # Your Location
|
||||||
|
|
||||||
|
|
||||||
|
def forecast_icon(idx):
|
||||||
|
icon = data['daily']['data'][idx]['icon']
|
||||||
|
image = GRAPHICS_PATH + icon + ".png"
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
# Dumps one forecast line to the printer
|
||||||
|
def forecast(idx):
|
||||||
|
date = datetime.fromtimestamp(int(data['daily']['data'][idx]['time']))
|
||||||
|
day = calendar.day_name[date.weekday()]
|
||||||
|
lo = data['daily']['data'][idx]['temperatureMin']
|
||||||
|
hi = data['daily']['data'][idx]['temperatureMax']
|
||||||
|
cond = data['daily']['data'][idx]['summary']
|
||||||
|
print(date)
|
||||||
|
print(day)
|
||||||
|
print(lo)
|
||||||
|
print(hi)
|
||||||
|
print(cond)
|
||||||
|
time.sleep(1)
|
||||||
|
printer.set(
|
||||||
|
font='a',
|
||||||
|
height=2,
|
||||||
|
align='left',
|
||||||
|
bold=False,
|
||||||
|
double_height=False)
|
||||||
|
printer.text(day + ' \n ')
|
||||||
|
time.sleep(5) # Sleep to prevent printer buffer overflow
|
||||||
|
printer.text('\n')
|
||||||
|
printer.image(forecast_icon(idx))
|
||||||
|
printer.text('low ' + str(lo))
|
||||||
|
printer.text(deg)
|
||||||
|
printer.text('\n')
|
||||||
|
printer.text(' high ' + str(hi))
|
||||||
|
printer.text(deg)
|
||||||
|
printer.text('\n')
|
||||||
|
# take care of pesky unicode dash
|
||||||
|
printer.text(cond.replace(u'\u2013', '-').encode('utf-8'))
|
||||||
|
printer.text('\n \n')
|
||||||
|
|
||||||
|
|
||||||
|
def icon():
|
||||||
|
icon = data['currently']['icon']
|
||||||
|
image = GRAPHICS_PATH + icon + ".png"
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
deg = ' C' # Degree symbol on thermal printer, need to find a better way to use a proper degree symbol
|
||||||
|
|
||||||
|
# if you want Fahrenheit change units= to 'us'
|
||||||
|
url = "https://api.darksky.net/forecast/" + API_KEY + "/" + LAT + "," + LONG + \
|
||||||
|
"?exclude=[alerts,minutely,hourly,flags]&units=si" # change last bit to 'us' for Fahrenheit
|
||||||
|
response = urllib.urlopen(url)
|
||||||
|
data = json.loads(response.read())
|
||||||
|
|
||||||
|
printer.print_and_feed(n=1)
|
||||||
|
printer.control("LF")
|
||||||
|
printer.set(font='a', height=2, align='center', bold=True, double_height=True)
|
||||||
|
printer.text("Weather Forecast")
|
||||||
|
printer.text("\n")
|
||||||
|
printer.set(align='center')
|
||||||
|
|
||||||
|
|
||||||
|
# Print current conditions
|
||||||
|
printer.set(font='a', height=2, align='center', bold=True, double_height=False)
|
||||||
|
printer.text('Current conditions: \n')
|
||||||
|
printer.image(icon())
|
||||||
|
printer.text("\n")
|
||||||
|
|
||||||
|
printer.set(font='a', height=2, align='left', bold=False, double_height=False)
|
||||||
|
temp = data['currently']['temperature']
|
||||||
|
cond = data['currently']['summary']
|
||||||
|
printer.text(temp)
|
||||||
|
printer.text(' ')
|
||||||
|
printer.text(deg)
|
||||||
|
printer.text(' ')
|
||||||
|
printer.text('\n')
|
||||||
|
printer.text('Sky: ' + cond)
|
||||||
|
printer.text('\n')
|
||||||
|
printer.text('\n')
|
||||||
|
|
||||||
|
# Print forecast
|
||||||
|
printer.set(font='a', height=2, align='center', bold=True, double_height=False)
|
||||||
|
printer.text('Forecast: \n')
|
||||||
|
forecast(0)
|
||||||
|
forecast(1)
|
||||||
|
printer.cut
|
||||||
|
printer.control("LF")
|
2
setup.py
@@ -126,7 +126,7 @@ setup(
|
|||||||
tests_require=[
|
tests_require=[
|
||||||
'jaconv',
|
'jaconv',
|
||||||
'tox',
|
'tox',
|
||||||
'pytest',
|
'pytest!=3.2.0',
|
||||||
'pytest-cov',
|
'pytest-cov',
|
||||||
'pytest-mock',
|
'pytest-mock',
|
||||||
'nose',
|
'nose',
|
||||||
|
@@ -251,5 +251,10 @@ S_RASTER_2H = _PRINT_RASTER_IMG(b'\x02') # Set raster image double height
|
|||||||
S_RASTER_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple
|
S_RASTER_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple
|
||||||
|
|
||||||
# Status Command
|
# Status Command
|
||||||
RT_STATUS_ONLINE = DLE + EOT + b'\x01';
|
RT_STATUS = DLE + EOT
|
||||||
RT_MASK_ONLINE = 8;
|
RT_STATUS_ONLINE = RT_STATUS + b'\x01'
|
||||||
|
RT_STATUS_PAPER = RT_STATUS + b'\x04'
|
||||||
|
RT_MASK_ONLINE = 8
|
||||||
|
RT_MASK_PAPER = 18
|
||||||
|
RT_MASK_LOWPAPER = 30
|
||||||
|
RT_MASK_NOPAPER = 114
|
@@ -36,6 +36,7 @@ from .constants import HW_RESET, HW_SELECT, HW_INIT
|
|||||||
from .constants import CTL_VT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON
|
from .constants import CTL_VT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON
|
||||||
from .constants import TXT_STYLE
|
from .constants import TXT_STYLE
|
||||||
from .constants import RT_STATUS_ONLINE, RT_MASK_ONLINE
|
from .constants import RT_STATUS_ONLINE, RT_MASK_ONLINE
|
||||||
|
from .constants import RT_STATUS_PAPER, RT_MASK_PAPER, RT_MASK_LOWPAPER, RT_MASK_NOPAPER
|
||||||
|
|
||||||
from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError
|
from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError
|
||||||
from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError
|
from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError
|
||||||
@@ -250,9 +251,9 @@ class Escpos(object):
|
|||||||
"""
|
"""
|
||||||
max_input = (256 << (out_bytes * 8) - 1)
|
max_input = (256 << (out_bytes * 8) - 1)
|
||||||
if not 1 <= out_bytes <= 4:
|
if not 1 <= out_bytes <= 4:
|
||||||
raise ValueError("Can only output 1-4 byes")
|
raise ValueError("Can only output 1-4 bytes")
|
||||||
if not 0 <= inp_number <= max_input:
|
if not 0 <= inp_number <= max_input:
|
||||||
raise ValueError("Number too large. Can only output up to {0} in {1} byes".format(max_input, out_bytes))
|
raise ValueError("Number too large. Can only output up to {0} in {1} bytes".format(max_input, out_bytes))
|
||||||
outp = b''
|
outp = b''
|
||||||
for _ in range(0, out_bytes):
|
for _ in range(0, out_bytes):
|
||||||
outp += six.int2byte(inp_number % 256)
|
outp += six.int2byte(inp_number % 256)
|
||||||
@@ -459,6 +460,28 @@ class Escpos(object):
|
|||||||
txt = six.text_type(txt)
|
txt = six.text_type(txt)
|
||||||
self.magic.write(txt)
|
self.magic.write(txt)
|
||||||
|
|
||||||
|
def textln(self, txt=''):
|
||||||
|
"""Print alpha-numeric text with a newline
|
||||||
|
|
||||||
|
The text has to be encoded in the currently selected codepage.
|
||||||
|
The input text has to be encoded in unicode.
|
||||||
|
|
||||||
|
:param txt: text to be printed with a newline
|
||||||
|
:raises: :py:exc:`~escpos.exceptions.TextError`
|
||||||
|
"""
|
||||||
|
self.text('{}\n'.format(txt))
|
||||||
|
|
||||||
|
def ln(self, count=1):
|
||||||
|
"""Print a newline or more
|
||||||
|
|
||||||
|
:param count: number of newlines to print
|
||||||
|
:raises: :py:exc:`ValueError` if count < 0
|
||||||
|
"""
|
||||||
|
if count < 0:
|
||||||
|
raise ValueError('Count cannot be lesser than 0')
|
||||||
|
if count > 0:
|
||||||
|
self.text('\n' * count)
|
||||||
|
|
||||||
def block_text(self, txt, font=None, columns=None):
|
def block_text(self, txt, font=None, columns=None):
|
||||||
""" Text is printed wrapped to specified columns
|
""" Text is printed wrapped to specified columns
|
||||||
|
|
||||||
@@ -754,19 +777,40 @@ class Escpos(object):
|
|||||||
else:
|
else:
|
||||||
self._raw(PANEL_BUTTON_OFF)
|
self._raw(PANEL_BUTTON_OFF)
|
||||||
|
|
||||||
def query_status(self):
|
def query_status(self, mode):
|
||||||
""" Queries the printer for its status, and returns an array of integers containing it.
|
""" Queries the printer for its status, and returns an array of integers containing it.
|
||||||
|
:param mode: Integer that sets the status mode queried to the printer.
|
||||||
|
RT_STATUS_ONLINE: Printer status.
|
||||||
|
RT_STATUS_PAPER: Paper sensor.
|
||||||
:rtype: array(integer)"""
|
:rtype: array(integer)"""
|
||||||
self._raw(RT_STATUS_ONLINE)
|
self._raw(mode)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
status = self._read()
|
status = self._read()
|
||||||
return status or [RT_MASK_ONLINE]
|
return status
|
||||||
|
|
||||||
def is_online(self):
|
def is_online(self):
|
||||||
""" Queries the printer its online status.
|
""" Queries the printer its online status.
|
||||||
When online, returns True; False otherwise.
|
When online, returns True; False otherwise.
|
||||||
:rtype: bool: True if online, False if offline."""
|
:rtype: bool: True if online, False if offline."""
|
||||||
return not (self.query_status()[0] & RT_MASK_ONLINE)
|
status = self.query_status(RT_STATUS_ONLINE)
|
||||||
|
if len(status) == 0:
|
||||||
|
return False
|
||||||
|
return not (status & RT_MASK_ONLINE)
|
||||||
|
|
||||||
|
def paper_status(self):
|
||||||
|
""" Queries the printer its paper status.
|
||||||
|
Returns 2 if there is plenty of paper, 1 if the paper has arrived to
|
||||||
|
the near-end sensor and 0 if there is no paper.
|
||||||
|
:rtype: int: 2: Paper is adequate. 1: Paper ending. 0: No paper."""
|
||||||
|
status = self.query_status(RT_STATUS_PAPER)
|
||||||
|
if len(status) == 0:
|
||||||
|
return 2
|
||||||
|
if (status[0] & RT_MASK_NOPAPER == RT_MASK_NOPAPER):
|
||||||
|
return 0
|
||||||
|
if (status[0] & RT_MASK_LOWPAPER == RT_MASK_LOWPAPER):
|
||||||
|
return 1
|
||||||
|
if (status[0] & RT_MASK_PAPER == RT_MASK_PAPER):
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
class EscposIO(object):
|
class EscposIO(object):
|
||||||
|
@@ -155,6 +155,10 @@ class Serial(Escpos):
|
|||||||
"""
|
"""
|
||||||
self.device.write(msg)
|
self.device.write(msg)
|
||||||
|
|
||||||
|
def _read(self):
|
||||||
|
""" Reads a data buffer and returns it to the caller. """
|
||||||
|
return self.device.read(16)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
""" Close Serial interface """
|
""" Close Serial interface """
|
||||||
if self.device is not None and self.device.is_open:
|
if self.device is not None and self.device.is_open:
|
||||||
|
@@ -39,3 +39,27 @@ def test_block_text():
|
|||||||
"All the presidents men were eating falafel for breakfast.", font='a')
|
"All the presidents men were eating falafel for breakfast.", font='a')
|
||||||
assert printer.output == \
|
assert printer.output == \
|
||||||
b'All the presidents men were eating falafel\nfor breakfast.'
|
b'All the presidents men were eating falafel\nfor breakfast.'
|
||||||
|
|
||||||
|
|
||||||
|
def test_textln():
|
||||||
|
printer = get_printer()
|
||||||
|
printer.textln('hello, world')
|
||||||
|
assert printer.output == b'hello, world\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_textln_empty():
|
||||||
|
printer = get_printer()
|
||||||
|
printer.textln()
|
||||||
|
assert printer.output == b'\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_ln():
|
||||||
|
printer = get_printer()
|
||||||
|
printer.ln()
|
||||||
|
assert printer.output == b'\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_ln():
|
||||||
|
printer = get_printer()
|
||||||
|
printer.ln(3)
|
||||||
|
assert printer.output == b'\n\n\n'
|
||||||
|
4
tox.ini
@@ -7,10 +7,11 @@ deps = nose
|
|||||||
coverage
|
coverage
|
||||||
scripttest
|
scripttest
|
||||||
mock
|
mock
|
||||||
pytest
|
pytest!=3.2.0
|
||||||
pytest-cov
|
pytest-cov
|
||||||
pytest-mock
|
pytest-mock
|
||||||
hypothesis
|
hypothesis
|
||||||
|
viivakoodi
|
||||||
commands = py.test --cov escpos
|
commands = py.test --cov escpos
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
@@ -18,6 +19,7 @@ basepython = python
|
|||||||
changedir = doc
|
changedir = doc
|
||||||
deps = sphinx>=1.5.1
|
deps = sphinx>=1.5.1
|
||||||
setuptools_scm
|
setuptools_scm
|
||||||
|
viivakoodi
|
||||||
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
|
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
|
||||||
|
|
||||||
[testenv:flake8]
|
[testenv:flake8]
|
||||||
|