Files
HPCS6500-py/label_printer.py
T
grabowski 3ac7cd243b Labels encode just the lamp ID
Internal inventory labels: the QR carries L#### only, which keeps it
at version 1 (large modules, reliable at 23 mm) and means scanning a
label at the station types the ID and lands directly in the
re-measure flow.
2026-07-09 18:16:13 +07:00

113 lines
4.0 KiB
Python

"""
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://<ip> 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://<ip> 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()