From 82386f74964644bc8d0502d125f09f0240b1e300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20L=C3=B3pez?= Date: Sat, 24 Aug 2024 23:29:00 +0200 Subject: [PATCH 1/3] New feature: Software columns - Part 3: Tests (#651) * Add test_function_software_columns.py * Improve coverage --- .../test_function_software_columns.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 test/test_functions/test_function_software_columns.py diff --git a/test/test_functions/test_function_software_columns.py b/test/test_functions/test_function_software_columns.py new file mode 100644 index 0000000..9ec1771 --- /dev/null +++ b/test/test_functions/test_function_software_columns.py @@ -0,0 +1,80 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""tests for software_columns + +:author: Benito López and the python-escpos developers +:organization: `python-escpos `_ +:copyright: Copyright (c) 2024 `python-escpos `_ +:license: MIT +""" + + +import pytest + + +def test_rearrange_into_cols(driver) -> None: + """ + GIVEN a list of columnable text + WHEN the column width is different for each column and some strings exceed the max width + THEN check the strings are properly wrapped, truncated and rearranged into some columns + """ + + output = driver._rearrange_into_cols( + text_list=["fits", "row1 row2", "truncate and wrap"], widths=[4, 5, 6] + ) + assert output == [["fits", "row1", "trunc."], ["", "row2", "and"], ["", "", "wrap"]] + + +def test_add_padding_into_cols(driver) -> None: + """ + GIVEN a list of strings + WHEN adding padding and different alignments to each string + THEN check the strings are correctly padded and aligned + """ + + output = driver._add_padding_into_cols( + text_list=["col1", "col2", "col3"], + widths=[6, 6, 6], + align=["center", "left", "right"], + ) + assert output == [" col1 ", "col2 ", " col3"] + + +@pytest.mark.parametrize("text_list", ["", [], None]) +@pytest.mark.parametrize("widths", [30.5, "30", None]) +@pytest.mark.parametrize("align", ["invalid_align_name", "", None]) +def test_software_columns_invalid_args(driver, text_list, widths, align) -> None: + """ + GIVEN a dummy printer object + WHEN non valid params are passed + THEN check raise exception + """ + bad_text_list = {"text_list": text_list, "widths": 5, "align": "left"} + bad_widths = {"text_list": ["valid"], "widths": widths, "align": "left"} + bad_align = {"text_list": ["valid"], "widths": 5, "align": align} + + bad_args = [bad_text_list, bad_widths, bad_align] + for kwargs in bad_args: + with pytest.raises(Exception): + driver.software_columns(**kwargs) + driver.close() + + +@pytest.mark.parametrize( + "text_list", + [ + ["col1", "col2", "col3"], + ["wrap this string", "wrap this string", "wrap this string"], + ["truncate_this_string", "truncate_this_string", "truncate_this_string"], + ], +) +@pytest.mark.parametrize("widths", [[10, 10, 10], [10], 30]) +@pytest.mark.parametrize("align", [["center", "left", "right"], ["center"], "center"]) +def test_software_columns_valid_args(driver, text_list, widths, align) -> None: + """ + GIVEN a dummy printer object + WHEN valid params are passed + THEN check no errors + """ + driver.software_columns(text_list=text_list, widths=widths, align=align) + driver.close() From 22982fbd123329f72476ba6c446d6c807785d1a7 Mon Sep 17 00:00:00 2001 From: Patrick Kanzler <4189642+patkan@users.noreply.github.com> Date: Sun, 25 Aug 2024 00:21:47 +0200 Subject: [PATCH 2/3] update organize import trigger, update capabilities data, fix mypy (#654) * update organize import trigger * update capabilities data * fix mypy error (jaconv is using a import hack) --- .vscode/settings.json | 2 +- capabilities-data | 2 +- src/escpos/katakana.py | 3 +++ test/test_magicencode.py | 4 +++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a850fc9..01b93af 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,7 +12,7 @@ "editor.formatOnPaste": true, "python.formatting.provider": "black", "editor.codeActionsOnSave": { - "source.organizeImports": true + "source.organizeImports": "explicit" }, "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, diff --git a/capabilities-data b/capabilities-data index 375135d..e3bf605 160000 --- a/capabilities-data +++ b/capabilities-data @@ -1 +1 @@ -Subproject commit 375135d552e3fe65cbd1462b8b8d56c401b13ae7 +Subproject commit e3bf6056ee75cf70ffaccb925081fffa7ad6ced5 diff --git a/src/escpos/katakana.py b/src/escpos/katakana.py index b20ac67..20c7d3f 100644 --- a/src/escpos/katakana.py +++ b/src/escpos/katakana.py @@ -4,7 +4,10 @@ I doubt that this currently works correctly. """ +import types +import typing +jaconv: typing.Optional[types.ModuleType] try: import jaconv except ImportError: diff --git a/test/test_magicencode.py b/test/test_magicencode.py index 77d1575..6c7b849 100644 --- a/test/test_magicencode.py +++ b/test/test_magicencode.py @@ -7,7 +7,8 @@ :copyright: Copyright (c) 2016 `python-escpos `_ :license: MIT """ - +import types +import typing import hypothesis.strategies as st import pytest @@ -111,6 +112,7 @@ class TestMagicEncode: assert driver.output == b"\x1bt\x00? ist teuro." +jaconv: typing.Optional[types.ModuleType] try: import jaconv except ImportError: From f42410603d3d2522a89555871ee1252172a94db4 Mon Sep 17 00:00:00 2001 From: aerialist Date: Sun, 25 Aug 2024 07:50:27 +0900 Subject: [PATCH 3/3] Add sleep in sending fragments (#624) * Add sleep in sending fragments Adding sleep prevents "USBTimeoutError: [Errno 110] Operation timed out". * change sorting * make sleep configurable * add spelling --------- Co-authored-by: Patrick Kanzler <4189642+patkan@users.noreply.github.com> Co-authored-by: Patrick Kanzler --- doc/spelling_wordlist.txt | 2 ++ doc/user/usage.rst | 8 ++++++++ src/escpos/escpos.py | 20 ++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/doc/spelling_wordlist.txt b/doc/spelling_wordlist.txt index 0fc9cf4..38db9e1 100644 --- a/doc/spelling_wordlist.txt +++ b/doc/spelling_wordlist.txt @@ -91,6 +91,7 @@ docstrings ean Ean encodable +Errno fff fullimage io @@ -124,6 +125,7 @@ Todo traceback udev usb +USBTimeoutError usec virtualenvs whitespaces diff --git a/doc/user/usage.rst b/doc/user/usage.rst index 30e1d44..3bcc4eb 100644 --- a/doc/user/usage.rst +++ b/doc/user/usage.rst @@ -213,6 +213,14 @@ 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``. +Resolving bus timeout issues during printing images +--------------------------------------------------- + +If an error message such as "USBTimeoutError: [Errno 110] Operation timed out" occurs, +setting a sleep time between printing fragments can help. + +This can be done with the :meth:`.set_sleep_in_fragment()` method. + Advanced Usage: Print from binary blob -------------------------------------- diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index daad90d..48e5296 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -12,6 +12,7 @@ This module contains the abstract base class :py:class:`Escpos`. from __future__ import annotations import textwrap +import time import warnings from abc import ABCMeta, abstractmethod # abstract base class support from re import match as re_match @@ -123,6 +124,9 @@ class Escpos(object, metaclass=ABCMeta): # object -> The connection object (Usb(), Serial(), Network(), etc.) _device: Union[Literal[False], Literal[None], object] = False + # sleep time in fragments: + _sleep_in_fragment_ms: int = 0 + def __init__(self, profile=None, magic_encode_args=None, **kwargs) -> None: """Initialize ESCPOS Printer. @@ -178,6 +182,21 @@ class Escpos(object, metaclass=ABCMeta): """ raise NotImplementedError() + def set_sleep_in_fragment(self, sleep_time_ms: int) -> None: + """Configures the currently active sleep time after sending a fragment. + + If during printing an image an issue like "USBTimeoutError: [Errno 110] + Operation timed out" occurs, setting this value to roughly 300 + milliseconds can help resolve the issue. + + :param sleep_time_ms: sleep time in milliseconds + """ + self._sleep_in_fragment_ms = sleep_time_ms + + def _sleep_in_fragment(self) -> None: + """Sleeps the preconfigured time after sending a fragment.""" + time.sleep(self._sleep_in_fragment_ms / 1000) + def image( self, img_source, @@ -246,6 +265,7 @@ class Escpos(object, metaclass=ABCMeta): impl=impl, fragment_height=fragment_height, ) + self._sleep_in_fragment() return if impl == "bitImageRaster":