# HPCS 6500 — Open-Source Driver Python driver and CLI for the HPCS 6500 spectrophotometer / integrating sphere. Communicates directly with the instrument over USB serial, replacing the vendor software for measurement automation and data extraction. ## What This Does - **Measure**: Luminous flux (lm), CCT (K), CRI (Ra, R1-R15), chromaticity (CIE xy, uv, u'v'), radiometric flux, spectrum 380-1050 nm - **Electrical**: Voltage, current, power, power factor, frequency - **Harmonics**: 50-harmonic voltage/current analysis, UThd, AThd, waveforms - **Power supply**: Control the built-in AC (100-240V, 50/60Hz) and DC (1-60V, 0-5A) power supply - **Export**: CSV output for data logging - **TM-30**: ANSI/IES TM-30-18 Rf, Rg, and hue-bin data computed from the measured spectrum (via colour-science) - **Lamp bundles**: one-command export of spectrum + TM-30 + metrics for the [buildfor.life lamp comparison](https://buildfor.life/comparisons) ## Quick Start ```bash # Install uv sync # Single measurement (auto-detects device, turns PSU on/off) uv run hpcs6500.py # Continuous measurements uv run hpcs6500.py --continuous # Quick test (lumen + CCT only) uv run hpcs6500.py --quick uv run hpcs6500.py --quick --continuous # Show full spectrum + harmonics uv run hpcs6500.py --spectrum --harmonics # Save to CSV uv run hpcs6500.py --continuous --csv output.csv ``` ## Power Supply Control ```bash # Read current PSU settings uv run hpcs6500.py --psu-status # Set AC mode, 230V 50Hz uv run hpcs6500.py --mode ac --voltage 230 --frequency 50 # Set DC mode, 12V with 1A current limit uv run hpcs6500.py --mode dc --voltage 12 --current 1.0 # Manual PSU on/off uv run hpcs6500.py --psu-on uv run hpcs6500.py --psu-off # Set integration time (ms) uv run hpcs6500.py --integration 500 ``` ## Lamp Comparison Export Produces the per-lamp data bundle consumed by the buildfor.life comparison pages: `spd.csv` (full 380-1050 nm spectrum), `tm30.csv` (TM-30-18 hue-bin data for the color vector graphic), and `metrics.json` (photometric, colorimetric, CRI R1-R15, TM-30 Rf/Rg, electrical). ```bash # Single reading from the device uv run lamp_export.py --name philips-a60-8w --manufacturer Philips --model "A60 8W 927" # Average several readings uv run lamp_export.py --name philips-a60-8w --readings 5 # Power the lamp from the built-in supply: 230 V / 50 Hz, 60 s warm-up, # PSU switches on before the readings and off afterwards uv run lamp_export.py --name philips-a60-8w --voltage 230 --frequency 50 --settle 60 # From an existing pcap capture uv run lamp_export.py --name some-lamp --parse captures/run.pcap ``` Output lands in `lamps//`. TM-30 is computed from the measured spectrum with [colour-science](https://www.colour-science.org/); the spectrum is relative, which TM-30 is invariant to. Sanity check of the implementation: `uv run tm30.py` reproduces the published values for the CIE FL2 illuminant (Rf 70, Rg 86). ## Lamp Station (barcode workflow) Interactive loop around `lamp_export.py` for building the comparison database end to end. Every lamp gets an internal ID (L0001, L0002, ...) as primary key; the EAN barcode is optional metadata, so one barcode can cover daylight/warm white variants (distinct IDs + variant field) and unbranded lamps work too. Every run opens with a freshly allocated ID (the ID only becomes real once a bundle is written, so aborting never burns a number). Then: scan the box's EAN (optional, Enter skips it), or type an existing ID like L0004 to re-measure that lamp with everything pre-filled, which also rewrites its bundle in the current metrics format. Scanned EANs are checksum-validated and resolved to manufacturer/model (previously measured lamps, local cache, 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, you confirm the identification and type the ADVERTISED values from the packaging (flux, CCT, power, CRI, lifetime, equivalent W -> stored 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 settle, 5 readings averaged), writes lamps// into the comparison-data checkout, commits, and pushes. The website picks the lamp up automatically: 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 for photos, where-to-buy links, or notes. ```bash uv run lamp_station.py # scan -> measure -> commit -> push uv run lamp_station.py --no-push # commit locally only uv run lamp_station.py --no-commit # just write the bundle uv run lamp_station.py --settle 0 --readings 1 # quick smoke test ``` Defaults assume `C:/dev/buildfor_life_web` with the comparison-data submodule initialized; override with --data-repo. ## Offline Parsing Parse previously captured USB traffic (pcap files from USBPcap): ```bash uv run hpcs6500.py --parse captures/some_capture.pcap uv run hpcs6500.py --parse captures/some_capture.pcap --quick ``` ## Files | File | Description | |------------------|----------------------------------------------------| | `hpcs6500.py` | Driver class (`HPCS6500`) and CLI | | `lamp_export.py` | Lamp comparison bundle export (spd/tm30/metrics) | | `tm30.py` | ANSI/IES TM-30-18 computation from a spectrum | | `usb_capture.py` | USB traffic capture tool (requires USBPcap) | | `PROTOCOL.md` | Complete protocol reference (byte-level) | | `pyproject.toml` | Project metadata and dependencies | ## Hardware - **Device**: HPCS 6500 spectrophotometer / integrating sphere - **USB**: STM32 Virtual COM Port (VID `0483`, PID `5741`) - **Protocol**: Custom binary over serial, documented in [PROTOCOL.md](PROTOCOL.md) ## Dependencies - Python 3.11+ - `pyserial` (serial communication) - `colour-science` (TM-30 computation) - USBPcap (only for `usb_capture.py`, not needed for normal operation) ## Protocol The binary protocol is fully documented in [PROTOCOL.md](PROTOCOL.md), including all command bytes, data block layouts, field offsets, and the complete measurement sequence. This was reverse-engineered from USB packet captures of the vendor software.