boardViewer

This commit is contained in:
chk
2026-06-19 06:43:06 +02:00
parent d36ef6189d
commit aa78116837
8 changed files with 593 additions and 73 deletions

View File

@@ -151,6 +151,108 @@ export async function runHoming({
}
}
/**
* Führt den Homing-Ablauf offline aus (Bilder und NPZ bereits im runDir).
* Identische 4b-Kette wie runHoming — ohne Webcam-Zugriff und ohne SSE-Stream.
* send() akkumuliert Logs; done-Event trägt den finalen State.
*
* @param {{
* robotJsonPath: string,
* runDir: string,
* send: (obj: object) => void,
* runScript: (args: string[], send: Function) => Promise<number>,
* runBoardPipelineOffline:(runDir: string, send: Function) => Promise<void>,
* SCRIPT_4B: string,
* SCRIPT_5POSE: string,
* }} opts
*/
export async function runHomingOffline({
robotJsonPath,
runDir,
send,
runScript,
runBoardPipelineOffline,
SCRIPT_4B,
SCRIPT_5POSE,
}) {
send({ type: 'log', text: `▶ Homing-Offline: ${path.basename(runDir)}` });
send({ type: 'log', text: `▶ Robot-JSON: ${robotJsonPath}` });
send({ type: 'log', text: '' });
// ── Schritt 1: Marker-Triangulierung (Bilder liegen bereits im runDir) ──────
send({ type: 'step', step: 1, total: 5, text: 'Marker-Triangulierung …' });
await runBoardPipelineOffline(runDir, send);
const arucoJson = path.join(runDir, 'aruco_marker_poses.json');
try {
await fsPromises.access(arucoJson);
} catch {
send({ type: 'error', text: '❌ aruco_marker_poses.json fehlt Script 3b hat nicht funktioniert.' });
send({ type: 'done', exitCode: -1 });
return;
}
// ── Schritt 2: X-Position schätzen ──────────────────────────────────────────
const xMm = estimateXFromMarkers(arucoJson, robotJsonPath);
send({ type: 'log', text: `▶ Geschätzte X-Position: ${xMm.toFixed(1)} mm` });
send({ type: 'analysis', key: 'x_mm', value: xMm });
// ── Schritte 35 (24): 4b-Kette Arm1 → Ellbow → Arm2 → Hand ───────────────
const links = ['Arm1', 'Ellbow', 'Arm2', 'Hand'];
let fromState = null;
let chainComplete = true;
for (let i = 0; i < links.length; i++) {
const link = links[i];
send({ type: 'step', step: 2 + i, total: 5, text: `Gelenkwinkel ${link}` });
send({ type: 'log', text: `\n─── 4b: ${link} ${'─'.repeat(35 - link.length)}` });
const outputPath = path.join(runDir, `state_${link}.json`);
const args = [SCRIPT_4B, '--robot', robotJsonPath, '--aruco', arucoJson, '--link', link, '--output', outputPath];
if (fromState) args.push('--from-state', fromState);
else args.push('--x-mm', String(xMm));
const exit = await runScript(args, send);
if (exit !== 0) {
send({ type: 'log', text: `⚠ 4b ${link} Exit ${exit} — falle auf 5_pose_estimation.py zurück` });
chainComplete = false;
break;
}
fromState = outputPath;
try {
const stateData = JSON.parse(await fsPromises.readFile(outputPath, 'utf8'));
send({ type: 'analysis', key: `state_${link}`, value: stateData.accumulated_state ?? stateData });
} catch { /* ignorieren */ }
}
// ── Endergebnis ──────────────────────────────────────────────────────────────
try {
let finalState;
if (chainComplete) {
const finalData = JSON.parse(await fsPromises.readFile(fromState, 'utf8'));
finalState = finalData.accumulated_state ?? finalData;
} else {
send({ type: 'step', step: 5, total: 5, text: '5_pose_estimation.py (Fallback) …' });
const poseOut = path.join(runDir, 'robot_state.json');
const args = [SCRIPT_5POSE, arucoJson, '-robot', robotJsonPath, '-out', poseOut];
if (fromState) args.push('--from-state', fromState);
const exit = await runScript(args, send);
if (exit !== 0) throw new Error(`5_pose_estimation.py Exit ${exit}`);
const poseData = JSON.parse(await fsPromises.readFile(poseOut, 'utf8'));
finalState = Object.fromEntries(
Object.entries(poseData.movements).map(([k, v]) => [k, v.value])
);
}
send({ type: 'log', text: '' });
send({ type: 'log', text: '✅ Homing-Offline abgeschlossen' });
send({ type: 'done', exitCode: 0, state: finalState });
} catch (err) {
send({ type: 'error', text: `❌ Endzustand konnte nicht gelesen werden: ${err.message}` });
send({ type: 'done', exitCode: -1 });
}
}
/** Timestamp-String YYYYMMDD_HHmmss */
function makeTimestamp() {
const now = new Date();