Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect printer model and media type #53

Merged
merged 11 commits into from
Aug 4, 2024
52 changes: 50 additions & 2 deletions brother_ql/backends/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
logger = logging.getLogger(__name__)

def discover(backend_identifier='linux_kernel'):

if backend_identifier is None:
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']
vulpes2 marked this conversation as resolved.
Show resolved Hide resolved

available_devices = list_available_devices()
return available_devices

Expand Down Expand Up @@ -101,3 +102,50 @@ 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
8 changes: 5 additions & 3 deletions brother_ql/backends/pyusb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)

Expand Down
60 changes: 58 additions & 2 deletions brother_ql/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -43,7 +46,48 @@ def cli(ctx, *args, **kwargs):
def discover(ctx):
""" find connected label printers """
backend = ctx.meta.get('BACKEND', 'pyusb')
discover_and_list_available_devices(backend)
if backend is None:
logger.info("Defaulting to pyusb as backend for discovery.")
backend = "pyusb"
from brother_ql.backends.helpers import discover, status

available_devices = discover(backend_identifier=backend)
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. You need to be a part of the lp group to access printers with this backend."
)
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),
)

def discover_and_list_available_devices(backend):
from brother_ql.backends.helpers import discover
Expand Down Expand Up @@ -120,7 +164,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.')
Expand Down Expand Up @@ -162,5 +206,17 @@ 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()
14 changes: 8 additions & 6 deletions brother_ql/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -182,14 +182,16 @@ 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:
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
2 changes: 1 addition & 1 deletion brother_ql/devicedependent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
Loading