Print lamp ID labels on the Brother QL-820NWB

label_printer.py renders a DK-11221 23x23 mm label (QR code linking
to the lamp's buildfor.life page plus the ID) and sends it over the
network backend (brother-ql-next). The station prints two after each
successful measurement, one for the box and one for the lamp; printer
failures never lose a run, and any lamp can be reprinted with
uv run label_printer.py <ID>. Printer address via HPCS_LABEL_PRINTER
or --printer.
This commit is contained in:
2026-07-09 18:15:01 +07:00
parent 7ef2960254
commit 719ddcf04a
4 changed files with 141 additions and 1 deletions
+109
View File
@@ -0,0 +1,109 @@
"""
Lamp ID labels on the Brother QL-820NWB (DK-11221, 23x23 mm die-cut).
Each label carries a QR code linking to the lamp's page on buildfor.life and
the lamp ID as text. 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"
URL_BASE = "https://buildfor.life/comparisons/lamps/"
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: QR to the lamp page on top, ID text below."""
image = Image.new("1", DOTS, 1)
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(f"{URL_BASE}{lamp_id.lower()}/")
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()