Skip to content

Commit

Permalink
Merge pull request #53 from vulpes2/status-query
Browse files Browse the repository at this point in the history
Detect printer model and media type
  • Loading branch information
matmair authored Aug 4, 2024
2 parents e99be69 + e929e77 commit 273629c
Show file tree
Hide file tree
Showing 8 changed files with 334 additions and 45 deletions.
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']

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

0 comments on commit 273629c

Please sign in to comment.