# HPCS 6500 — open-source driver Rust driver and command line tool for the HPCS 6500 spectrophotometer / integrating sphere. It talks to the instrument directly over its USB serial port and replaces the vendor software for measurement automation and data extraction. This repository is only the driver. The lamp measurement station, the lamp comparison export (TM-30, label printing) and the LED light calibration are in the testbench repository, [B4L/lamp-testbench](https://git.b4l.co.th/B4L/lamp-testbench), which pulls this repository in as a submodule (the same layout as [mppt-testbench](https://git.b4l.co.th/B4L/mppt-testbench)). ## What it reads - **Photometric**: luminous flux (lm), efficacy (lm/W), CCT (K), Duv - **Colour**: CRI Ra and R1-R15, SDCM, TLCI-2012, CIE xy / uv / u'v', CIE 1931 XYZ - **Radiometric**: total, UV, blue, yellow, red, far-red and IR flux (mW) - **Spectrum**: 350 points, 380-1050 nm - **Electrical**: voltage, current, power, power factor, frequency - **Harmonics**: 50 voltage and current harmonics, UThd, AThd, one cycle of both waveforms - **Power supply**: the built-in AC (100-240 V, 50/60 Hz) and DC (1-60 V, 0-5 A) supply ## Install ```bash cargo install --git https://git.b4l.co.th/B4L/HPCS6500 # puts `hpcs6500` on the PATH # or, in a checkout: cargo build --release # target/release/hpcs6500(.exe) ``` Windows needs no driver: the instrument is an STM32 virtual COM port (VID `0483`, PID `5741`) and is found automatically. Linux: add yourself to the `dialout` group. ## Command line The flags are the ones of the former Python tool: `uv run hpcs6500.py ...` is now `hpcs6500 ...`. ```bash hpcs6500 # single reading (vendor single-test flow) hpcs6500 --continuous # continuous readings, PSU on while measuring hpcs6500 --continuous --count 10 --csv out.csv hpcs6500 --quick --continuous # lumen + CCT only hpcs6500 --spectrum --harmonics # full spectrum and harmonics hpcs6500 --json # one JSON object per reading on stdout hpcs6500 --passive --continuous # read only, while the vendor software drives the instrument hpcs6500 --port COM42 # pick the port hpcs6500 ports # list serial ports, the instrument is marked # power supply hpcs6500 --psu-status hpcs6500 --mode ac --voltage 230 --frequency 50 hpcs6500 --mode dc --voltage 12 --current 1.0 hpcs6500 --psu-on / hpcs6500 --psu-off hpcs6500 --integration 500 # ms, 0 = auto ``` Ctrl+C stops a continuous run cleanly: the supply is switched off and the instrument reset. The text output and the CSV files are character for character those of the Python tool, so scripts that read them keep working. ### Without the instrument `--simulate` talks to a simulated instrument that answers every command of the protocol: with a capture it replays the readings recorded in it, without one it uses a built-in sample reading. ```bash hpcs6500 --simulate # built-in sample hpcs6500 --simulate captures/run.pcap --continuous # replay a capture ``` ### USB captures (reverse engineering) ```bash hpcs6500 --parse captures/run.pcap [--quick] [--spectrum] [--json] # readings in a capture hpcs6500 dump captures/run.pcap # traffic timeline + _TX.bin / _RX.bin hpcs6500 capture # record the vendor software with USBPcap (admin, Windows) ``` `capture` starts `USBPcapCMD` on every root hub, waits for Ctrl+C, keeps the largest file and dumps it. Captures go to `captures/` (not in git). ## Library ```toml [dependencies] hpcs6500 = { git = "https://git.b4l.co.th/B4L/HPCS6500", default-features = false } ``` `default-features = false` leaves out the command line tool's dependencies. ```rust use hpcs6500::{Hpcs6500, Mode}; let port = hpcs6500::find_port().ok_or(hpcs6500::Error::NotFound)?; let mut dev = Hpcs6500::open(&port)?; dev.identify()?; // "HPCS6500" dev.set_mode(Mode::Ac)?; dev.set_ac_voltage(230.0)?; dev.set_ac_frequency(50.0)?; dev.psu_on()?; dev.send_test_config(false)?; // the vendor's single-test configuration let r = dev.take_single_reading()?; // trigger, poll, read both blocks, reset dev.psu_off()?; println!("{:.0} lm {:.0} K Ra {:.1}", r.flux_lm(), r.cct_k(), r.ra()); println!("{:?}", r.get("Phi_eb_mW")); // every field by its CSV name let nm = r.spectrum_nm(); // with r.spectrum ``` | Item | | |---|---| | `Hpcs6500::open(port)` / `with_transport(t, name)` | the instrument over the serial port or any `Transport` | | `identify`, `poll_state`, `wait_for_data` | | | `take_single_reading`, `take_reading(psu)`, `read_current` | single test, one continuous reading, passive read | | `start_measurement`, `stop_measurement`, `reset`, `send_test_config` | the raw sequence steps | | `psu_on`, `psu_off`, `set_mode`, `set_ac_voltage`, `set_ac_frequency`, `set_dc_voltage`, `set_dc_current`, `read_psu_settings` | built-in supply | | `set_integration_time(ms)` | 0 = auto | | `read_measurement_block`, `read_electrical_block` | raw 3904 / 1584-byte blocks | | `set_cancel_flag(flag)`, `pause(d)` | long waits end with `Error::Cancelled` when the flag goes up (Ctrl+C) | | `Reading` | `values` by `FIELDS`, `get(name)`, `scalars()`, `spectrum`, `spectrum_nm()`, `electrical` with `harmonics`; serializes to the Python driver's JSON keys | | `pcap::read_messages`, `pcap::readings`, `pcap::blocks` | USBPcap captures | | `sim::Simulator` | simulated instrument (`sample()`, `from_pcap(path)`), logs every frame | | `format::*` | the text report and the CSV export | | `pyfmt::*` | Python-compatible number formatting (`repr`, `.6g`, `.2f`, `round`) | Every method returns `Result`; errors say what was expected and what came back (`no acknowledge for command 0x72 (got nothing)`). ## Protocol The binary protocol is documented byte by byte in [PROTOCOL.md](PROTOCOL.md): command bytes, block layouts, field offsets and the measurement sequences. It was reverse-engineered from USB captures of the vendor software. ## Development ```bash cargo test # protocol, simulator, golden tests cargo clippy --all-targets -- -D warnings ``` `tests/golden/` holds the output of the former Python driver for real readings (`tests/fixtures/`): the parsed values (bit-exact), the text report, the CSV row and 2,000 number-formatting cases. The Rust driver reproduces all of them exactly. Where `captures/` is present, the `--parse` reports of real captures are compared too. The Python driver this replaces is at tag `python-final`.