From d56136a40c7508786fc05097df68921b4d78717f Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Tue, 23 Jul 2024 17:52:48 +0000 Subject: [PATCH 01/11] implement status check to retrieve printer and media info --- brother_ql/backends/helpers.py | 49 ++++++++++++++++++++++++++++++++++ brother_ql/cli.py | 13 +++++++++ brother_ql/reader.py | 4 +++ 3 files changed, 66 insertions(+) diff --git a/brother_ql/backends/helpers.py b/brother_ql/backends/helpers.py index 279221c..7939115 100755 --- a/brother_ql/backends/helpers.py +++ b/brother_ql/backends/helpers.py @@ -101,3 +101,52 @@ def send(instructions, printer_identifier=None, backend_identifier=None, blockin logger.info("Printing was successful. Waiting for the next job.") return status + + +def status( + printer_identifier=None, + backend_identifier=None, +): + """ + Retrieve status info from the printer, including model and currently loaded media size. + + :param str printer_identifier: Identifier for the printer. + :param str backend_identifier: Can enforce the use of a specific backend. + """ + + selected_backend = None + if backend_identifier: + selected_backend = backend_identifier + else: + try: + selected_backend = guess_backend(printer_identifier) + except ValueError: + logger.info( + "No backend stated. Selecting the default linux_kernel backend." + ) + selected_backend = "linux_kernel" + if selected_backend == "network": + # Not implemented due to lack of an available test device + raise NotImplementedError + + be = backend_factory(selected_backend) + BrotherQLBackend = be["backend_class"] + printer = BrotherQLBackend(printer_identifier) + + logger.info("Sending status information request to the printer.") + printer.write(b"\x1b\x69\x53") # "ESC i S" Status information request + data = printer.read() + try: + result = interpret_response(data) + except ValueError: + logger.error("Failed to parse response data: %s", data) + + logger.info(f"Printer Series Code: 0x{result["series_code"]:02x}") + logger.info(f"Printer Model Code: 0x{result["model_code"]:02x}") + logger.info(f"Printer Status Type: {result["status_type"]} ") + logger.info(f"Printer Phase Type: {result["phase_type"]})") + logger.info(f"Printer Errors: {result["errors"]}") + logger.info(f"Media Type: {result["media_type"]}") + logger.info(f"Media Size: {result["media_width"]} x {result["media_length"]} mm") + + return result diff --git a/brother_ql/cli.py b/brother_ql/cli.py index f573f8c..d5b2884 100755 --- a/brother_ql/cli.py +++ b/brother_ql/cli.py @@ -160,7 +160,20 @@ def analyze_cmd(ctx, *args, **kwargs): @click.pass_context def send_cmd(ctx, *args, **kwargs): from brother_ql.backends.helpers import send + send(instructions=kwargs['instructions'].read(), printer_identifier=ctx.meta.get('PRINTER'), backend_identifier=ctx.meta.get('BACKEND'), blocking=True) + +@cli.command(name="status", short_help="query printer status and the loaded media size") +@click.pass_context +def status_cmd(ctx, *args, **kwargs): + from brother_ql.backends.helpers import status + + status( + printer_identifier=ctx.meta.get("PRINTER"), + backend_identifier=ctx.meta.get("BACKEND"), + ) + + if __name__ == '__main__': cli() diff --git a/brother_ql/reader.py b/brother_ql/reader.py index ecf2fa5..bf4f7a0 100755 --- a/brother_ql/reader.py +++ b/brother_ql/reader.py @@ -167,6 +167,8 @@ def interpret_response(data): raise NameError("Printer response doesn't start with the usual header (80:20:42)", hex_format(data)) for i, byte_name in enumerate(RESP_BYTE_NAMES): logger.debug('Byte %2d %24s %02X', i, byte_name+':', data[i]) + series_code = data[3] + model_code = data[4] errors = [] error_info_1 = data[8] error_info_2 = data[9] @@ -204,6 +206,8 @@ def interpret_response(data): logger.error("Unknown phase type %02X", phase_type) response = { + 'series_code': series_code, + 'model_code': model_code, 'status_type': status_type, 'phase_type': phase_type, 'media_type': media_type, From 0ecd6e820d9bb54c07b8ca9139b544c85ab6cb41 Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Tue, 23 Jul 2024 17:55:01 +0000 Subject: [PATCH 02/11] add hardware info for known printers --- brother_ql/models.py | 157 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 135 insertions(+), 22 deletions(-) diff --git a/brother_ql/models.py b/brother_ql/models.py index d964f90..be494bf 100644 --- a/brother_ql/models.py +++ b/brother_ql/models.py @@ -37,34 +37,147 @@ class Model(object): two_color = attrib(type=bool, default=False) #: Number of NULL bytes needed for the invalidate command. num_invalidate_bytes = attrib(type=int, default=200) + #: Hardware IDs + series_code = attrib(type=int, default=0xFFFF) + model_code = attrib(type=int, default=0xFFFF) + product_id = attrib(type=int, default=0xFFFF) @property def name(self): return self.identifier ALL_MODELS = [ - Model('QL-500', (295, 11811), compression=False, mode_setting=False, expanded_mode=False, cutting=False), - Model('QL-550', (295, 11811), compression=False, mode_setting=False), - Model('QL-560', (295, 11811), compression=False, mode_setting=False), - Model('QL-570', (150, 11811), compression=False, mode_setting=False), - Model('QL-580N', (150, 11811)), - Model('QL-600', (150, 11811)), - Model('QL-650TD', (295, 11811)), - Model('QL-700', (150, 11811), compression=False, mode_setting=False), - Model('QL-710W', (150, 11811)), - Model('QL-720NW', (150, 11811)), - Model('QL-800', (150, 11811), two_color=True, compression=False, num_invalidate_bytes=400), - Model('QL-810W', (150, 11811), two_color=True, num_invalidate_bytes=400), - Model('QL-820NWB',(150, 11811), two_color=True, num_invalidate_bytes=400), - Model('QL-1050', (295, 35433), number_bytes_per_row=162, additional_offset_r=44), - Model('QL-1060N', (295, 35433), number_bytes_per_row=162, additional_offset_r=44), - Model('QL-1100', (301, 35434), number_bytes_per_row=162, additional_offset_r=44), - Model('QL-1110NWB',(301, 35434), number_bytes_per_row=162, additional_offset_r=44), - Model('QL-1115NWB',(301, 35434), number_bytes_per_row=162, additional_offset_r=44), - Model('PT-E550W', (31, 14172), number_bytes_per_row=16), - Model('PT-P750W', (31, 14172), number_bytes_per_row=16), - Model('PT-P900W', (57, 28346), number_bytes_per_row=70), - Model('PT-P950NW', (57, 28346), number_bytes_per_row=70), + Model( + identifier="QL-500", + min_max_length_dots=(295, 11811), + compression=False, + mode_setting=False, + expanded_mode=False, + cutting=False, + series_code=0x30, + model_code=0x4F, + product_id=0x2015, + ), + Model( + identifier="QL-550", + min_max_length_dots=(295, 11811), + compression=False, + mode_setting=False, + series_code=0x30, + model_code=0x4F, + product_id=0x2016, + ), + Model( + identifier="QL-560", + min_max_length_dots=(295, 11811), + compression=False, + mode_setting=False, + series_code=0x34, + model_code=0x31, + product_id=0x2027, + ), + Model( + identifier="QL-570", + min_max_length_dots=(150, 11811), + compression=False, + mode_setting=False, + series_code=0x34, + model_code=0x32, + product_id=0x2028, + ), + Model( + identifier="QL-580N", + min_max_length_dots=(150, 11811), + series_code=0x34, + model_code=0x33, + product_id=0x2029, + ), + Model(identifier="QL-600", min_max_length_dots=(150, 11811)), + Model( + identifier="QL-650TD", + min_max_length_dots=(295, 11811), + series_code=0x30, + model_code=0x51, + product_id=0x201B, + ), + Model( + identifier="QL-700", + min_max_length_dots=(150, 11811), + compression=False, + mode_setting=False, + series_code=0x34, + model_code=0x35, + product_id=0x2042, + ), + Model(identifier="QL-710W", min_max_length_dots=(150, 11811)), + Model(identifier="QL-720NW", min_max_length_dots=(150, 11811)), + Model( + identifier="QL-800", + min_max_length_dots=(150, 11811), + two_color=True, + compression=False, + num_invalidate_bytes=400, + ), + Model( + identifier="QL-810W", + min_max_length_dots=(150, 11811), + two_color=True, + num_invalidate_bytes=400, + ), + Model( + identifier="QL-820NWB", + min_max_length_dots=(150, 11811), + two_color=True, + num_invalidate_bytes=400, + ), + Model( + identifier="QL-1050", + min_max_length_dots=(295, 35433), + number_bytes_per_row=162, + additional_offset_r=44, + series_code=0x30, + model_code=0x50, + product_id=0x2020, + ), + Model( + identifier="QL-1060N", + min_max_length_dots=(295, 35433), + number_bytes_per_row=162, + additional_offset_r=44, + series_code=0x34, + model_code=0x34, + product_id=0x202A, + ), + Model( + identifier="QL-1100", + min_max_length_dots=(301, 35434), + number_bytes_per_row=162, + additional_offset_r=44, + ), + Model( + identifier="QL-1110NWB", + min_max_length_dots=(301, 35434), + number_bytes_per_row=162, + additional_offset_r=44, + ), + Model( + identifier="QL-1115NWB", + min_max_length_dots=(301, 35434), + number_bytes_per_row=162, + additional_offset_r=44, + ), + Model( + identifier="PT-E550W", min_max_length_dots=(31, 14172), number_bytes_per_row=16 + ), + Model( + identifier="PT-P750W", min_max_length_dots=(31, 14172), number_bytes_per_row=16 + ), + Model( + identifier="PT-P900W", min_max_length_dots=(57, 28346), number_bytes_per_row=70 + ), + Model( + identifier="PT-P950NW", min_max_length_dots=(57, 28346), number_bytes_per_row=70 + ), ] class ModelsManager(ElementsManager): From 6d0b108b4872bcb1bc2bfd2d836a24b5489edd98 Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Tue, 23 Jul 2024 19:02:12 +0000 Subject: [PATCH 03/11] printer model detection during discovery --- brother_ql/backends/pyusb.py | 6 ++-- brother_ql/cli.py | 53 ++++++++++++++++++++++++++++++------ brother_ql/output_helpers.py | 5 ---- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/brother_ql/backends/pyusb.py b/brother_ql/backends/pyusb.py index 183e596..794a339 100755 --- a/brother_ql/backends/pyusb.py +++ b/brother_ql/backends/pyusb.py @@ -44,8 +44,10 @@ def __call__(self, device): def identifier(dev): try: - serial = usb.util.get_string(dev, 256, dev.iSerialNumber) - return 'usb://0x{:04x}:0x{:04x}_{}'.format(dev.idVendor, dev.idProduct, serial) + serial = usb.util.get_string(dev, dev.iSerialNumber) + return "usb://0x{:04x}:0x{:04x}/{}".format( + dev.idVendor, dev.idProduct, serial + ) except: return 'usb://0x{:04x}:0x{:04x}'.format(dev.idVendor, dev.idProduct) diff --git a/brother_ql/cli.py b/brother_ql/cli.py index d5b2884..86c0890 100755 --- a/brother_ql/cli.py +++ b/brother_ql/cli.py @@ -2,12 +2,15 @@ # Python standard library import logging +import os +from urllib.parse import urlparse # external dependencies import click # imports from this very package from brother_ql.devicedependent import models, label_sizes, label_type_specs, DIE_CUT_LABEL, ENDLESS_LABEL, ROUND_DIE_CUT_LABEL +from brother_ql.models import ModelsManager from brother_ql.backends import available_backends, backend_factory @@ -41,16 +44,50 @@ def cli(ctx, *args, **kwargs): @cli.command() @click.pass_context def discover(ctx): - """ find connected label printers """ - backend = ctx.meta.get('BACKEND', 'pyusb') - discover_and_list_available_devices(backend) + """find connected label printers""" + backend = ctx.meta.get("BACKEND", "pyusb") + if backend is None: + logger.info("Defaulting to pyusb as backend for discovery") + backend = "pyusb" + from brother_ql.backends.helpers import discover, status -def discover_and_list_available_devices(backend): - from brother_ql.backends.helpers import discover available_devices = discover(backend_identifier=backend) - from brother_ql.output_helpers import log_discovered_devices, textual_description_discovered_devices - log_discovered_devices(available_devices) - print(textual_description_discovered_devices(available_devices)) + for device in available_devices: + device_status = None + result = {"model": "unknown"} + + # skip network discovery since it's not supported + if backend == "pyusb" or backend == "linux_kernel": + logger.info(f"Probing device at {device["identifier"]}") + + # check permissions before accessing lp* devices + if backend == "linux_kernel": + url = urlparse(device["identifier"]) + if not os.access(url.path, os.W_OK): + logger.info( + f"Cannot access device {device["identifier"]} due to insufficient permissions" + ) + continue + + # send status request + device_status = status( + printer_identifier=device["identifier"], + backend_identifier=backend, + ) + + # look up series code and model code + for m in ModelsManager().iter_elements(): + if ( + device_status["series_code"] == m.series_code + and device_status["model_code"] == m.model_code + ): + result = {"model": m.identifier} + break + + result.update(device) + logger.info( + "Found a label printer at: {identifier} (model: {model})".format(**result), + ) @cli.group() @click.pass_context diff --git a/brother_ql/output_helpers.py b/brother_ql/output_helpers.py index 2817b7a..0d42955 100644 --- a/brother_ql/output_helpers.py +++ b/brother_ql/output_helpers.py @@ -24,11 +24,6 @@ def textual_label_description(labels_to_include): output += fmt.format(label_size=label_size, dots_printable=dots_printable, label_descr=label_descr) return output -def log_discovered_devices(available_devices, level=logging.INFO): - for ad in available_devices: - result = {'model': 'unknown'} - result.update(ad) - logger.log(level, " Found a label printer: {identifier} (model: {model})".format(**result)) def textual_description_discovered_devices(available_devices): output = "" From b30f71efa68b0c1033a2f0b5bd2949a292a5151a Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Tue, 23 Jul 2024 15:24:20 +0000 Subject: [PATCH 04/11] use pyusb as the default backend instead of linux_kernel pyusb is cross-platform and works out of the box after the correct udev rules have been set up, while /dev/usb/lp0 is still typically inaccessible by regular users unless the user is in a specific user group. --- brother_ql/backends/helpers.py | 23 ++++++++++------------- brother_ql/backends/pyusb.py | 2 +- brother_ql/brother_ql_debug.py | 6 ++---- brother_ql/brother_ql_print.py | 6 ++++-- brother_ql/cli.py | 2 +- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/brother_ql/backends/helpers.py b/brother_ql/backends/helpers.py index 7939115..7167552 100755 --- a/brother_ql/backends/helpers.py +++ b/brother_ql/backends/helpers.py @@ -14,14 +14,13 @@ logger = logging.getLogger(__name__) -def discover(backend_identifier='linux_kernel'): - +def discover(backend_identifier="pyusb"): + if backend_identifier is None: + logger.info("Backend for discovery not specified, defaulting to pyusb") + backend_identifier = "pyusb" be = backend_factory(backend_identifier) - list_available_devices = be['list_available_devices'] - BrotherQLBackend = be['backend_class'] - - available_devices = list_available_devices() - return available_devices + list_available_devices = be["list_available_devices"] + return list_available_devices() def send(instructions, printer_identifier=None, backend_identifier=None, blocking=True): """ @@ -47,8 +46,8 @@ def send(instructions, printer_identifier=None, backend_identifier=None, blockin try: selected_backend = guess_backend(printer_identifier) except: - logger.info("No backend stated. Selecting the default linux_kernel backend.") - selected_backend = 'linux_kernel' + logger.info("No backend stated. Selecting the default pyusb backend.") + selected_backend = "pyusb" be = backend_factory(selected_backend) list_available_devices = be['list_available_devices'] @@ -121,10 +120,8 @@ def status( try: selected_backend = guess_backend(printer_identifier) except ValueError: - logger.info( - "No backend stated. Selecting the default linux_kernel backend." - ) - selected_backend = "linux_kernel" + logger.info("No backend stated. Selecting the default pyusb backend.") + selected_backend = "pyusb" if selected_backend == "network": # Not implemented due to lack of an available test device raise NotImplementedError diff --git a/brother_ql/backends/pyusb.py b/brother_ql/backends/pyusb.py index 794a339..7be2749 100755 --- a/brother_ql/backends/pyusb.py +++ b/brother_ql/backends/pyusb.py @@ -21,7 +21,7 @@ def list_available_devices(): returns: devices: a list of dictionaries with the keys 'identifier' and 'instance': \ [ {'identifier': 'usb://0x04f9:0x2015/C5Z315686', 'instance': pyusb.core.Device()}, ] - The 'identifier' is of the format idVendor:idProduct_iSerialNumber. + The 'identifier' is of the format idVendor:idProduct/iSerialNumber. """ class find_class(object): diff --git a/brother_ql/brother_ql_debug.py b/brother_ql/brother_ql_debug.py index 3415097..e056d0a 100755 --- a/brother_ql/brother_ql_debug.py +++ b/brother_ql/brother_ql_debug.py @@ -9,10 +9,8 @@ logger = logging.getLogger(__name__) class BrotherQL_USBdebug(object): - - def __init__(self, dev, instructions_data, backend='linux_kernel'): - - be_cls = backend_factory(backend)['backend_class'] + def __init__(self, dev, instructions_data, backend="pyusb"): + be_cls = backend_factory(backend)["backend_class"] self.be = be_cls(dev) self.sleep_time = 0.0 diff --git a/brother_ql/brother_ql_print.py b/brother_ql/brother_ql_print.py index cbec231..d7cd8e0 100755 --- a/brother_ql/brother_ql_print.py +++ b/brother_ql/brother_ql_print.py @@ -55,8 +55,10 @@ def main(): try: selected_backend = guess_backend(args.printer) except: - logger.info("No backend stated. Selecting the default linux_kernel backend.") - selected_backend = 'linux_kernel' + logger.info( + "No backend stated. Selecting the default pyusb backend." + ) + selected_backend = "pyusb" # List any printers found, if explicitly asked to do so or if no identifier has been provided. if args.list_printers or not args.printer: diff --git a/brother_ql/cli.py b/brother_ql/cli.py index 86c0890..97dfc50 100755 --- a/brother_ql/cli.py +++ b/brother_ql/cli.py @@ -157,7 +157,7 @@ def env(ctx, *args, **kwargs): @cli.command('print', short_help='Print a label') @click.argument('images', nargs=-1, type=click.File('rb'), metavar='IMAGE [IMAGE] ...') -@click.option('-l', '--label', type=click.Choice(label_sizes), envvar='BROTHER_QL_LABEL', help='The label (size, type - die-cut or endless). Run `brother_ql info labels` for a full list including ideal pixel dimensions.') +@click.option('-l', '--label', required=True, type=click.Choice(label_sizes), envvar='BROTHER_QL_LABEL', help='The label (size, type - die-cut or endless). Run `brother_ql info labels` for a full list including ideal pixel dimensions.') @click.option('-r', '--rotate', type=click.Choice(('auto', '0', '90', '180', '270')), default='auto', help='Rotate the image (counterclock-wise) by this amount of degrees.') @click.option('-t', '--threshold', type=float, default=70.0, help='The threshold value (in percent) to discriminate between black and white pixels.') @click.option('-d', '--dither', is_flag=True, help='Enable dithering when converting the image to b/w. If set, --threshold is meaningless.') From a0ee20d1632143e87b6e9d00c223c412a9f3c44f Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Tue, 23 Jul 2024 20:48:05 +0000 Subject: [PATCH 05/11] add hardware info for PT-P700 and PT-P750W --- brother_ql/models.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/brother_ql/models.py b/brother_ql/models.py index be494bf..509f8f0 100644 --- a/brother_ql/models.py +++ b/brother_ql/models.py @@ -170,7 +170,19 @@ def name(self): identifier="PT-E550W", min_max_length_dots=(31, 14172), number_bytes_per_row=16 ), Model( - identifier="PT-P750W", min_max_length_dots=(31, 14172), number_bytes_per_row=16 + identifier="PT-P700", + min_max_length_dots=(31, 14172), + number_bytes_per_row=16, + series_code=0x30, + model_code=0x67, + product_id=0x2061, + ), + Model( + identifier="PT-P750W", + min_max_length_dots=(31, 14172), + number_bytes_per_row=16, + series_code=0x30, + model_code=0x68, ), Model( identifier="PT-P900W", min_max_length_dots=(57, 28346), number_bytes_per_row=70 From e44b154e705a2c0142262cea7e1abacbc17f3f9b Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Mon, 29 Jul 2024 22:26:34 +0000 Subject: [PATCH 06/11] restore removed functions during refactor --- brother_ql/backends/helpers.py | 6 ++++-- brother_ql/brother_ql_debug.py | 4 +++- brother_ql/cli.py | 8 +++++++- brother_ql/output_helpers.py | 5 +++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/brother_ql/backends/helpers.py b/brother_ql/backends/helpers.py index 7167552..2298b3f 100755 --- a/brother_ql/backends/helpers.py +++ b/brother_ql/backends/helpers.py @@ -19,8 +19,10 @@ def discover(backend_identifier="pyusb"): logger.info("Backend for discovery not specified, defaulting to pyusb") backend_identifier = "pyusb" be = backend_factory(backend_identifier) - list_available_devices = be["list_available_devices"] - return list_available_devices() + list_available_devices = be['list_available_devices'] + BrotherQLBackend = be['backend_class'] + available_devices = list_available_devices() + return available_devices def send(instructions, printer_identifier=None, backend_identifier=None, blocking=True): """ diff --git a/brother_ql/brother_ql_debug.py b/brother_ql/brother_ql_debug.py index e056d0a..495825c 100755 --- a/brother_ql/brother_ql_debug.py +++ b/brother_ql/brother_ql_debug.py @@ -9,8 +9,10 @@ logger = logging.getLogger(__name__) class BrotherQL_USBdebug(object): + def __init__(self, dev, instructions_data, backend="pyusb"): - be_cls = backend_factory(backend)["backend_class"] + + be_cls = backend_factory(backend)['backend_class'] self.be = be_cls(dev) self.sleep_time = 0.0 diff --git a/brother_ql/cli.py b/brother_ql/cli.py index 97dfc50..3f6932d 100755 --- a/brother_ql/cli.py +++ b/brother_ql/cli.py @@ -89,6 +89,13 @@ def discover(ctx): "Found a label printer at: {identifier} (model: {model})".format(**result), ) +def discover_and_list_available_devices(backend): + from brother_ql.backends.helpers import discover + available_devices = discover(backend_identifier=backend) + from brother_ql.output_helpers import log_discovered_devices, textual_description_discovered_devices + log_discovered_devices(available_devices) + print(textual_description_discovered_devices(available_devices)) + @cli.group() @click.pass_context def info(ctx, *args, **kwargs): @@ -197,7 +204,6 @@ def analyze_cmd(ctx, *args, **kwargs): @click.pass_context def send_cmd(ctx, *args, **kwargs): from brother_ql.backends.helpers import send - send(instructions=kwargs['instructions'].read(), printer_identifier=ctx.meta.get('PRINTER'), backend_identifier=ctx.meta.get('BACKEND'), blocking=True) diff --git a/brother_ql/output_helpers.py b/brother_ql/output_helpers.py index 0d42955..2817b7a 100644 --- a/brother_ql/output_helpers.py +++ b/brother_ql/output_helpers.py @@ -24,6 +24,11 @@ def textual_label_description(labels_to_include): output += fmt.format(label_size=label_size, dots_printable=dots_printable, label_descr=label_descr) return output +def log_discovered_devices(available_devices, level=logging.INFO): + for ad in available_devices: + result = {'model': 'unknown'} + result.update(ad) + logger.log(level, " Found a label printer: {identifier} (model: {model})".format(**result)) def textual_description_discovered_devices(available_devices): output = "" From de284d110d0316684bc9a87b0ca486f5f3abb4e0 Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Mon, 29 Jul 2024 22:57:31 +0000 Subject: [PATCH 07/11] fill in hardware info for all known models --- brother_ql/models.py | 65 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/brother_ql/models.py b/brother_ql/models.py index 509f8f0..a98cd6d 100644 --- a/brother_ql/models.py +++ b/brother_ql/models.py @@ -11,6 +11,7 @@ class Model(object): This class represents a printer model. All specifics of a certain model and the opcodes it supports should be contained in this class. """ + #: A string identifier given to each model implemented. Eg. 'QL-500'. identifier = attrib(type=str) #: Minimum and maximum number of rows or 'dots' that can be printed. @@ -92,7 +93,13 @@ def name(self): model_code=0x33, product_id=0x2029, ), - Model(identifier="QL-600", min_max_length_dots=(150, 11811)), + Model( + identifier="QL-600", + min_max_length_dots=(150, 11811), + series_code=0x34, + model_code=0x47, + product_id=0x20C0, + ), Model( identifier="QL-650TD", min_max_length_dots=(295, 11811), @@ -109,26 +116,47 @@ def name(self): model_code=0x35, product_id=0x2042, ), - Model(identifier="QL-710W", min_max_length_dots=(150, 11811)), - Model(identifier="QL-720NW", min_max_length_dots=(150, 11811)), + Model( + identifier="QL-710W", + min_max_length_dots=(150, 11811), + series_code=0x34, + model_code=0x36, + product_id=0x2043, + ), + Model( + identifier="QL-720NW", + min_max_length_dots=(150, 11811), + series_code=0x34, + model_code=0x37, + product_id=0x2044, + ), Model( identifier="QL-800", min_max_length_dots=(150, 11811), two_color=True, compression=False, num_invalidate_bytes=400, + series_code=0x34, + model_code=0x38, + product_id=0x209B, ), Model( identifier="QL-810W", min_max_length_dots=(150, 11811), two_color=True, num_invalidate_bytes=400, + series_code=0x34, + model_code=0x39, + product_id=0x209C, ), Model( identifier="QL-820NWB", min_max_length_dots=(150, 11811), two_color=True, num_invalidate_bytes=400, + series_code=0x34, + model_code=0x41, + product_id=0x209D, ), Model( identifier="QL-1050", @@ -153,21 +181,35 @@ def name(self): min_max_length_dots=(301, 35434), number_bytes_per_row=162, additional_offset_r=44, + series_code=0x34, + model_code=0x43, + product_id=0x20A7, ), Model( identifier="QL-1110NWB", min_max_length_dots=(301, 35434), number_bytes_per_row=162, additional_offset_r=44, + series_code=0x34, + model_code=0x44, + product_id=0x20A8, ), Model( identifier="QL-1115NWB", min_max_length_dots=(301, 35434), number_bytes_per_row=162, additional_offset_r=44, + series_code=0x34, + model_code=0x45, + product_id=0x20AB, ), Model( - identifier="PT-E550W", min_max_length_dots=(31, 14172), number_bytes_per_row=16 + identifier="PT-E550W", + min_max_length_dots=(31, 14172), + number_bytes_per_row=16, + series_code=0x30, + model_code=0x68, + product_id=0x2060, ), Model( identifier="PT-P700", @@ -183,12 +225,23 @@ def name(self): number_bytes_per_row=16, series_code=0x30, model_code=0x68, + product_id=0x2062, ), Model( - identifier="PT-P900W", min_max_length_dots=(57, 28346), number_bytes_per_row=70 + identifier="PT-P900W", + min_max_length_dots=(57, 28346), + number_bytes_per_row=70, + series_code=0x30, + model_code=0x69, + product_id=0x2085, ), Model( - identifier="PT-P950NW", min_max_length_dots=(57, 28346), number_bytes_per_row=70 + identifier="PT-P950NW", + min_max_length_dots=(57, 28346), + number_bytes_per_row=70, + series_code=0x30, + model_code=0x70, + product_id=0x2086, ), ] From 1e681b34233606d182cc698ae9e7fd2b7d3cba5e Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Sat, 3 Aug 2024 03:31:25 +0000 Subject: [PATCH 08/11] revert default backend to linux_kernel --- brother_ql/backends/helpers.py | 14 +++++++------- brother_ql/brother_ql_debug.py | 2 +- brother_ql/brother_ql_print.py | 6 ++---- brother_ql/cli.py | 8 ++++---- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/brother_ql/backends/helpers.py b/brother_ql/backends/helpers.py index 2298b3f..62209f9 100755 --- a/brother_ql/backends/helpers.py +++ b/brother_ql/backends/helpers.py @@ -14,10 +14,10 @@ logger = logging.getLogger(__name__) -def discover(backend_identifier="pyusb"): +def discover(backend_identifier='linux_kernel'): if backend_identifier is None: - logger.info("Backend for discovery not specified, defaulting to pyusb") - backend_identifier = "pyusb" + logger.info("Backend for discovery not specified, defaulting to linux_kernel.") + backend_identifier = "linux_kernel" be = backend_factory(backend_identifier) list_available_devices = be['list_available_devices'] BrotherQLBackend = be['backend_class'] @@ -48,8 +48,8 @@ def send(instructions, printer_identifier=None, backend_identifier=None, blockin try: selected_backend = guess_backend(printer_identifier) except: - logger.info("No backend stated. Selecting the default pyusb backend.") - selected_backend = "pyusb" + logger.info("No backend stated. Selecting the default linux_kernel backend.") + selected_backend = 'linux_kernel' be = backend_factory(selected_backend) list_available_devices = be['list_available_devices'] @@ -122,8 +122,8 @@ def status( try: selected_backend = guess_backend(printer_identifier) except ValueError: - logger.info("No backend stated. Selecting the default pyusb backend.") - selected_backend = "pyusb" + logger.info("No backend stated. Selecting the default linux_kernel backend.") + selected_backend = "linux_kernel" if selected_backend == "network": # Not implemented due to lack of an available test device raise NotImplementedError diff --git a/brother_ql/brother_ql_debug.py b/brother_ql/brother_ql_debug.py index 495825c..3415097 100755 --- a/brother_ql/brother_ql_debug.py +++ b/brother_ql/brother_ql_debug.py @@ -10,7 +10,7 @@ class BrotherQL_USBdebug(object): - def __init__(self, dev, instructions_data, backend="pyusb"): + def __init__(self, dev, instructions_data, backend='linux_kernel'): be_cls = backend_factory(backend)['backend_class'] self.be = be_cls(dev) diff --git a/brother_ql/brother_ql_print.py b/brother_ql/brother_ql_print.py index d7cd8e0..cbec231 100755 --- a/brother_ql/brother_ql_print.py +++ b/brother_ql/brother_ql_print.py @@ -55,10 +55,8 @@ def main(): try: selected_backend = guess_backend(args.printer) except: - logger.info( - "No backend stated. Selecting the default pyusb backend." - ) - selected_backend = "pyusb" + logger.info("No backend stated. Selecting the default linux_kernel backend.") + selected_backend = 'linux_kernel' # List any printers found, if explicitly asked to do so or if no identifier has been provided. if args.list_printers or not args.printer: diff --git a/brother_ql/cli.py b/brother_ql/cli.py index 3f6932d..3c08b87 100755 --- a/brother_ql/cli.py +++ b/brother_ql/cli.py @@ -44,10 +44,10 @@ def cli(ctx, *args, **kwargs): @cli.command() @click.pass_context def discover(ctx): - """find connected label printers""" - backend = ctx.meta.get("BACKEND", "pyusb") + """ find connected label printers """ + backend = ctx.meta.get('BACKEND', 'pyusb') if backend is None: - logger.info("Defaulting to pyusb as backend for discovery") + logger.info("Defaulting to pyusb as backend for discovery.") backend = "pyusb" from brother_ql.backends.helpers import discover, status @@ -65,7 +65,7 @@ def discover(ctx): url = urlparse(device["identifier"]) if not os.access(url.path, os.W_OK): logger.info( - f"Cannot access device {device["identifier"]} due to insufficient permissions" + f"Cannot access device {device["identifier"]} due to insufficient permissions. You need to be a part of the lp group to access printers with this backend." ) continue From a22180d0aca9ac23a1b6a3f07d3e40990a0ad894 Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Sat, 3 Aug 2024 06:24:14 +0000 Subject: [PATCH 09/11] fix cutter setting on PT printers --- brother_ql/conversion.py | 8 ++++++-- brother_ql/models.py | 4 ++-- brother_ql/raster.py | 7 ++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/brother_ql/conversion.py b/brother_ql/conversion.py index 4ec9dec..2fb9805 100755 --- a/brother_ql/conversion.py +++ b/brother_ql/conversion.py @@ -73,7 +73,7 @@ def convert(qlr, images, label, **kwargs): except BrotherQLUnsupportedCmd: pass - for image in images: + for i, image in enumerate(images): if isinstance(image, Image.Image): im = image else: @@ -190,6 +190,10 @@ def convert(qlr, images, label, **kwargs): qlr.add_raster_data(black_im, red_im) else: qlr.add_raster_data(im) - qlr.add_print() + + if i == len(images) - 1: + qlr.add_print() + else: + qlr.add_print(last_page=False) return qlr.data diff --git a/brother_ql/models.py b/brother_ql/models.py index a98cd6d..86c0a7c 100644 --- a/brother_ql/models.py +++ b/brother_ql/models.py @@ -213,7 +213,7 @@ def name(self): ), Model( identifier="PT-P700", - min_max_length_dots=(31, 14172), + min_max_length_dots=(31, 7086), number_bytes_per_row=16, series_code=0x30, model_code=0x67, @@ -221,7 +221,7 @@ def name(self): ), Model( identifier="PT-P750W", - min_max_length_dots=(31, 14172), + min_max_length_dots=(31, 7086), number_bytes_per_row=16, series_code=0x30, model_code=0x68, diff --git a/brother_ql/raster.py b/brother_ql/raster.py index 4add429..0c40ee2 100644 --- a/brother_ql/raster.py +++ b/brother_ql/raster.py @@ -60,7 +60,7 @@ def __init__(self, model='QL-500'): self._compression = False self.exception_on_warning = False self.half_cut = True - self.no_chain_printing = False + self.no_chain_printing = True self.num_invalidate_bytes = 200 for m in ModelsManager().iter_elements(): @@ -167,10 +167,7 @@ def add_autocut(self, autocut = False): self._unsupported("Trying to call add_autocut with a printer that doesn't support it") return self.data += b'\x1B\x69\x4D' # ESC i M - if self.model.startswith('PT'): - self.data += bytes([autocut << 5]) - else: - self.data += bytes([autocut << 6]) + self.data += bytes([autocut << 6]) def add_cut_every(self, n=1): if self.model not in cuttingsupport: From 9bf9fbb07e6b9f1ad2145147bb1f393662df4e5f Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Sat, 3 Aug 2024 08:39:04 +0000 Subject: [PATCH 10/11] always send compression mode on supported printers --- brother_ql/conversion.py | 6 ++---- brother_ql/devicedependent.py | 2 +- brother_ql/models.py | 14 +++++++------- brother_ql/raster.py | 8 +++++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/brother_ql/conversion.py b/brother_ql/conversion.py index 2fb9805..a966593 100755 --- a/brother_ql/conversion.py +++ b/brother_ql/conversion.py @@ -182,10 +182,8 @@ def convert(qlr, images, label, **kwargs): except BrotherQLUnsupportedCmd: pass qlr.add_margins(label_specs['feed_margin']) - try: - if compress: qlr.add_compression(True) - except BrotherQLUnsupportedCmd: - pass + if qlr.compression_support: + qlr.add_compression(compress) if red: qlr.add_raster_data(black_im, red_im) else: diff --git a/brother_ql/devicedependent.py b/brother_ql/devicedependent.py index ac90317..29cd52b 100644 --- a/brother_ql/devicedependent.py +++ b/brother_ql/devicedependent.py @@ -54,7 +54,7 @@ def _populate_model_legacy_structures(): if model.mode_setting: modesetting.append(model.identifier) if model.cutting: cuttingsupport.append(model.identifier) if model.expanded_mode: expandedmode.append(model.identifier) - if model.compression: compressionsupport.append(model.identifier) + if model.compression_support: compressionsupport.append(model.identifier) if model.two_color: two_color_support.append(model.identifier) def _populate_label_legacy_structures(): diff --git a/brother_ql/models.py b/brother_ql/models.py index 86c0a7c..fbd4936 100644 --- a/brother_ql/models.py +++ b/brother_ql/models.py @@ -32,7 +32,7 @@ class Model(object): expanded_mode = attrib(type=bool, default=True) #: Model has support for compressing the transmitted raster data. #: Some models with only USB connectivity don't support compression. - compression = attrib(type=bool, default=True) + compression_support = attrib(type=bool, default=True) #: Support for two color printing (black/red/white) #: available only on some newer models. two_color = attrib(type=bool, default=False) @@ -51,7 +51,7 @@ def name(self): Model( identifier="QL-500", min_max_length_dots=(295, 11811), - compression=False, + compression_support=False, mode_setting=False, expanded_mode=False, cutting=False, @@ -62,7 +62,7 @@ def name(self): Model( identifier="QL-550", min_max_length_dots=(295, 11811), - compression=False, + compression_support=False, mode_setting=False, series_code=0x30, model_code=0x4F, @@ -71,7 +71,7 @@ def name(self): Model( identifier="QL-560", min_max_length_dots=(295, 11811), - compression=False, + compression_support=False, mode_setting=False, series_code=0x34, model_code=0x31, @@ -80,7 +80,7 @@ def name(self): Model( identifier="QL-570", min_max_length_dots=(150, 11811), - compression=False, + compression_support=False, mode_setting=False, series_code=0x34, model_code=0x32, @@ -110,7 +110,7 @@ def name(self): Model( identifier="QL-700", min_max_length_dots=(150, 11811), - compression=False, + compression_support=False, mode_setting=False, series_code=0x34, model_code=0x35, @@ -134,7 +134,7 @@ def name(self): identifier="QL-800", min_max_length_dots=(150, 11811), two_color=True, - compression=False, + compression_support=False, num_invalidate_bytes=400, series_code=0x34, model_code=0x38, diff --git a/brother_ql/raster.py b/brother_ql/raster.py index 0c40ee2..ea3ac58 100644 --- a/brother_ql/raster.py +++ b/brother_ql/raster.py @@ -57,7 +57,8 @@ def __init__(self, model='QL-500'): self.cut_at_end = True self.dpi_600 = False self.two_color_printing = False - self._compression = False + self.compression_enabled = False + self.compression_support = False self.exception_on_warning = False self.half_cut = True self.no_chain_printing = True @@ -66,6 +67,7 @@ def __init__(self, model='QL-500'): for m in ModelsManager().iter_elements(): if self.model == m.identifier: self.num_invalidate_bytes = m.num_invalidate_bytes + self.compression_support = m.compression_support break def _warn(self, problem, kind=BrotherQLRasterError): @@ -213,7 +215,7 @@ def add_compression(self, compression=True): if self.model not in compressionsupport: self._unsupported("Trying to set compression on a printer that doesn't support it") return - self._compression = compression + self.compression_enabled = compression self.data += b'\x4D' # M self.data += bytes([compression << 1]) @@ -255,7 +257,7 @@ def add_raster_data(self, image, second_image=None): while start + row_len <= frame_len: for i, frame in enumerate(frames): row = frame[start:start+row_len] - if self._compression: + if self.compression_enabled: row = packbits.encode(row) translen = len(row) # number of bytes to be transmitted if self.model.startswith('PT'): From e929e771688c3bad38fd46056475e40f4922cb78 Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Sun, 4 Aug 2024 04:53:29 +0000 Subject: [PATCH 11/11] fix f-strings --- brother_ql/backends/helpers.py | 14 +++++++------- brother_ql/cli.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/brother_ql/backends/helpers.py b/brother_ql/backends/helpers.py index 62209f9..46f38f2 100755 --- a/brother_ql/backends/helpers.py +++ b/brother_ql/backends/helpers.py @@ -140,12 +140,12 @@ def status( except ValueError: logger.error("Failed to parse response data: %s", data) - logger.info(f"Printer Series Code: 0x{result["series_code"]:02x}") - logger.info(f"Printer Model Code: 0x{result["model_code"]:02x}") - logger.info(f"Printer Status Type: {result["status_type"]} ") - logger.info(f"Printer Phase Type: {result["phase_type"]})") - logger.info(f"Printer Errors: {result["errors"]}") - logger.info(f"Media Type: {result["media_type"]}") - logger.info(f"Media Size: {result["media_width"]} x {result["media_length"]} mm") + logger.info(f"Printer Series Code: 0x{result['series_code']:02x}") + logger.info(f"Printer Model Code: 0x{result['model_code']:02x}") + logger.info(f"Printer Status Type: {result['status_type']} ") + logger.info(f"Printer Phase Type: {result['phase_type']})") + logger.info(f"Printer Errors: {result['errors']}") + logger.info(f"Media Type: {result['media_type']}") + logger.info(f"Media Size: {result['media_width']} x {result['media_length']} mm") return result diff --git a/brother_ql/cli.py b/brother_ql/cli.py index 3c08b87..77cc934 100755 --- a/brother_ql/cli.py +++ b/brother_ql/cli.py @@ -58,14 +58,14 @@ def discover(ctx): # skip network discovery since it's not supported if backend == "pyusb" or backend == "linux_kernel": - logger.info(f"Probing device at {device["identifier"]}") + logger.info(f"Probing device at {device['identifier']}") # check permissions before accessing lp* devices if backend == "linux_kernel": url = urlparse(device["identifier"]) if not os.access(url.path, os.W_OK): logger.info( - f"Cannot access device {device["identifier"]} due to insufficient permissions. You need to be a part of the lp group to access printers with this backend." + f"Cannot access device {device['identifier']} due to insufficient permissions. You need to be a part of the lp group to access printers with this backend." ) continue