label_printer.py renders a DK-11221 23x23 mm label (QR code linking to the lamp's buildfor.life page plus the ID) and sends it over the network backend (brother-ql-next). The station prints two after each successful measurement, one for the box and one for the lamp; printer failures never lose a run, and any lamp can be reprinted with uv run label_printer.py <ID>. Printer address via HPCS_LABEL_PRINTER or --printer.
171 lines
6.6 KiB
Markdown
171 lines
6.6 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, then prints two ID labels (QR code linking to
|
|
the lamp's page plus the ID, DK-11221 23x23 mm) on a Brother QL-820NWB network
|
|
printer: one for the box, one for the lamp. Set HPCS_LABEL_PRINTER (e.g.
|
|
tcp://192.168.1.50) or pass --printer; reprint anytime with
|
|
`uv run label_printer.py <ID>`. 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.
|