Every run opens with the freshly allocated L#### and then asks for the EAN, which Enter skips. Typing an existing ID re-measures that lamp with all identification and rated values pre-filled, which also rewrites old bundles in the current metrics format. Unused IDs are never burned since numbering derives from written bundles.
167 lines
6.3 KiB
Markdown
167 lines
6.3 KiB
Markdown
# 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/<name>/`. 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/<ID>/ 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.
|