fix(chartread): enable ARGYLL_NOT_INTERACTIVE for stdin piping & fix instlist JSON parsing (resolves #134) #135

Merged
gronod merged 1 commits from fix/134-chartread-stdin-instlist-json into development 2026-08-29 13:25:33 +01:00
5 changed files with 61 additions and 24 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "iccery", "name": "iccery",
"private": true, "private": true,
"version": "0.3.4", "version": "0.3.5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"fetch-argyll": "node scripts/fetch-argyll.mjs", "fetch-argyll": "node scripts/fetch-argyll.mjs",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "iccery" name = "iccery"
version = "0.3.4" version = "0.3.5"
description = "Modern Printer Profiling UI frontend for ArgyllCMS" description = "Modern Printer Profiling UI frontend for ArgyllCMS"
authors = ["Gordon"] authors = ["Gordon"]
edition = "2021" edition = "2021"
+1
View File
@@ -43,6 +43,7 @@ impl ProcessManager {
command.stdout(Stdio::piped()); command.stdout(Stdio::piped());
command.stderr(Stdio::piped()); command.stderr(Stdio::piped());
command.stdin(Stdio::piped()); command.stdin(Stdio::piped());
command.env("ARGYLL_NOT_INTERACTIVE", "1");
#[cfg(windows)] #[cfg(windows)]
{ {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "ICCery", "productName": "ICCery",
"version": "0.3.4", "version": "0.3.5",
"identifier": "com.gronod.iccery", "identifier": "com.gronod.iccery",
"build": { "build": {
"frontendDist": "../src" "frontendDist": "../src"
+57 -21
View File
@@ -94,22 +94,24 @@ export function initChartread() {
btnDetectInstruments.textContent = "Detecting..."; btnDetectInstruments.textContent = "Detecting...";
setPrompt("Querying connected spectrophotometers and colorimeters via instlist..."); setPrompt("Querying connected spectrophotometers and colorimeters via instlist...");
const detected = []; let stdoutAccumulator = "";
const regexDetected = [];
try { try {
const unlistenStdout = await listen("process:stdout", (event) => { const unlistenStdout = await listen("process:stdout", (event) => {
if (event.payload.id !== "instlist" || !event.payload.line) return; if (event.payload.id !== "instlist" || !event.payload.line) return;
const line = event.payload.line.trim(); const line = event.payload.line;
stdoutAccumulator += line + "\n";
// Matches Argyll instlist output e.g. "1: 'i1Pro' on 'USB'" or "1: 'ColorMunki'" or "1 = 'i1Display'" // Legacy regex line matching fallback
const KNOWN_INST_TOKENS = /i1|ColorMunki|Spyder|spectro|Display|Huey|DTP|SpectroScan|Smile|Klein/i; const KNOWN_INST_TOKENS = /i1|ColorMunki|Spyder|spectro|Display|Huey|DTP|SpectroScan|Smile|Klein/i;
const match = line.match(/^(\d+)[\s:=]+'?([^'\n]+)'?(?:\s+on\s+'?([^'\n]+)'?)?/i); const match = line.trim().match(/^(\d+)[\s:=]+'?([^'\n]+)'?(?:\s+on\s+'?([^'\n]+)'?)?/i);
if (match) { if (match) {
const index = match[1]; const index = match[1];
const name = match[2].trim(); const name = match[2].trim();
const port = match[3] ? match[3].trim() : ""; const port = match[3] ? match[3].trim() : "";
if (KNOWN_INST_TOKENS.test(name) || port.length > 0) { if (KNOWN_INST_TOKENS.test(name) || port.length > 0) {
detected.push({ index, name, port }); regexDetected.push({ index, name, port });
} }
} }
}); });
@@ -123,16 +125,35 @@ export function initChartread() {
instrumentSelect.innerHTML = `<option value="">Auto (First available port)</option>`; instrumentSelect.innerHTML = `<option value="">Auto (First available port)</option>`;
if (detected.length > 0) { let devicesList = [];
detected.forEach((inst) => {
// Try parsing JSON output from instlist
try {
const trimmed = stdoutAccumulator.trim();
const parsed = JSON.parse(trimmed);
if (parsed && Array.isArray(parsed.devices)) {
devicesList = parsed.devices.map(d => ({
index: String(d.port || ""),
name: d.name || d.type || "Instrument",
type: d.type || "",
port: String(d.port || ""),
}));
}
} catch (_) {
// Fall back to regex parsed items if not JSON
devicesList = regexDetected;
}
if (devicesList.length > 0) {
devicesList.forEach((inst) => {
const opt = document.createElement("option"); const opt = document.createElement("option");
// Do not pass instlist ordinal as -c comm port; use empty value for auto-port (#111) // Port value for -c switch. If port is 1 or auto, empty string leaves -c omitted for default port
opt.value = ""; opt.value = inst.port && inst.port !== "1" ? inst.port : "";
opt.textContent = `${inst.name}${inst.port ? ` on ${inst.port}` : ""}`; opt.textContent = `${inst.type || inst.name}${inst.port ? ` (Port ${inst.port})` : ""}`;
instrumentSelect.appendChild(opt); instrumentSelect.appendChild(opt);
}); });
instrumentSelect.value = ""; instrumentSelect.value = "";
setPrompt(`Found ${detected.length} instrument(s): ${detected.map(d => d.name).join(", ")}`); setPrompt(`Found ${devicesList.length} instrument(s): ${devicesList.map(d => d.type || d.name).join(", ")}`);
} else { } else {
setPrompt("No instruments found via instlist. Ensure USB cable is plugged in."); setPrompt("No instruments found via instlist. Ensure USB cable is plugged in.");
} }
@@ -164,6 +185,8 @@ export function initChartread() {
btnStartRead.classList.remove("hidden"); btnStartRead.classList.remove("hidden");
break; break;
case STATE.CALIBRATING: case STATE.CALIBRATING:
btnCalibrate.disabled = false;
btnCalibrate.textContent = "✓ Calibrate";
btnCalibrate.classList.remove("hidden"); btnCalibrate.classList.remove("hidden");
btnCancel.classList.remove("hidden"); btnCancel.classList.remove("hidden");
break; break;
@@ -243,21 +266,26 @@ export function initChartread() {
// Parse prompts for state transitions // Parse prompts for state transitions
const lineLower = line.toLowerCase(); const lineLower = line.toLowerCase();
if (lineLower.includes("calibrat") && lineLower.includes("place")) { if (
(lineLower.includes("place") && (lineLower.includes("reference") || lineLower.includes("white") || lineLower.includes("calibrat"))) ||
lineLower.includes("hit any key to continue") ||
lineLower.includes("calibration")
) {
setState(STATE.CALIBRATING); setState(STATE.CALIBRATING);
setPrompt(line); setPrompt(line.trim());
} else if (lineLower.includes("hit") && lineLower.includes("read") && lineLower.includes("strip")) { } else if (
(lineLower.includes("hit") && lineLower.includes("read") && lineLower.includes("strip")) ||
lineLower.includes("ready to read") ||
(lineLower.includes("read") && lineLower.includes("strip") && lineLower.includes("key"))
) {
setState(STATE.AWAITING_STRIP); setState(STATE.AWAITING_STRIP);
setPrompt(line); setPrompt(line.trim());
} else if (lineLower.includes("ready to read")) {
setState(STATE.AWAITING_STRIP);
setPrompt(line);
} else if (lineLower.includes("reading strip") || lineLower.includes("processing")) { } else if (lineLower.includes("reading strip") || lineLower.includes("processing")) {
setState(STATE.READING); setState(STATE.READING);
setPrompt(line); setPrompt(line.trim());
} else if (lineLower.includes("error") || lineLower.includes("too fast") || lineLower.includes("too slow") || lineLower.includes("misread")) { } else if (lineLower.includes("error") || lineLower.includes("too fast") || lineLower.includes("too slow") || lineLower.includes("misread")) {
setState(STATE.ERROR); setState(STATE.ERROR);
setPrompt("⚠️ " + line); setPrompt("⚠️ " + line.trim());
} }
}); });
@@ -423,8 +451,16 @@ export function initChartread() {
if (btnCalibrate) { if (btnCalibrate) {
btnCalibrate.addEventListener("click", async () => { btnCalibrate.addEventListener("click", async () => {
try { try {
btnCalibrate.disabled = true;
btnCalibrate.textContent = "⏳ Calibrating...";
setPrompt("Sending calibration command to instrument...");
await invoke("send_stdin", { id: currentProcessId, input: " \n" }); await invoke("send_stdin", { id: currentProcessId, input: " \n" });
} catch (e) { console.error("send_stdin error:", e); } } catch (e) {
console.error("send_stdin error:", e);
btnCalibrate.disabled = false;
btnCalibrate.textContent = "✓ Calibrate";
setPrompt(`Failed to send calibration signal: ${e}`);
}
}); });
} }