Files
grabowski 2a3f28001c Lamp testbench in Rust around the HPCS 6500
lampbench station / export / label / tm30 / calibrate replace the Python
lamp_station.py, lamp_export.py, label_printer.py, tm30.py and
calibration.py that lived in HPCS6500-py. The driver is a submodule
(HPCS6500/, like mppt-testbench). Workspace crates: tm30 (TM-30-18 port of
colour-science 0.4.7), brother-ql (raster protocol, byte-identical to
brother_ql) and lamp-label (QR + ID labels for the QL-820NWB).

Bundles are byte-identical to lamp_export.py's (golden tests); the station
runs end to end in a test against the simulated instrument.
2026-10-22 17:26:14 +07:00

204 lines
5.6 KiB
Rust

//! The station end to end: scripted answers on stdin, the simulated
//! instrument, a throwaway comparison-data repository.
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
fn git(dir: &Path, args: &[&str]) -> String {
let o = Command::new("git")
.arg("-C")
.arg(dir)
.args(args)
.output()
.unwrap();
assert!(
o.status.success(),
"git {args:?}: {}",
String::from_utf8_lossy(&o.stderr)
);
String::from_utf8_lossy(&o.stdout).to_string()
}
/// A valid EAN-13 with the given 12 leading digits.
fn ean(d12: &str) -> String {
let sum: u32 = d12
.chars()
.enumerate()
.map(|(i, c)| c.to_digit(10).unwrap() * if i % 2 == 1 { 3 } else { 1 })
.sum();
format!("{d12}{}", (10 - sum % 10) % 10)
}
#[test]
fn station_measures_commits_and_re_measures() {
let tmp = std::env::temp_dir().join(format!("lampbench-station-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&tmp);
let repo = tmp.join("comparison-data");
std::fs::create_dir_all(repo.join("lamps")).unwrap();
git(&repo, &["init", "-q"]);
git(&repo, &["config", "user.name", "Test"]);
git(&repo, &["config", "user.email", "test@example.com"]);
std::fs::write(repo.join("README.md"), "data\n").unwrap();
git(&repo, &["add", "."]);
git(&repo, &["commit", "-qm", "init"]);
// --interval 0.5 keeps the runs in different seconds, so the re-measure changes measured_at
let internal = ean("200123456789");
let answers = [
// lamp 1: no barcode
"",
"philips", // manufacturer -> canonical "Philips"
"Test A60", // model
"", // type: led
"y", // dimmable
"Warm White", // variant -> lower case
"", // notes
"806",
"2700",
"8.5",
"",
"",
"60", // rated: flux, CCT, W, Ra, lifetime, equivalent
"", // insert the lamp
// lamp 2: retailer-internal EAN (no online lookup)
&internal,
"",
"Shop bulb",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
// re-measure lamp 1, everything pre-filled
"L0001",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
// a bad barcode, then quit
"12345",
"q",
]
.join("\n")
+ "\n";
let mut child = Command::new(env!("CARGO_BIN_EXE_lampbench"))
.args([
"station",
"--simulate",
"--settle",
"0",
"--readings",
"2",
"--interval",
"0.5",
"--no-push",
"--no-labels",
"--no-lookup",
])
.arg("--data-repo")
.arg(&repo)
.arg("--cache")
.arg(tmp.join("ean-cache.json"))
.env_remove("HPCS_LABEL_PRINTER")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child
.stdin
.take()
.unwrap()
.write_all(answers.as_bytes())
.unwrap();
let out = child.wait_with_output().unwrap();
let text = String::from_utf8_lossy(&out.stdout);
assert!(
out.status.success(),
"{text}\n{}",
String::from_utf8_lossy(&out.stderr)
);
assert!(text.contains("--- New lamp L0001 ---"), "{text}");
assert!(
text.contains("Using canonical brand name: Philips") || text.contains("Manufacturer"),
"{text}"
);
assert!(
text.contains("Lookup: ? / ? (retailer-internal barcode, not resolvable)"),
"{text}"
);
assert!(text.contains("Re-measuring L0001"), "{text}");
assert!(text.contains("Not a valid EAN-13/UPC-A: '12345'"), "{text}");
assert!(text.contains("PSU off"), "{text}");
assert!(text.trim_end().ends_with("Bye."), "{text}");
let m: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(repo.join("lamps/L0001/metrics.json")).unwrap(),
)
.unwrap();
assert_eq!(m["manufacturer"], "Philips");
assert_eq!(m["variant"], "warm white");
assert_eq!(m["dimmable"], true);
assert_eq!(m["type"], "led");
assert_eq!(
m["rated"],
serde_json::json!({"Phi_lm": 806, "CCT_K": 2700, "Power_W": 8.5, "W_equiv": 60})
);
assert_eq!(m["readings_averaged"], 2);
assert_eq!(m["supply"]["ac_voltage"], 230.0);
let m2: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(repo.join("lamps/L0002/metrics.json")).unwrap(),
)
.unwrap();
assert_eq!(m2["ean"], internal.as_str());
assert_eq!(m2["notes"], "retailer-internal barcode, brand unknown");
assert_eq!(
std::fs::read_to_string(repo.join("lamps/L0001/spd.csv"))
.unwrap()
.lines()
.count(),
351
);
assert_eq!(
std::fs::read_to_string(repo.join("lamps/L0001/tm30.csv"))
.unwrap()
.lines()
.count(),
17
);
let log = git(&repo, &["log", "--format=%s"]);
let subjects: Vec<&str> = log.lines().collect();
assert_eq!(
subjects[0], "Re-measure lamp L0001 (Philips, Test A60, warm white)",
"{log}"
);
assert_eq!(
subjects[1],
format!("Add lamp L0002 (Shop bulb, EAN {internal})"),
"{log}"
);
assert_eq!(
subjects[2], "Add lamp L0001 (Philips, Test A60, warm white)",
"{log}"
);
let _ = std::fs::remove_dir_all(&tmp);
}