Lamp testbench in Rust around the HPCS 6500
lampbench station / export / label / tm30 / calibrate replace the Python lamp_station.py, lamp_export.py, label_printer.py, tm30.py and calibration.py that lived in HPCS6500-py. The driver is a submodule (HPCS6500/, like mppt-testbench). Workspace crates: tm30 (TM-30-18 port of colour-science 0.4.7), brother-ql (raster protocol, byte-identical to brother_ql) and lamp-label (QR + ID labels for the QL-820NWB). Bundles are byte-identical to lamp_export.py's (golden tests); the station runs end to end in a test against the simulated instrument.
This commit is contained in:
commit
2a3f28001c
241 files changed
+46390
No files matched your search
@@ -0,0 +1,162 @@
|
||||
# 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-py](https://git.b4l.co.th/B4L/HPCS6500-py)) |
|
||||
| **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/<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.
|
||||
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/<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
|
||||
|
||||
```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-py at tag `python-final`.
|
||||
Reference in new issue
Block a user