Lamp testbench
Measurement station and tools around the HPCS 6500 spectrophotometer /
integrating sphere, for the buildfor.life lamp comparison
and for calibrating B4L's tunable-white LED light. Everything is Rust; one
binary, lampbench.
| Part | Role | Where |
|---|---|---|
| HPCS 6500 | spectrum, photometry, CRI, electrical, built-in AC/DC supply | driver: submodule HPCS6500/ (B4L/HPCS6500) |
| Brother QL-820NWB | lamp ID labels, DK-11221 23x23 mm | crates/brother-ql, crates/lamp-label |
| TM-30-18 | Rf, Rg and the 16 hue bins from the spectrum | crates/tm30 |
| comparison-data | the published lamp bundles | --data-repo (git checkout) |
Same layout as mppt-testbench: the instrument driver is its own repository, pulled in as a submodule.
Install
git clone --recurse-submodules https://git.b4l.co.th/B4L/lamp-testbench.git
cd lamp-testbench
cargo build --release # target/release/lampbench(.exe)
cargo install --path . # or put lampbench on the PATH
The instrument is found by its USB IDs (STM32 virtual COM port, VID 0483,
PID 5741); --port COM42 picks one by hand. hpcs6500 (the driver's CLI,
cargo install --path HPCS6500) reads and controls the instrument directly.
Lamp station
lampbench station # scan -> measure -> commit -> push -> labels
lampbench station --no-push # commit locally only
lampbench station --no-commit # just write the bundle
lampbench station --settle 0 --readings 1 # quick smoke test
lampbench station --simulate --no-commit --data-repo D:/scratch-data # no instrument needed
Every lamp gets an internal ID (L0001, L0002, ...) as its primary key;
the EAN is optional metadata, so one barcode can cover daylight and warm
white variants, and unbranded lamps work too.
- Every run opens with a fresh ID; it only becomes real once a bundle is written, so aborting never burns a number.
- Scan the box's EAN (Enter skips it) or type an existing ID like
L0004to re-measure that lamp with everything pre-filled (this also rewrites its bundle in the current format). EANs are checksum-validated and resolved to manufacturer / model from previous lamps, the local cache (~/.cache/hpcs6500/ean-cache.json), then upcitemdb.com; GS1-restricted 20-29 barcodes are flagged as retailer-internal. Lamps already measured under the same EAN are offered for re-measurement. - Confirm the identification and type the advertised values from the
packaging (flux, CCT, power, CRI, lifetime, equivalent W). They are
stored under
ratedin metrics.json for claimed-vs-measured comparison. - Insert the lamp and press Enter. The procedure: 230 V / 50 Hz from the built-in supply, 60 s settle, 5 readings averaged (vendor single-test cycle per reading).
lamps/<ID>/is written into the comparison-data checkout, committed and pushed; the website picks it up automatically (data-driven device list, auto-bump deploy). An .md page in the web repo is only needed for photos, buy links or notes.- Two ID labels (QR code of the ID plus the ID, DK-11221) print on the
Brother QL-820NWB: one for the box, one for the lamp. Scanning a label at
the station types the ID and lands in the re-measure flow. Set
HPCS_LABEL_PRINTER=tcp://192.168.1.50or pass--printer.
Ctrl+C during a measurement switches the supply off and skips the lamp;
at the scan prompt, q or Ctrl+C quits.
Defaults assume C:\dev\buildfor_life_web\apps\web\comparison-data with the
comparison-data submodule initialized; override with --data-repo.
Lamp bundle export
lampbench export --name philips-a60-8w --manufacturer Philips --model "A60 8W 927"
lampbench export --name philips-a60-8w --readings 5
lampbench export --name philips-a60-8w --voltage 230 --frequency 50 --settle 60 # power from the built-in supply
lampbench export --name some-lamp --parse captures/run.pcap # from a USB capture
lampbench export --name x --passive # vendor software drives the instrument
Output lands in lamps/<name>/ (--out):
| File | |
|---|---|
spd.csv |
wavelength_nm,value, the full 380-1050 nm spectrum |
tm30.csv |
TM-30-18 per hue bin: reference/test a' b' (raw and normalized), Rcs, Rhs, Rf,h |
metrics.json |
name, EAN, type, rated values, TM-30 Rf/Rg, CRI Ra + R1-R15, supply, photometric, chromaticity, radiometric, electrical, sensor |
The files are byte for byte what the former Python lamp_export.py wrote
(tested against its output), so re-measured lamps only change where the
measurement changed.
Labels
lampbench label L0006 # 2 labels on $HPCS_LABEL_PRINTER
lampbench label L0006 --copies 1 --printer tcp://192.168.1.50
lampbench label L0006 --preview l6.png # render only
TM-30
lampbench tm30 lamps/L0011/spd.csv # Rf 83.2 Rg 109.3 CCT 4008 K Duv -0.0370
lampbench tm30 lamps/L0011/spd.csv --bins # the tm30.csv rows
crates/tm30 implements ANSI/IES TM-30-18 (CIE 2017 colour fidelity, 99 CES,
CAM02-UCS, Ohno 2013 CCT) in plain Rust. It reproduces colour-science 0.4.7,
which the Python tools used, on 52 golden cases (the published lamps, CIE
illuminants, blackbodies, odd grids): Rf and Rg agree to 1e-10, every
output to 1e-9 relative or 1e-11 absolute. The spectrum is relative; TM-30
is invariant to its scale.
Known issue: the spectrum's wavelength axis. The CCT/Duv that TM-30 derives from the spectrum (4008 K / -0.037 for L0011) disagree with the instrument's own values (4049 K / +0.0026), because the spectrum's chromaticity on the assumed linear 380-1050 nm axis is 0.09-0.13 (xy) off the instrument's x, y. The published Rf/Rg inherit this. Details and how to confirm the axis:
HPCS6500/PROTOCOL.md, "Spectral Data".
LED light calibration
Calibrates the mixing table of the B4L tunable-white light (2700 K + 5000 K LEDs) over its UART, with the HPCS 6500 as the meter:
lampbench calibrate COM3 # y-data, then up to 10 mixing-ratio passes until all 45 points are within 5 %
lampbench calibrate COM3 --skip-y # mixing ratios only
lampbench calibrate COM3 --y-only # LED curves only
lampbench calibrate COM3 --read # show the table in the light
lampbench calibrate COM3 --clear # back to firmware defaults
lampbench calibrate COM3 --generate-only # table from calibration_table.json
lampbench calibrate COM3 --write # ... and write it to the light
Progress and results go to calibration_table.json (--output), in the
format of the former calibration.py.
Development
cargo test --workspace # everything, including the driver submodule
cargo clippy --workspace --all-targets -- -D warnings
tests/golden/bundles/are bundles written by the Pythonlamp_export.pyfrom real readings (the driver'stests/fixtures/);bundle.rsmust reproduce them byte for byte.tests/station.rsruns the whole station with scripted answers against the simulated instrument and a throwaway comparison-data repository.- The calibration is tested against a simulated light whose CCT follows its mixing ratio.
crates/tm30/tests,crates/brother-ql/tests,crates/lamp-label/testscompare against the Python libraries (colour-science, brother_ql, qrcode);tools/in each crate regenerates the golden data.
The Python tools these replace are in HPCS6500 at tag python-final.