The repository is now only the HPCS 6500 driver: a Rust library (serial transport, every protocol command, block parsing, USBPcap captures, a simulated instrument) and the hpcs6500 CLI with the flags of the former hpcs6500.py, plus dump/capture for reverse engineering. The lamp station, lamp export, TM-30, label printer and LED calibration move to B4L/lamp-testbench. Golden tests pin the Rust output to the Python driver's: parsed values bit-exact, report text, CSV and number formatting. The Python driver is at tag python-final.
144 lines
4.6 KiB
Rust
144 lines
4.6 KiB
Rust
//! The driver against the simulated instrument: command bytes, measurement
|
|
//! sequences, supply handling, timeouts and cancellation.
|
|
|
|
use hpcs6500::protocol::{self, cmd};
|
|
use hpcs6500::sim::Simulator;
|
|
use hpcs6500::{Error, Hpcs6500, Mode};
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::time::{Duration, Instant};
|
|
|
|
fn dev() -> Hpcs6500<Simulator> {
|
|
Hpcs6500::with_transport(Simulator::sample(), "sim")
|
|
}
|
|
|
|
fn cmds(d: &Hpcs6500<Simulator>) -> Vec<u8> {
|
|
d.transport().log.iter().map(|f| f[1]).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn identify_and_supply_commands() {
|
|
let mut d = dev();
|
|
assert_eq!(d.identify().unwrap(), "HPCS6500");
|
|
d.set_mode(Mode::Dc).unwrap();
|
|
d.set_dc_voltage(12.0).unwrap();
|
|
d.set_dc_current(1.5).unwrap();
|
|
d.set_ac_voltage(230.0).unwrap();
|
|
d.set_ac_frequency(60.0).unwrap();
|
|
d.set_integration_time(500.0).unwrap();
|
|
let s = d.read_psu_settings().unwrap();
|
|
assert_eq!(
|
|
(
|
|
s.mode,
|
|
s.dc_voltage,
|
|
s.dc_current,
|
|
s.ac_voltage,
|
|
s.ac_frequency
|
|
),
|
|
(Mode::Dc, 12.0, 1.5, 230.0, 60.0)
|
|
);
|
|
let log = &d.transport().log;
|
|
assert_eq!(log[1], vec![0x8C, 0x7A, 0x01]);
|
|
assert_eq!(log[2], protocol::frame_f32(cmd::SET_DC, 1, 12.0));
|
|
assert_eq!(log[3], protocol::frame_f32(cmd::SET_DC, 0, 1.5));
|
|
assert_eq!(log[6], vec![0x8C, 0x01, 0x20, 0xA1, 0x07, 0x00]); // 500 000 us
|
|
d.psu_on().unwrap();
|
|
assert!(d.transport().output_on);
|
|
d.psu_off().unwrap();
|
|
assert!(!d.transport().output_on);
|
|
}
|
|
|
|
#[test]
|
|
fn single_reading_follows_the_vendor_sequence() {
|
|
let mut d = dev();
|
|
d.send_test_config(false).unwrap();
|
|
let r = d.take_single_reading().unwrap();
|
|
assert_eq!(r.device, "HPCS6500");
|
|
assert!((r.flux_lm() - 483.7375).abs() < 1e-3);
|
|
assert!(r.electrical.is_some());
|
|
// 2B 2B, trigger (0E 02), polls, 13, 77, reset
|
|
let c = cmds(&d);
|
|
assert_eq!(&c[..3], &[cmd::TEST_CONFIG, cmd::TEST_CONFIG, cmd::START]);
|
|
assert_eq!(d.transport().log[2], vec![0x8C, 0x0E, 0x02]);
|
|
assert!(c[3..c.len() - 3].iter().all(|&x| x == cmd::POLL_STATE));
|
|
assert_eq!(
|
|
&c[c.len() - 3..],
|
|
&[cmd::READ_MEASUREMENT, cmd::READ_ELECTRICAL, cmd::RESET]
|
|
);
|
|
assert!(!d.transport().output_on, "the supply is not touched");
|
|
}
|
|
|
|
#[test]
|
|
fn continuous_reading_switches_the_supply_around_it() {
|
|
let mut d = dev();
|
|
let r = d.take_reading(true).unwrap();
|
|
assert_eq!(r.spectrum.len(), 350);
|
|
let c = cmds(&d);
|
|
assert_eq!(c[0], cmd::PSU_OUTPUT);
|
|
assert_eq!(d.transport().log[0], vec![0x8C, 0x72, 0x00]);
|
|
assert_eq!(d.transport().log[1], vec![0x8C, 0x0E, 0x01]);
|
|
assert_eq!(
|
|
&c[c.len() - 4..],
|
|
&[
|
|
cmd::READ_MEASUREMENT,
|
|
cmd::READ_ELECTRICAL,
|
|
cmd::START,
|
|
cmd::PSU_OUTPUT
|
|
]
|
|
);
|
|
assert_eq!(d.transport().log.last().unwrap(), &vec![0x8C, 0x72, 0x01]);
|
|
assert!(!d.transport().output_on);
|
|
}
|
|
|
|
#[test]
|
|
fn silent_instrument_times_out_and_the_supply_still_goes_off() {
|
|
let mut d = dev();
|
|
d.transport_mut().mute = vec![cmd::POLL_STATE];
|
|
let mut sim = Simulator::sample();
|
|
sim.mute = vec![cmd::IDENTIFY];
|
|
let mut quiet = Hpcs6500::with_transport(sim, "sim");
|
|
let t = Instant::now();
|
|
assert!(matches!(
|
|
quiet.identify(),
|
|
Err(Error::BadResponse { cmd: 0, .. })
|
|
));
|
|
assert!(t.elapsed() >= Duration::from_secs(2) && t.elapsed() < Duration::from_secs(4));
|
|
|
|
// a reading whose data never arrives: cancel it instead of waiting 30 s
|
|
let stop = Arc::new(AtomicBool::new(false));
|
|
d.set_cancel_flag(stop.clone());
|
|
let s2 = stop.clone();
|
|
std::thread::spawn(move || {
|
|
std::thread::sleep(Duration::from_millis(300));
|
|
s2.store(true, Ordering::SeqCst);
|
|
});
|
|
let t = Instant::now();
|
|
let r = d.take_reading(true);
|
|
assert!(matches!(r, Err(Error::Cancelled)), "{r:?}");
|
|
assert!(t.elapsed() < Duration::from_secs(8));
|
|
assert!(
|
|
!d.transport().output_on,
|
|
"supply off after the failed reading"
|
|
);
|
|
assert_eq!(d.transport().log.last().unwrap(), &vec![0x8C, 0x72, 0x01]);
|
|
}
|
|
|
|
#[test]
|
|
fn missing_ack_is_an_error() {
|
|
let mut sim = Simulator::sample();
|
|
sim.mute = vec![cmd::PSU_OUTPUT];
|
|
let mut d = Hpcs6500::with_transport(sim, "sim");
|
|
assert!(matches!(d.psu_on(), Err(Error::NoAck { cmd: 0x72, .. })));
|
|
}
|
|
|
|
#[test]
|
|
fn passive_read_and_replay_cycle_through_readings() {
|
|
let mut sim = Simulator::sample();
|
|
sim.polls_to_ready = 0;
|
|
let mut d = Hpcs6500::with_transport(sim, "sim");
|
|
let a = d.read_current().unwrap();
|
|
let b = d.take_single_reading().unwrap();
|
|
assert_eq!(a, b);
|
|
assert_eq!(d.transport().served, 1);
|
|
}
|