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
Quick Start
# 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
# 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).
# 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/<name>/. TM-30 is computed from the measured spectrum
with colour-science; 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.
Per lamp: scan the box (or 'n' for no barcode), the EAN is 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. If the web repo is present it also scaffolds the device page stub with the rated specs pre-filled (review and commit that one manually).
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 / --web-repo.
Offline Parsing
Parse previously captured USB traffic (pcap files from USBPcap):
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, PID5741) - Protocol: Custom binary over serial, documented in 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, 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.