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:
@@ -110,7 +110,11 @@ from the packaging (flux, CCT, power, CRI, lifetime, equivalent W -> stored
|
|||||||
under "rated" in metrics.json for claimed-vs-measured comparison), insert the
|
under "rated" in metrics.json for claimed-vs-measured comparison), insert the
|
||||||
lamp, and the tool measures per the published procedure (230 V / 50 Hz, 60 s
|
lamp, and the tool measures per the published procedure (230 V / 50 Hz, 60 s
|
||||||
settle, 5 readings averaged), writes lamps/<ID>/ into the comparison-data
|
settle, 5 readings averaged), writes lamps/<ID>/ into the comparison-data
|
||||||
checkout, commits, and pushes. The website picks the lamp up automatically:
|
checkout, commits, and pushes, then prints two ID labels (QR code linking to
|
||||||
|
the lamp's page plus the ID, DK-11221 23x23 mm) on a Brother QL-820NWB network
|
||||||
|
printer: one for the box, one for the lamp. Set HPCS_LABEL_PRINTER (e.g.
|
||||||
|
tcp://192.168.1.50) or pass --printer; reprint anytime with
|
||||||
|
`uv run label_printer.py <ID>`. The website picks the lamp up automatically:
|
||||||
its device list is data-driven from the comparison-data bundles and the push
|
its device list is data-driven from the comparison-data bundles and the push
|
||||||
triggers the auto-bump deploy. An .md page in the web repo is only ever needed
|
triggers the auto-bump deploy. An .md page in the web repo is only ever needed
|
||||||
for photos, where-to-buy links, or notes.
|
for photos, where-to-buy links, or notes.
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -31,6 +31,10 @@ supports unbranded lamps without any barcode.
|
|||||||
website picks it up automatically (data-driven device list plus the
|
website picks it up automatically (data-driven device list plus the
|
||||||
auto-bump deploy); an .md page in the web repo is only ever needed
|
auto-bump deploy); an .md page in the web repo is only ever needed
|
||||||
for photos, where-to-buy links, or notes.
|
for photos, where-to-buy links, or notes.
|
||||||
|
6. Two ID labels (QR to the lamp's page + the ID, DK-11221 23x23 mm)
|
||||||
|
print on the Brother QL-820NWB: one for the box, one for the lamp.
|
||||||
|
Configure the printer via HPCS_LABEL_PRINTER or --printer; reprint
|
||||||
|
any lamp later with `uv run label_printer.py L0006`.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
uv run lamp_station.py # defaults below
|
uv run lamp_station.py # defaults below
|
||||||
@@ -40,6 +44,7 @@ Usage:
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -304,6 +309,20 @@ def measure_one(new_id, ean, lamps, args, data_repo, existing_id=None):
|
|||||||
print(" The lamp publishes automatically (auto-bump deploy); add an .md under")
|
print(" The lamp publishes automatically (auto-bump deploy); add an .md under")
|
||||||
print(" src/content/comparisons/lamps/ only for photos, buy links, or notes.")
|
print(" src/content/comparisons/lamps/ only for photos, buy links, or notes.")
|
||||||
|
|
||||||
|
# Two ID labels on the QL-820NWB: one for the box, one for the lamp.
|
||||||
|
if not args.no_labels:
|
||||||
|
if args.printer:
|
||||||
|
try:
|
||||||
|
from label_printer import print_labels
|
||||||
|
print_labels(lamp_id, args.printer, copies=2)
|
||||||
|
print(f" Printed 2 ID labels for {lamp_id}.")
|
||||||
|
except Exception as e: # noqa: BLE001 — a printer problem must not lose the run
|
||||||
|
print(f" Label printing failed ({e}); reprint later with:")
|
||||||
|
print(f" uv run label_printer.py {lamp_id}")
|
||||||
|
else:
|
||||||
|
print(f" (no label printer configured; set HPCS_LABEL_PRINTER or use --printer,")
|
||||||
|
print(f" reprint later with: uv run label_printer.py {lamp_id})")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Barcode-driven lamp measurement station")
|
parser = argparse.ArgumentParser(description="Barcode-driven lamp measurement station")
|
||||||
@@ -320,6 +339,11 @@ def main():
|
|||||||
help="Write the bundle but skip git entirely")
|
help="Write the bundle but skip git entirely")
|
||||||
parser.add_argument("--no-push", action="store_true",
|
parser.add_argument("--no-push", action="store_true",
|
||||||
help="Commit locally but do not push")
|
help="Commit locally but do not push")
|
||||||
|
parser.add_argument("--printer", default=os.environ.get("HPCS_LABEL_PRINTER", ""),
|
||||||
|
help="QL-820NWB address, e.g. tcp://192.168.1.50 "
|
||||||
|
"(default: HPCS_LABEL_PRINTER env)")
|
||||||
|
parser.add_argument("--no-labels", action="store_true",
|
||||||
|
help="Skip printing the two ID labels after each lamp")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
data_repo = Path(args.data_repo)
|
data_repo = Path(args.data_repo)
|
||||||
|
|||||||
@@ -7,4 +7,7 @@ requires-python = ">=3.11"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"pyserial>=3.5",
|
"pyserial>=3.5",
|
||||||
"colour-science>=0.4.4",
|
"colour-science>=0.4.4",
|
||||||
|
"qrcode>=8.2",
|
||||||
|
"pillow>=12.3.0",
|
||||||
|
"brother-ql-next>=0.12.0",
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user