From 312630a41d2034bd7fe834bac586b6a825c7e561 Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Mon, 25 Jan 2021 18:46:11 +0100 Subject: [PATCH 1/9] Detach the kernel driver if it is attached to the printer --- brother_label_printer/backends/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/brother_label_printer/backends/__init__.py b/brother_label_printer/backends/__init__.py index 1f15853..384c00b 100644 --- a/brother_label_printer/backends/__init__.py +++ b/brother_label_printer/backends/__init__.py @@ -14,6 +14,8 @@ def is_usb_printer(dev): class PyUSBBackend(): def __init__(self, dev): self.dev = dev + if self.dev.is_kernel_driver_active(0): + self.dev.detach_kernel_driver(0) self.lock = threading.Lock() @classmethod @@ -29,6 +31,7 @@ def write(self, data: bytes): def read(self, count: int) -> bytes: return self.dev.read(0x81, count) + class BTSerialBackend(): def __init__(self, dev): self.dev = dev From d90d5bd1b0769e0b804c2c613fa19cf861bbcf5e Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Mon, 25 Jan 2021 23:16:07 +0100 Subject: [PATCH 2/9] workaround for not unix based systems --- brother_label_printer/backends/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/brother_label_printer/backends/__init__.py b/brother_label_printer/backends/__init__.py index 384c00b..76dca69 100644 --- a/brother_label_printer/backends/__init__.py +++ b/brother_label_printer/backends/__init__.py @@ -14,8 +14,11 @@ def is_usb_printer(dev): class PyUSBBackend(): def __init__(self, dev): self.dev = dev - if self.dev.is_kernel_driver_active(0): - self.dev.detach_kernel_driver(0) + try: + if self.dev.is_kernel_driver_active(0): + self.dev.detach_kernel_driver(0) + except NotImplemented: + pass self.lock = threading.Lock() @classmethod From 72ec864e284f0fb78e8ee6677ab55d1fd521f6cc Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Thu, 28 Jan 2021 22:18:02 +0100 Subject: [PATCH 3/9] Put the printer commands in additional functions, which allow a more universal usage. Included all parameters defined in the raster command reference for the P700. Added the possibility to print multiple copies. Implemented waiting for the printing process. --- .DS_Store | Bin 6148 -> 6148 bytes brother_label_printer/printers/__init__.py | 2 +- .../printers/brother_pt700.py | 156 ++++++++++++++---- 3 files changed, 122 insertions(+), 36 deletions(-) diff --git a/.DS_Store b/.DS_Store index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..fe69c7478d7f32cb165bcf653f07cc4d6d2ecc9d 100644 GIT binary patch delta 428 zcmZoMXfc=|#>B)qF;Q%yo}wr-0|Nsi1A_nqLk2@CLlHwfLk>gY#>C}}^&lBhh9sa^ zK0^tzEKn>Js4AYJ04SHqkO!0n>P#vxE=bDBPXg*XkyMbASzKaZaGjBfnT3^&or9B; zgOistHaH`{Jh&vWq_o&6u_zkE3rH-iHlh#8;G!M=H}q$0LI0}#P7_L`9&02nSiAjHu~2NHo+1YW5HK<@2y8B7p2o7dfmw=qGdl-A2T%b} Tuple[float, float]: xpixels, ypixels = label.size return (xpixels / xdpi) * 2.54, (ypixels / ydpi) * 2.54 - def print_label(self, label: Label) -> BaseStatus: + def print_label(self, label: Label, copies=1) -> BaseStatus: """Print the label""" @abstractmethod diff --git a/brother_label_printer/printers/brother_pt700.py b/brother_label_printer/printers/brother_pt700.py index 3a80b7f..00b028e 100644 --- a/brother_label_printer/printers/brother_pt700.py +++ b/brother_label_printer/printers/brother_pt700.py @@ -2,13 +2,12 @@ Brother P-Touch P700 Driver """ import io -import random import struct +import threading import time from collections import namedtuple from enum import Enum, IntEnum -from itertools import chain, islice -from pprint import pprint +from itertools import islice from typing import Iterable, Sequence from math import ceil import logging @@ -28,6 +27,14 @@ def batch_iter_bytes(b, size): return iter(lambda: bytes(tuple(islice(i, size))), b"") +def create_copies(b, size, copies): + result = list() + for i in range(copies): + result.append(batch_iter_bytes(b, size)) + + return result + + class INFO_OFFSETS(IntEnum): PRINTHEAD_MARK = 0 MODEL_CODE = 4 @@ -171,6 +178,11 @@ class P700(BasePrinter): """Printer Class for the Brother P-Touch P700/PT-700 Printer""" DPI = (180, 180) + def __init__(self, io_obj: io.BufferedIOBase): + super().__init__(io_obj) + self.status = self.get_status() + self._check_print_status = False + def connect(self) -> None: """Connect to Printer""" self.io.write(b'\x00' * 100) @@ -201,13 +213,13 @@ def _debug_status(self): def get_label_width(self): return self.get_status().tape_info.width - def print_label(self, label: Label) -> Status: - status = self.get_status() - if not status.ready(): + def print_label(self, label: Label, copies=1) -> Status: + self.status = self.get_status() + if not self.status.ready(): raise IOError("Printer is not ready") - img = label.render(height=status.tape_info.printarea) - logger.debug("printarea is %s dots", status.tape_info.printarea) + img = label.render(height=self.status.tape_info.printarea) + logger.debug("printarea is %s dots", self.status.tape_info.printarea) if not img.mode == "1": raise ValueError("render output has invalid mode '1'") img = img.transpose(Image.ROTATE_270).transpose( @@ -215,48 +227,122 @@ def print_label(self, label: Label) -> Status: img = ImageChops.invert(img) logger.info("label output size: %s", img.size) - logger.info("tape info: %s", status.tape_info) + logger.info("tape info: %s", self.status.tape_info) img_bytes = img.tobytes() with self.io.lock: self._raw_print( - status, batch_iter_bytes(img_bytes, ceil(img.size[0] / 8))) - - # wait for label to finish printing - time.sleep(len(img_bytes)/1000 if len(img_bytes)/1000 > 5 else 5) + self.status, create_copies(img_bytes, ceil(img.size[0] / 8), 2)) return self.get_status() def _dummy_print(self, status: Status, document: Iterable[bytes]) -> None: for line in document: - encode_line(line, status.tape_info) + print(b'G' + encode_line(line, status.tape_info)) + print('------') + for line in document: + print(b'G' + encode_line(line, status.tape_info)) + + def _print_status_check(self): + while self._check_print_status: + data = self.io.read(32) + if len(data) == 32: + self.status = Status(data) + time.sleep(0.1) - def _raw_print(self, status: Status, document: Iterable[bytes]) -> None: + def _raw_print(self, status: Status, documents: list[Iterable[bytes]]) -> None: logger.info("starting print") self.connect() + self._check_print_status = True + + status_thread = threading.Thread(target=self._print_status_check) + status_thread.start() + + try: + self.set_raster_mode() + self.set_various_mode() + self.set_advanced_mode() + self.set_margin(14) + self.set_compression_mode() + + for i in range(len(documents)): + for line in documents[i]: + self.io.write(b'G' + encode_line(line, status.tape_info)) + + self.print_empty_row() + + if i+1 < len(documents): + self.next_page() + print("next page") + end = time.time() + 20 + while True: + if self.status.data.get('status_type') == 6 and self.status.data.get('phase_type') == 0: + break + time.sleep(0.1) + if time.time() > end: + raise TimeoutError("The printer did not reach receiving state for the next page") + + # end page + self.last_page_end() + logger.info("end of page") + + end = time.time() + 20 + while True: + if self.status.data.get('status_type') == 1: + break + time.sleep(0.1) + if time.time() > end: + raise TimeoutError("The printer did not reach printing complete state") + + finally: + self._check_print_status = False + status_thread.join() + + def last_page_end(self): + self.io.write(b'\x1A') - # raster mode - self.io.write(b'\x1B\x69\x69\x01') - - # Various mode - self.io.write(b'\x1B\x69\x4D\x40') - - # Advanced mode - self.io.write(b'\x1B\x69\x4B\x08') - - # margin - self.io.write(b'\x1B\x69\x64\x0E\x00') - - # Compression mode - self.io.write(b'\x4D\x02') - - for line in document: - self.io.write(b'G' + encode_line(line, status.tape_info)) + def next_page(self): + self.io.write(b'\x0C') + def print_empty_row(self): self.io.write(b'\x5A') - # end page - self.io.write(b'\x1A') - logger.info("end of page") \ No newline at end of file + def set_compression_mode(self, tiff=True): + data = b'\x4D' + self.build_byte({1: tiff}) + self.io.write(data) + + def set_margin(self, margin: int): + data = b'\x1B\x69\x64' + margin.to_bytes(2, 'little') + self.io.write(data) + + def set_advanced_mode(self, no_chain_printing=True, special_tape=False, no_buffer_clearing=False): + """ + No chain printing + When printing multiple copies, the labels are fed after the last one is printed. + 1:No chain printing(Feeding and cutting are performed after the last one is printed.) + 0:Chain printing(Feeding and cutting are not performed after the last one is printed.) + Special tape (no cutting) + Labels are not cut when special tape is installed. + 1.Special tape (no cutting) ON 0:Special tape (no cutting) OFF + No buffer clearing when printing + The expansion buffer of the machine is not cleared with the “no buffer clearing when printing” + """ + data = b'\x1B\x69\x4B' + self.build_byte({3: no_chain_printing, 4: special_tape, 7: no_buffer_clearing}) + self.io.write(data) + + def set_various_mode(self, cut=True, mirror=False): + """ + Autocut 1.Automatically cuts 0.Does not automatically cut + Mirror printing 1. Mirror printing 0. No mirror printing + """ + data = b'\x1B\x69\x4D' + self.build_byte({6: cut, 7: mirror}) + self.io.write(data) + + def set_raster_mode(self): + self.io.write(b'\x1B\x69\x61\x01') + + @staticmethod + def build_byte(bits: dict): + return bytes([int(''.join([str(int(bits.get(i, 0))) for i in reversed(range(8))]), 2)]) From e2c77e918df259f1953f19643f03923079d5e606 Mon Sep 17 00:00:00 2001 From: micha91 Date: Thu, 28 Jan 2021 22:46:46 +0100 Subject: [PATCH 4/9] Delete .DS_Store --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index fe69c7478d7f32cb165bcf653f07cc4d6d2ecc9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!EVz)5S>i}*s2gYP?h6eddneIQFB7FLOJw?WCRC_Qg0nKmW(%w9U=&+{2Aqs z_yc~2H@n-4DsbopA~Yk-zRB#&uKmW^%@C0qE%HO69uY-Q#!igkH^TF*8`24$9iVb+ zOld-M8dFKxMzlD#kpZsV9vM{4QbWJi&!CniYB|GPK^10ETeGZZrnClG-_5Kn>Si$b zp*lO=2M-@baU8{u;}7Z7F4HD!7WF8bzvo}Cvu0|7{l?nz*3Qh^VzM0fp1!nIlNHru z?vjgqf{?R!MU~s-$S$h9cI%{m!fiC}?XOnH!@fQm4%U6W8V(12{ru>7y^f>(gToi6 z=N~_RS$$n!U3=OUzECGm0^Yzk824b{Q#!9oTU{cHSB&oRLv>zXlm_p3v%H`%APfit zTgX5-5u@ELd^UL`VL%x8w+!(4prMROz}BNXIxx5v0N91w32g2qI7bIe0=6D815rK| z=u=f_F_ce-Ux&CPVC&JRlM2m;3OlRNp(wXI*4HkaRN_%aVL%wT%K-O&kRIRvum0Ts zyC4~a0b$_(WI%OK(vuP1E!?deZ^?J9hMq%NIIi`0n*zgK#fasr_!-m*{F)7560r3M R4@CY5I2vRS2L37oKLM6mW6b~n From 1a8eae54827fc237ec826a12fc5e816ba9fe0dd1 Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Fri, 29 Jan 2021 06:47:46 +0100 Subject: [PATCH 5/9] rename project in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6315019..15b870f 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ README_TEXT = readme.read() setup( - name="labelprinterkit-avis", + name="brother-label-printer", version="0.0.5", description="A library for creating and printing labels", use_scm_version=True, From 3a98e5b971b32caab846fc83b12c1deba542ed95 Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Fri, 29 Jan 2021 11:21:49 +0100 Subject: [PATCH 6/9] Minor bug fix and increased minimum python version to 3.9 --- brother_label_printer/backends/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/brother_label_printer/backends/__init__.py b/brother_label_printer/backends/__init__.py index 76dca69..5f33686 100644 --- a/brother_label_printer/backends/__init__.py +++ b/brother_label_printer/backends/__init__.py @@ -17,7 +17,7 @@ def __init__(self, dev): try: if self.dev.is_kernel_driver_active(0): self.dev.detach_kernel_driver(0) - except NotImplemented: + except NotImplementedError: pass self.lock = threading.Lock() diff --git a/setup.py b/setup.py index 15b870f..467442a 100644 --- a/setup.py +++ b/setup.py @@ -28,5 +28,5 @@ 'pyserial', 'qrcode' ], - python_requires='>=3.4' + python_requires='>=3.9' ) From f1c916f921f07759d02a08087dec405ce7f3f2eb Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Fri, 29 Jan 2021 11:31:47 +0100 Subject: [PATCH 7/9] Ignore USB errors --- brother_label_printer/backends/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/brother_label_printer/backends/__init__.py b/brother_label_printer/backends/__init__.py index 5f33686..6c373be 100644 --- a/brother_label_printer/backends/__init__.py +++ b/brother_label_printer/backends/__init__.py @@ -19,6 +19,8 @@ def __init__(self, dev): self.dev.detach_kernel_driver(0) except NotImplementedError: pass + except usb.core.USBError: + pass self.lock = threading.Lock() @classmethod From c41d25b909d748a7e3e83ef7207cfd8abc433124 Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Fri, 29 Jan 2021 13:43:23 +0100 Subject: [PATCH 8/9] removed fixed value of two copies. Instead a default of one will be taken if none is provided --- brother_label_printer/printers/brother_pt700.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brother_label_printer/printers/brother_pt700.py b/brother_label_printer/printers/brother_pt700.py index 00b028e..9991333 100644 --- a/brother_label_printer/printers/brother_pt700.py +++ b/brother_label_printer/printers/brother_pt700.py @@ -233,7 +233,7 @@ def print_label(self, label: Label, copies=1) -> Status: with self.io.lock: self._raw_print( - self.status, create_copies(img_bytes, ceil(img.size[0] / 8), 2)) + self.status, create_copies(img_bytes, ceil(img.size[0] / 8), copies)) return self.get_status() From 6cb499eb0a30c8f859900bbeb2ae8ececd7c35ee Mon Sep 17 00:00:00 2001 From: Michael Harbarth Date: Sat, 22 May 2021 20:45:29 +0200 Subject: [PATCH 9/9] Deleted a debug print information print --- brother_label_printer/printers/brother_pt700.py | 1 - 1 file changed, 1 deletion(-) diff --git a/brother_label_printer/printers/brother_pt700.py b/brother_label_printer/printers/brother_pt700.py index 9991333..c5c5b26 100644 --- a/brother_label_printer/printers/brother_pt700.py +++ b/brother_label_printer/printers/brother_pt700.py @@ -275,7 +275,6 @@ def _raw_print(self, status: Status, documents: list[Iterable[bytes]]) -> None: if i+1 < len(documents): self.next_page() - print("next page") end = time.time() + 20 while True: if self.status.data.get('status_type') == 6 and self.status.data.get('phase_type') == 0: