# Lamp testbench Measurement station and tools around the **HPCS 6500** spectrophotometer / integrating sphere, for the [buildfor.life lamp comparison](https://buildfor.life/comparisons) 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](https://git.b4l.co.th/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](https://git.b4l.co.th/B4L/mppt-testbench): the instrument driver is its own repository, pulled in as a submodule. ## Install ```bash 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 ```bash 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. 1. Every run opens with a fresh ID; it only becomes real once a bundle is written, so aborting never burns a number. 2. Scan the box's EAN (Enter skips it) or type an existing ID like `L0004` to 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. 3. Confirm the identification and type the **advertised** values from the packaging (flux, CCT, power, CRI, lifetime, equivalent W). They are stored under `rated` in metrics.json for claimed-vs-measured comparison. 4. 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). 5. `lamps//` 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. 6. 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.50` or 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 ```bash 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//` (`--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 ```bash 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 ```bash 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: ```bash 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 ```bash cargo test --workspace # everything, including the driver submodule cargo clippy --workspace --all-targets -- -D warnings ``` - `tests/golden/bundles/` are bundles written by the Python `lamp_export.py` from real readings (the driver's `tests/fixtures/`); `bundle.rs` must reproduce them byte for byte. - `tests/station.rs` runs 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/tests` compare 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`.