From a3fbdcfc50d70eb426c80f430f9382d96cbfe95b Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Mon, 5 Aug 2024 05:30:39 +0000 Subject: [PATCH 1/8] printer power setting support on QL printers --- brother_ql/backends/helpers.py | 107 ++++++++++++++++++++++++++++++++- brother_ql/cli.py | 21 +++++++ brother_ql/reader.py | 16 ++++- 3 files changed, 138 insertions(+), 6 deletions(-) diff --git a/brother_ql/backends/helpers.py b/brother_ql/backends/helpers.py index 46f38f2..41083ed 100755 --- a/brother_ql/backends/helpers.py +++ b/brother_ql/backends/helpers.py @@ -103,13 +103,12 @@ def send(instructions, printer_identifier=None, backend_identifier=None, blockin return status - -def status( +def get_printer( printer_identifier=None, backend_identifier=None, ): """ - Retrieve status info from the printer, including model and currently loaded media size. + Instantiate a printer object for communication. Only bidirectional transport backends are supported. :param str printer_identifier: Identifier for the printer. :param str backend_identifier: Can enforce the use of a specific backend. @@ -131,6 +130,18 @@ def status( be = backend_factory(selected_backend) BrotherQLBackend = be["backend_class"] printer = BrotherQLBackend(printer_identifier) + return printer + +def status( + printer_identifier=None, + backend_identifier=None, +): + """ + Retrieve status info from the printer, including model and currently loaded media size. + + """ + + printer=get_printer(printer_identifier, backend_identifier) logger.info("Sending status information request to the printer.") printer.write(b"\x1b\x69\x53") # "ESC i S" Status information request @@ -149,3 +160,93 @@ def status( logger.info(f"Media Size: {result['media_width']} x {result['media_length']} mm") return result + +def configure( + printer_identifier=None, + backend_identifier=None, + write=False, + auto_power_off=None, + auto_power_on=None, +): + """ + Read or modify power settings. + + :param bool write: Write configuration to printer + :param int auto_power_off: multiples of 10 minutes for power off, 0 means disabled + :param bool auto_power_on: whether to enable auto power on or not, 1 means enabled, 0 means disabled + """ + printer=get_printer(printer_identifier, backend_identifier) + + if write: + if auto_power_off is None or auto_power_on is None: + raise ValueError("You must provide the settings values") + if auto_power_off < 0 or auto_power_off > 6: + raise ValueError("Auto power off can only be set between 0 to 6") + if type(auto_power_on) is not bool: + raise ValueError("Auto power on is a boolean setting") + + printer.write(b"\x1b\x69\x53") # "ESC i S" Status information request + data = printer.read() + result = interpret_response(data) + if result['status_type'] != 'Reply to status request': + raise ValueError + + if write: + # change auto power on settings + printer.write(b"\x1b\x69\x61\x01") # Switch to raster command mode + # 0x1b 0x69 0x55 settings + # 0x70 auto power on + # 0x00 write + # bool state + command = b"\x1b\x69\x55\x70\x00" + auto_power_on.to_bytes(1) + printer.write(command) + + # retrieve status to make sure no errors occured + printer.write(b"\x1b\x69\x53") # "ESC i S" Status information request + data = printer.read() + result = interpret_response(data) + if result['status_type'] != 'Reply to status request': + raise ValueError("Failed to modify settings") + + # read auto power on settings + printer.write(b"\x1b\x69\x61\x01") # Switch to raster command mode + # 0x1b 0x69 0x55 settings + # 0x70 auto power on + # 0x01 read + printer.write(b"\x1b\x69\x55\x70\x01") + data = printer.read() + result = interpret_response(data) + if result['status_type'] != 'Settings report': + raise ValueError + logger.info(f"Auto power on: {'Enabled' if result['setting'] else 'Disabled'}") + + if write: + # change auto power off settings + printer.write(b"\x1b\x69\x61\x01") # Switch to raster command mode + # 0x1b 0x69 0x55 settings + # 0x41 auto power off + # 0x00 write + # u8 multiples of 10 minutes, 0 means disabled + command = b"\x1b\x69\x55\x41\x00" + auto_power_off.to_bytes(1) + printer.write(command) + + # retrieve status to make sure no errors occured + printer.write(b"\x1b\x69\x53") # "ESC i S" Status information request + data = printer.read() + result = interpret_response(data) + if result['status_type'] != 'Reply to status request': + raise ValueError("Failed to modify settings") + + # read auto power off settings + printer.write(b"\x1b\x69\x61\x01") # Switch to raster command mode + # 0x1b 0x69 0x55 settings + # 0x41 auto power off + # 0x01 read + printer.write(b"\x1b\x69\x55\x41\x01") + data = printer.read() + result = interpret_response(data) + if result['status_type'] != 'Settings report': + raise ValueError + logger.info(f"Auto power off delay: {result['setting']*10} minutes") + + return result diff --git a/brother_ql/cli.py b/brother_ql/cli.py index 77cc934..2b5b609 100755 --- a/brother_ql/cli.py +++ b/brother_ql/cli.py @@ -217,6 +217,27 @@ def status_cmd(ctx, *args, **kwargs): backend_identifier=ctx.meta.get("BACKEND"), ) +@cli.command(name="configure", short_help="read and modify printer settings") +@click.option('-w', '--write', is_flag=True, default=False, help='Write settings') +@click.option('-on', '--auto-power-on', is_flag=True, show_default=True, default=False, help='Enable automatic power-on') +@click.option('-off', '--power-off-delay', type=click.IntRange(0, 6), default=6, help='Automatic power-off delay in multiples of 10 minutes. Use 0 to disable automatic power-off.') + +@click.pass_context +def configure_cmd(ctx, *args, **kwargs): + from brother_ql.backends.helpers import configure + + if ctx.meta['MODEL'] is None: + raise ValueError("You need to provide a printer model to change printer settings.") + elif not ctx.meta['MODEL'].startswith("QL"): + raise ValueError("Only QL series printers are supported at the moment.") + + configure( + printer_identifier=ctx.meta.get("PRINTER"), + backend_identifier=ctx.meta.get("BACKEND"), + write=kwargs.get('write'), + auto_power_off=kwargs.get('power_off_delay'), + auto_power_on=kwargs.get('auto_power_on') + ) if __name__ == '__main__': cli() diff --git a/brother_ql/reader.py b/brother_ql/reader.py index bf4f7a0..407c639 100755 --- a/brother_ql/reader.py +++ b/brother_ql/reader.py @@ -29,7 +29,9 @@ b'\x1b\x69\x4B': ("expanded", 1, ""), b'\x1b\x69\x64': ("margins", 2, ""), b'\x1b\x69\x55\x77\x01': ('amedia', 127, "Additional media information command"), + b'\x1b\x69\x55\x41': ('auto_power_off', -1, "Auto power off setting command"), b'\x1b\x69\x55\x4A': ('jobid', 14, "Job ID setting command"), + b'\x1b\x69\x55\x70': ('auto_power_on', -1, "Auto power on setting command"), b'\x1b\x69\x58\x47': ("request_config", 0, "Request transmission of .ini config file of printer"), b'\x1b\x69\x6B\x63': ("number_of_copies", 2, "Internal specification commands"), b'\x1b\x69\x53': ('status request', 0, "A status information request sent to the printer"), @@ -79,6 +81,7 @@ 0x04: 'Turned off', 0x05: 'Notification', 0x06: 'Phase change', + 0xF0: 'Settings report', } RESP_PHASE_TYPES = { @@ -191,9 +194,9 @@ def interpret_response(data): else: logger.error("Unknown media type %02X", media_type) - status_type = data[18] - if status_type in RESP_STATUS_TYPES: - status_type = RESP_STATUS_TYPES[status_type] + status_type_code = data[18] + if status_type_code in RESP_STATUS_TYPES: + status_type = RESP_STATUS_TYPES[status_type_code] logger.debug("Status type: %s", status_type) else: logger.error("Unknown status type %02X", status_type) @@ -205,6 +208,12 @@ def interpret_response(data): else: logger.error("Unknown phase type %02X", phase_type) + setting = None + # settings report + if status_type_code == 0xF0: + logger.debug("Settings report detected") + setting = data[30] + response = { 'series_code': series_code, 'model_code': model_code, @@ -213,6 +222,7 @@ def interpret_response(data): 'media_type': media_type, 'media_width': media_width, 'media_length': media_length, + 'setting': setting, 'errors': errors, } return response From b5abae9d8b1e0e2a2058d012f9e1c6cfac147eff Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Mon, 5 Aug 2024 11:35:38 +0000 Subject: [PATCH 2/8] refactor config and status functions, support PT printer settings --- brother_ql/backends/helpers.py | 205 ++++++++++++++++++--------------- brother_ql/cli.py | 68 +++++------ brother_ql/reader.py | 76 +++++++----- 3 files changed, 192 insertions(+), 157 deletions(-) diff --git a/brother_ql/backends/helpers.py b/brother_ql/backends/helpers.py index 41083ed..17099cd 100755 --- a/brother_ql/backends/helpers.py +++ b/brother_ql/backends/helpers.py @@ -132,121 +132,140 @@ def get_printer( printer = BrotherQLBackend(printer_identifier) return printer -def status( - printer_identifier=None, - backend_identifier=None, +def get_status( + printer, + receive_only=False, + target_status=None, ): """ - Retrieve status info from the printer, including model and currently loaded media size. + Get printer status. + :param BrotherQLBackendGeneric printer: A printer instance. + :param bool receive_only: Don't send the status request command. + :param int target_status: Expected status code. """ - printer=get_printer(printer_identifier, backend_identifier) - - logger.info("Sending status information request to the printer.") - printer.write(b"\x1b\x69\x53") # "ESC i S" Status information request + if not receive_only: + 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) + if target_status is not None: + if result['status_code'] != target_status: + raise ValueError(f"Printer reported 0x{result['status_code']:02x} status instead of 0x{target_status:02x}") + return result + - 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") +def get_setting( + printer, + setting, + payload=None +): + """ + Get setting from printer. + :param BrotherQLBackendGeneric printer: A printer instance. + :param int setting: The code for the setting. + :param bytes payload: Optional additional payload, usually not required. + """ + + # ensure printer is free of errors before proceeding + get_status(printer, target_status=0x0) + # switch to raster command mode + printer.write(b"\x1b\x69\x61\x01") + # send command + command = b"\x1b\x69\x55" + setting.to_bytes(1) + b"\x01" + if payload is not None: + command += payload + printer.write(command) + result = get_status(printer, receive_only=True, target_status=0xF0) return result + +def write_setting( + printer, + setting, + payload, +): + """ + Write setting to printer. + + :param BrotherQLBackendGeneric printer: A printer instance. + :param int setting: The code for the setting. + :param bytes payload: Payload for the setting. + """ + + # switch to raster command mode + printer.write(b"\x1b\x69\x61\x01") + # write settings + command = b"\x1b\x69\x55" + setting.to_bytes(1) + b"\x00" + command += payload + printer.write(command) + # retrieve status to make sure no errors occured + result = get_status(printer) + if result['status_code'] != 0x0: + raise ValueError("Failed to modify settings") + return result + + def configure( printer_identifier=None, backend_identifier=None, - write=False, - auto_power_off=None, - auto_power_on=None, + action="get", + key=None, + value=None, ): """ Read or modify power settings. - :param bool write: Write configuration to printer - :param int auto_power_off: multiples of 10 minutes for power off, 0 means disabled - :param bool auto_power_on: whether to enable auto power on or not, 1 means enabled, 0 means disabled + :param str printer_identifier: Identifier for the printer + :param str backend_identifier: Can enforce the use of a specific backend + :param str action: Action to perform, get or set + :param str key: Key name for the settings + :param int value: Value to update for the specified key """ - printer=get_printer(printer_identifier, backend_identifier) - if write: - if auto_power_off is None or auto_power_on is None: - raise ValueError("You must provide the settings values") - if auto_power_off < 0 or auto_power_off > 6: - raise ValueError("Auto power off can only be set between 0 to 6") - if type(auto_power_on) is not bool: - raise ValueError("Auto power on is a boolean setting") + printer=get_printer(printer_identifier, backend_identifier) - printer.write(b"\x1b\x69\x53") # "ESC i S" Status information request - data = printer.read() - result = interpret_response(data) - if result['status_type'] != 'Reply to status request': - raise ValueError - - if write: - # change auto power on settings - printer.write(b"\x1b\x69\x61\x01") # Switch to raster command mode - # 0x1b 0x69 0x55 settings - # 0x70 auto power on - # 0x00 write - # bool state - command = b"\x1b\x69\x55\x70\x00" + auto_power_on.to_bytes(1) - printer.write(command) - - # retrieve status to make sure no errors occured - printer.write(b"\x1b\x69\x53") # "ESC i S" Status information request - data = printer.read() - result = interpret_response(data) - if result['status_type'] != 'Reply to status request': - raise ValueError("Failed to modify settings") - - # read auto power on settings - printer.write(b"\x1b\x69\x61\x01") # Switch to raster command mode - # 0x1b 0x69 0x55 settings - # 0x70 auto power on - # 0x01 read - printer.write(b"\x1b\x69\x55\x70\x01") - data = printer.read() - result = interpret_response(data) - if result['status_type'] != 'Settings report': - raise ValueError - logger.info(f"Auto power on: {'Enabled' if result['setting'] else 'Disabled'}") - - if write: - # change auto power off settings - printer.write(b"\x1b\x69\x61\x01") # Switch to raster command mode - # 0x1b 0x69 0x55 settings - # 0x41 auto power off - # 0x00 write - # u8 multiples of 10 minutes, 0 means disabled - command = b"\x1b\x69\x55\x41\x00" + auto_power_off.to_bytes(1) - printer.write(command) - - # retrieve status to make sure no errors occured - printer.write(b"\x1b\x69\x53") # "ESC i S" Status information request - data = printer.read() - result = interpret_response(data) - if result['status_type'] != 'Reply to status request': - raise ValueError("Failed to modify settings") - - # read auto power off settings - printer.write(b"\x1b\x69\x61\x01") # Switch to raster command mode - # 0x1b 0x69 0x55 settings - # 0x41 auto power off - # 0x01 read - printer.write(b"\x1b\x69\x55\x41\x01") - data = printer.read() - result = interpret_response(data) - if result['status_type'] != 'Settings report': - raise ValueError - logger.info(f"Auto power off delay: {result['setting']*10} minutes") + if action not in ['get', 'set']: + raise ValueError(f"Invalid action '{action}'") + if key not in ['power-off-delay', 'auto-power-on']: + raise ValueError(f"Invalid key '{key}'") + if action == 'set': + if value is None: + raise ValueError(f"Specify a valid value for key '{key}'") + + series_code = get_status(printer, target_status=0x0)['series_code'] + + if action == 'set': + if key == 'auto-power-on': + payload = value.to_bytes(1) + write_setting(printer, 0x70, payload) + get_status(printer, 0x0) + elif key == 'power-off-delay': + payload = b'' + # 0x30 series needs an extra byte here + if series_code == 0x30: + payload += b"\x00" + payload += value.to_bytes(1) + write_setting(printer, 0x41, payload) + get_status(printer, 0x0) + else: + raise ValueError(f"Key {key} is invalid") + + # retrieve settings + retrieved_val = None + if key == 'auto-power-on': + retrieved_val = get_setting(printer, 0x70)['setting'] + elif key == 'power-off-delay': + payload = b'' + # 0x30 series needs an extra byte here + if series_code == 0x30: + payload += b"\x00" + retrieved_val = get_setting(printer, 0x41, payload)['setting'] + else: + raise ValueError(f"Key {key} is invalid") - return result + print(f"{key}: {retrieved_val}") diff --git a/brother_ql/cli.py b/brother_ql/cli.py index 2b5b609..42bbf30 100755 --- a/brother_ql/cli.py +++ b/brother_ql/cli.py @@ -49,16 +49,15 @@ def discover(ctx): if backend is None: logger.info("Defaulting to pyusb as backend for discovery.") backend = "pyusb" - from brother_ql.backends.helpers import discover, status + from brother_ql.backends.helpers import discover, get_printer, get_status available_devices = discover(backend_identifier=backend) for device in available_devices: - device_status = None - result = {"model": "unknown"} + status = None # skip network discovery since it's not supported if backend == "pyusb" or backend == "linux_kernel": - logger.info(f"Probing device at {device['identifier']}") + print(f"Probing device at {device['identifier']}") # check permissions before accessing lp* devices if backend == "linux_kernel": @@ -70,24 +69,12 @@ def discover(ctx): continue # send status request - device_status = status( + printer = get_printer( 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), - ) + status = get_status(printer) + print(f"Found a label printer at: {device['identifier']} ({status['model']})") def discover_and_list_available_devices(backend): from brother_ql.backends.helpers import discover @@ -210,33 +197,42 @@ def send_cmd(ctx, *args, **kwargs): @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 + from brother_ql.backends.helpers import get_status, get_printer - status( - printer_identifier=ctx.meta.get("PRINTER"), - backend_identifier=ctx.meta.get("BACKEND"), - ) + printer=get_printer(ctx.meta.get("PRINTER"), ctx.meta.get("BACKEND")) + logger.debug("Sending status information request to the printer.") + result = get_status(printer) + + print(f"Model: {result['model']}") + if result['model'] == "Unknown": + print("Unknown printer detected") + print(f"Series Code: 0x{result['series_code']:02x}") + print(f"Model Code: 0x{result['model_code']:02x}") + print(f"Status type: {result['status_type']}") + print(f"Phase: {result['phase_type']}") + if len(result['errors']) != 0: + print(f"Errors: {result['errors']}") + print(f"Media type: {result['media_type']}") + print(f"Media size: {result['media_width']} x {result['media_length']} mm") -@cli.command(name="configure", short_help="read and modify printer settings") -@click.option('-w', '--write', is_flag=True, default=False, help='Write settings') -@click.option('-on', '--auto-power-on', is_flag=True, show_default=True, default=False, help='Enable automatic power-on') -@click.option('-off', '--power-off-delay', type=click.IntRange(0, 6), default=6, help='Automatic power-off delay in multiples of 10 minutes. Use 0 to disable automatic power-off.') +@cli.command(name="configure", short_help="read and modify printer settings") +@click.argument('action', required=True, type=click.Choice(['get', 'set']), metavar='[ACTION]') +@click.argument('key', required=True, type=click.Choice(['power-off-delay', 'auto-power-on']), metavar='[KEY]') +@click.argument('value', type=int, metavar='[VALUE]', default=-1) @click.pass_context def configure_cmd(ctx, *args, **kwargs): from brother_ql.backends.helpers import configure - if ctx.meta['MODEL'] is None: - raise ValueError("You need to provide a printer model to change printer settings.") - elif not ctx.meta['MODEL'].startswith("QL"): - raise ValueError("Only QL series printers are supported at the moment.") - + if kwargs.get('action') == 'set' and kwargs.get('value') == -1: + raise ValueError(f"Specify a valid value for key {kwargs.get('key')}") + configure( printer_identifier=ctx.meta.get("PRINTER"), backend_identifier=ctx.meta.get("BACKEND"), - write=kwargs.get('write'), - auto_power_off=kwargs.get('power_off_delay'), - auto_power_on=kwargs.get('auto_power_on') + action=kwargs.get('action'), + key=kwargs.get('key'), + value=kwargs.get('value') ) if __name__ == '__main__': diff --git a/brother_ql/reader.py b/brother_ql/reader.py index 407c639..e2008a4 100755 --- a/brother_ql/reader.py +++ b/brother_ql/reader.py @@ -4,6 +4,7 @@ import io import logging import sys +from brother_ql.models import ModelsManager from PIL import Image from PIL.ImageOps import colorize @@ -66,11 +67,15 @@ RESP_MEDIA_TYPES = { 0x00: 'No media', - 0x01: 'Laminated tape', - 0x03: 'Non-laminated type', - 0x11: 'Heat-Shrink Tube', - 0x0A: 'Continuous length tape', - 0x0B: 'Die-cut labels', + 0x01: '[TZe] Laminated tape', + 0x03: '[TZe] Non-laminated type', + 0x11: '[TZe] Heat-Shrink Tube (HS 2:1)', + 0x17: '[TZe] Heat-Shrink Tube (HS 3:1)', + 0x0A: '[DK] Continuous length tape', + 0x0B: '[DK] Die-cut labels', + 0x4A: '[RD] Continuous length tape', + 0x4B: '[RD] Die-cut labels', + 0xFF: 'Incompatible tape', } RESP_STATUS_TYPES = { @@ -90,24 +95,24 @@ } RESP_BYTE_NAMES = [ - 'Print head mark', - 'Size', - 'Fixed (B=0x42)', - 'Device dependent', - 'Device dependent', - 'Fixed (0=0x30)', - 'Fixed (0x00 or 0=0x30)', - 'Fixed (0x00)', + 'Print head mark (0x80)', + 'Size (0x20)', + 'Brother code (B=0x42)', + 'Series code', + 'Model code', + 'Country code', + 'Power status', + 'Reserved', 'Error information 1', 'Error information 2', 'Media width', 'Media type', - 'Fixed (0x00)', - 'Fixed (0x00)', - 'Reserved', + 'Number of colors', + 'Media length (high)', + 'Media sensor value', 'Mode', - 'Fixed (0x00)', - 'Media length', + 'Density', + 'Media length (low)', 'Status type', 'Phase type', 'Phase number (high)', @@ -116,7 +121,12 @@ 'Expansion area', 'Tape color information', 'Text color information', - 'Hardware settings', + 'Hardware settings 1', + 'Hardware settings 2', + 'Hardware settings 3', + 'Hardware settings 4', + 'Requested setting', + 'Reserved', ] def hex_format(data): @@ -187,16 +197,16 @@ def interpret_response(data): media_width = data[10] media_length = data[17] - media_type = data[11] - if media_type in RESP_MEDIA_TYPES: - media_type = RESP_MEDIA_TYPES[media_type] + media_code = data[11] + if media_code in RESP_MEDIA_TYPES: + media_type = RESP_MEDIA_TYPES[media_code] logger.debug("Media type: %s", media_type) else: - logger.error("Unknown media type %02X", media_type) + logger.error("Unknown media type %02X", media_code) - status_type_code = data[18] - if status_type_code in RESP_STATUS_TYPES: - status_type = RESP_STATUS_TYPES[status_type_code] + status_code = data[18] + if status_code in RESP_STATUS_TYPES: + status_type = RESP_STATUS_TYPES[status_code] logger.debug("Status type: %s", status_type) else: logger.error("Unknown status type %02X", status_type) @@ -208,18 +218,28 @@ def interpret_response(data): else: logger.error("Unknown phase type %02X", phase_type) - setting = None # settings report - if status_type_code == 0xF0: + setting = None + if status_code == 0xF0: logger.debug("Settings report detected") setting = data[30] + # printer model detection + model = "Unknown" + for m in ModelsManager().iter_elements(): + if series_code == m.series_code and model_code == m.model_code: + model = m.identifier + break + response = { 'series_code': series_code, 'model_code': model_code, + 'model': model, 'status_type': status_type, + 'status_code': status_code, 'phase_type': phase_type, 'media_type': media_type, + 'media_code': media_code, 'media_width': media_width, 'media_length': media_length, 'setting': setting, From 70748606b64bf0a1727fb7380e76abc02ce47aab Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Mon, 5 Aug 2024 12:02:33 +0000 Subject: [PATCH 3/8] report TZe tape color info --- brother_ql/cli.py | 6 ++- brother_ql/reader.py | 89 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 85 insertions(+), 10 deletions(-) diff --git a/brother_ql/cli.py b/brother_ql/cli.py index 42bbf30..2c8f909 100755 --- a/brother_ql/cli.py +++ b/brother_ql/cli.py @@ -212,7 +212,11 @@ def status_cmd(ctx, *args, **kwargs): print(f"Phase: {result['phase_type']}") if len(result['errors']) != 0: print(f"Errors: {result['errors']}") - print(f"Media type: {result['media_type']}") + print(f"Media type: [{result['media_category']}] {result['media_type']}") + if result['media_category'] == 'TZe': + print("Note: tape color information may be incorrect for aftermarket tape cartridges.") + print(f"Tape color: {result['tape_color']}") + print(f"Text color: {result['text_color']}") print(f"Media size: {result['media_width']} x {result['media_length']} mm") diff --git a/brother_ql/reader.py b/brother_ql/reader.py index e2008a4..916ef53 100755 --- a/brother_ql/reader.py +++ b/brother_ql/reader.py @@ -67,17 +67,76 @@ RESP_MEDIA_TYPES = { 0x00: 'No media', - 0x01: '[TZe] Laminated tape', - 0x03: '[TZe] Non-laminated type', - 0x11: '[TZe] Heat-Shrink Tube (HS 2:1)', - 0x17: '[TZe] Heat-Shrink Tube (HS 3:1)', - 0x0A: '[DK] Continuous length tape', - 0x0B: '[DK] Die-cut labels', - 0x4A: '[RD] Continuous length tape', - 0x4B: '[RD] Die-cut labels', + 0x01: 'Laminated tape', + 0x03: 'Non-laminated type', + 0x11: 'Heat-Shrink Tube (HS 2:1)', + 0x17: 'Heat-Shrink Tube (HS 3:1)', + 0x0A: 'Continuous length tape', + 0x0B: 'Die-cut labels', + 0x4A: 'Continuous length tape', + 0x4B: 'Die-cut labels', 0xFF: 'Incompatible tape', } +RESP_MEDIA_CATEGORIES = { + 0x00: 'No media', + 0x01: 'TZe', + 0x03: 'TZe', + 0x11: 'TZe', + 0x17: 'TZe', + 0x0A: 'DK', + 0x0B: 'DK', + 0x4A: 'RD', + 0x4B: 'RD', + 0xFF: 'Incompatible', +} + +RESP_TAPE_COLORS = { + 0x01: 'White', + 0x02: 'Other', + 0x03: 'Clear', + 0x04: 'Red', + 0x05: 'Blue', + 0x06: 'Yellow', + 0x07: 'Green', + 0x08: 'Black', + 0x09: 'Clear(White text)', + 0x20: 'Matte White', + 0x21: 'Matte Clear', + 0x22: 'Matte Silver', + 0x23: 'Satin Gold', + 0x24: 'Satin Silver', + 0x30: 'Blue(D)', + 0x31: 'Red(D)', + 0x40: 'Fluorescent Orange', + 0x41: 'Fluorescent Yellow', + 0x50: 'Berry Pink(S)', + 0x51: 'Light Gray(S)', + 0x52: 'Lime Green(S)', + 0x60: 'Yellow(F)', + 0x61: 'Pink(F)', + 0x62: 'Blue(F)', + 0x70: 'White(Heat-shrink Tube)', + 0x90: 'White(Flex. ID)', + 0x91: 'Yellow(Flex. ID)', + 0xF0: 'Clearning', + 0xF1: 'Stencil', + 0xFF: 'Incompatible', +} + +RESP_TEXT_COLORS = { + 0x01: 'White', + 0x04: 'Red', + 0x05: 'Blue', + 0x08: 'Black', + 0x0A: 'Gold', + 0x62: 'Blue(F)', + 0xF0: 'Cleaning', + 0xF1: 'Stencil', + 0x02: 'Other', + 0xFF: 'Incompatible', +} + RESP_STATUS_TYPES = { 0x00: 'Reply to status request', 0x01: 'Printing completed', @@ -198,12 +257,22 @@ def interpret_response(data): media_length = data[17] media_code = data[11] + media_category = "" if media_code in RESP_MEDIA_TYPES: media_type = RESP_MEDIA_TYPES[media_code] + media_category = RESP_MEDIA_CATEGORIES[media_code] logger.debug("Media type: %s", media_type) else: logger.error("Unknown media type %02X", media_code) + tape_color_code = data[24] + text_color_code = data[25] + tape_color = '' + text_color = '' + if media_category == 'TZe': + tape_color = RESP_TAPE_COLORS[tape_color_code] + text_color = RESP_TEXT_COLORS[text_color_code] + status_code = data[18] if status_code in RESP_STATUS_TYPES: status_type = RESP_STATUS_TYPES[status_code] @@ -239,7 +308,9 @@ def interpret_response(data): 'status_code': status_code, 'phase_type': phase_type, 'media_type': media_type, - 'media_code': media_code, + 'media_category': media_category, + 'tape_color': tape_color, + 'text_color': text_color, 'media_width': media_width, 'media_length': media_length, 'setting': setting, From 9ca0a98635418c1cacdeba876da829fc750a4d0c Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 5 Aug 2024 21:22:40 +0200 Subject: [PATCH 4/8] add deprecation warning --- brother_ql/backends/helpers.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/brother_ql/backends/helpers.py b/brother_ql/backends/helpers.py index 17099cd..d920318 100755 --- a/brother_ql/backends/helpers.py +++ b/brother_ql/backends/helpers.py @@ -8,6 +8,7 @@ """ import logging, time +from warnings import warn from brother_ql.backends import backend_factory, guess_backend from brother_ql.reader import interpret_response @@ -158,6 +159,30 @@ def get_status( return result +def status( + printer_identifier=None, + backend_identifier=None, +): + """ + DEPRECATED + Retrieve status info from the printer, including model and currently loaded media size. + This function will be removed in a release after 2025-08-05. + + :param str printer_identifier: Identifier for the printer. + :param str backend_identifier: Can enforce the use of a specific backend. + """ + warn("The 'status' function is deprecated. Use 'get_status' instead.", DeprecationWarning, stacklevel=2) + + printer = get_printer(printer_identifier, backend_identifier) + result = get_status(printer) + 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") + def get_setting( printer, setting, From 087f7a0ae6bd1fef8d4e60f2213144c5fcd5ae73 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 5 Aug 2024 21:23:54 +0200 Subject: [PATCH 5/8] fix possible use before assign --- brother_ql/reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brother_ql/reader.py b/brother_ql/reader.py index 916ef53..f204959 100755 --- a/brother_ql/reader.py +++ b/brother_ql/reader.py @@ -278,7 +278,7 @@ def interpret_response(data): status_type = RESP_STATUS_TYPES[status_code] logger.debug("Status type: %s", status_type) else: - logger.error("Unknown status type %02X", status_type) + logger.error("Unknown status type %02X", status_code) phase_type = data[19] if phase_type in RESP_PHASE_TYPES: From 77135c816eee7e5aae67010c56b403fecc54bb01 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 5 Aug 2024 21:37:06 +0200 Subject: [PATCH 6/8] remove warning again --- brother_ql/backends/helpers.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/brother_ql/backends/helpers.py b/brother_ql/backends/helpers.py index d920318..17099cd 100755 --- a/brother_ql/backends/helpers.py +++ b/brother_ql/backends/helpers.py @@ -8,7 +8,6 @@ """ import logging, time -from warnings import warn from brother_ql.backends import backend_factory, guess_backend from brother_ql.reader import interpret_response @@ -159,30 +158,6 @@ def get_status( return result -def status( - printer_identifier=None, - backend_identifier=None, -): - """ - DEPRECATED - Retrieve status info from the printer, including model and currently loaded media size. - This function will be removed in a release after 2025-08-05. - - :param str printer_identifier: Identifier for the printer. - :param str backend_identifier: Can enforce the use of a specific backend. - """ - warn("The 'status' function is deprecated. Use 'get_status' instead.", DeprecationWarning, stacklevel=2) - - printer = get_printer(printer_identifier, backend_identifier) - result = get_status(printer) - 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") - def get_setting( printer, setting, From ebd29131cc0145882ef20b4c4b970d137d32fbc2 Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Mon, 5 Aug 2024 19:52:14 +0000 Subject: [PATCH 7/8] return settings value --- brother_ql/backends/helpers.py | 3 ++- brother_ql/cli.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/brother_ql/backends/helpers.py b/brother_ql/backends/helpers.py index 17099cd..6ee43e9 100755 --- a/brother_ql/backends/helpers.py +++ b/brother_ql/backends/helpers.py @@ -268,4 +268,5 @@ def configure( else: raise ValueError(f"Key {key} is invalid") - print(f"{key}: {retrieved_val}") + logger.info(f"{key}: {retrieved_val}") + return retrieved_val diff --git a/brother_ql/cli.py b/brother_ql/cli.py index 2c8f909..f737d01 100755 --- a/brother_ql/cli.py +++ b/brother_ql/cli.py @@ -57,7 +57,7 @@ def discover(ctx): # skip network discovery since it's not supported if backend == "pyusb" or backend == "linux_kernel": - print(f"Probing device at {device['identifier']}") + logger.info(f"Probing device at {device['identifier']}") # check permissions before accessing lp* devices if backend == "linux_kernel": From e5bb09c3f303b14d6d6677bd07481f3c2184e63d Mon Sep 17 00:00:00 2001 From: vulpes2 Date: Mon, 5 Aug 2024 22:31:01 +0000 Subject: [PATCH 8/8] document magic bytes --- brother_ql/backends/helpers.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/brother_ql/backends/helpers.py b/brother_ql/backends/helpers.py index 6ee43e9..cecfac0 100755 --- a/brother_ql/backends/helpers.py +++ b/brother_ql/backends/helpers.py @@ -176,6 +176,10 @@ def get_setting( # switch to raster command mode printer.write(b"\x1b\x69\x61\x01") # send command + # 0x1b 0x69 0x55 setting + # u8 setting + # 0x01 read + # optional extra payload command = b"\x1b\x69\x55" + setting.to_bytes(1) + b"\x01" if payload is not None: command += payload @@ -200,6 +204,10 @@ def write_setting( # switch to raster command mode printer.write(b"\x1b\x69\x61\x01") # write settings + # 0x1b 0x69 0x55 setting + # u8 setting + # 0x0 write + # payload (size dependent on setting and machine series) command = b"\x1b\x69\x55" + setting.to_bytes(1) + b"\x00" command += payload printer.write(command)