""" Lamp ID labels on the Brother QL-820NWB (DK-11221, 23x23 mm die-cut). Each label carries the lamp ID as a QR code and as text. These are internal inventory labels: scanning one with the station's barcode scanner types the ID, which drops straight into the re-measure flow. The station prints two per lamp: one for the box, one for the lamp itself. Printer address comes from --printer or the HPCS_LABEL_PRINTER environment variable, e.g. tcp://192.168.1.50 (the QL-820NWB listens on port 9100). Usage: uv run label_printer.py L0006 # print 2 labels uv run label_printer.py L0006 --copies 1 uv run label_printer.py L0006 --preview out.png # render only, no printer """ import argparse import os import sys import qrcode from PIL import Image, ImageDraw, ImageFont LABEL_ID = "23x23" # DK-11221 DOTS = (202, 202) # printable area of the 23x23 label MODEL = "QL-820NWB" TEXT_HEIGHT = 52 # bottom strip for the ID text def _font(size): for name in ("arialbd.ttf", "arial.ttf", "DejaVuSans-Bold.ttf"): try: return ImageFont.truetype(name, size) except OSError: continue return ImageFont.load_default(size) def render_label(lamp_id: str) -> Image.Image: """202x202 1-bit label: the bare lamp ID as QR on top, as text below.""" image = Image.new("1", DOTS, 1) # Just the ID (internal use): a 5-character QR stays at version 1, so the # modules print large and scan reliably at 23 mm. Scanning it at the # station types the ID and lands in the re-measure flow. qr = qrcode.QRCode( error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=1, border=2, # quiet zone; tight but fine for close-range scanning ) qr.add_data(lamp_id.upper()) qr.make(fit=True) modules = len(qr.get_matrix()) qr_size = DOTS[1] - TEXT_HEIGHT qr.box_size = max(1, qr_size // modules) qr_img = qr.make_image().get_image().convert("1") image.paste(qr_img, ((DOTS[0] - qr_img.width) // 2, (qr_size - qr_img.height) // 2)) draw = ImageDraw.Draw(image) size = 44 while size > 10: font = _font(size) box = draw.textbbox((0, 0), lamp_id, font=font) if box[2] - box[0] <= DOTS[0] - 8: break size -= 2 draw.text( ((DOTS[0] - (box[2] - box[0])) // 2 - box[0], DOTS[1] - TEXT_HEIGHT + (TEXT_HEIGHT - (box[3] - box[1])) // 2 - box[1]), lamp_id, font=font, fill=0, ) return image def print_labels(lamp_id: str, printer: str, copies: int = 2) -> None: """Render and send `copies` labels to the QL-820NWB.""" from brother_ql.backends.helpers import send from brother_ql.conversion import convert from brother_ql.raster import BrotherQLRaster image = render_label(lamp_id) raster = BrotherQLRaster(MODEL) instructions = convert(qlr=raster, images=[image] * copies, label=LABEL_ID, cut=True) send(instructions=instructions, printer_identifier=printer, backend_identifier="network", blocking=True) def main(): parser = argparse.ArgumentParser(description="Print lamp ID labels (QR + ID)") parser.add_argument("lamp_id", help="e.g. L0006") parser.add_argument("--printer", default=os.environ.get("HPCS_LABEL_PRINTER", ""), help="tcp:// of the QL-820NWB (default: HPCS_LABEL_PRINTER env)") parser.add_argument("--copies", type=int, default=2) parser.add_argument("--preview", metavar="PNG", help="Write the rendered label to a PNG instead of printing") args = parser.parse_args() lamp_id = args.lamp_id.upper() if args.preview: render_label(lamp_id).save(args.preview) print(f"Wrote {args.preview}") return if not args.printer: print("ERROR: no printer configured. Pass --printer tcp:// or set HPCS_LABEL_PRINTER.") sys.exit(1) print_labels(lamp_id, args.printer, args.copies) print(f"Printed {args.copies} label(s) for {lamp_id} on {args.printer}") if __name__ == "__main__": main()