/** * driverClient.js – WebSocket-Transport zum appRobotDriver * * Der Driver nimmt Steuerbefehle als Plain-Text-G-Code über einen WebSocket * entgegen (wss://…:2096, self-signed), NICHT über HTTP — siehe * appRobotDriver/doc/API.md. Ein früher angenommenes `POST /api/state` existiert * dort nicht (war Platzhalter, vgl. doc/accessRobotAPI.md). G92 setzt am Driver * die Motorposition ohne Bewegung (intern M92) = exakt die Homing-Semantik. * * DRIVER_WS_URL nicht gesetzt → kein Kontakt, klarer 501-Fehler (analog zum * früheren ROBOT_URL-Verhalten). */ import { WebSocket } from 'ws'; const DRIVER_WS_URL = process.env.DRIVER_WS_URL || ''; /** true, wenn ein Driver-WebSocket konfiguriert ist. */ export function isDriverConfigured() { return Boolean(DRIVER_WS_URL); } /** * Öffnet eine kurzlebige WS-Verbindung zum Driver, sendet eine G-Code-Zeile und * wartet auf die erste Antwort (Positions-JSON bzw. Fehler-Envelope). Der Driver * broadcastet nach jedem G-Code das aktuelle Positions-JSON an alle Clients — * der Sender ist selbst Client und bekommt es zurück. * * @param {string} line z.B. "G92 X1 Y2 …" * @param {{timeoutMs?: number}} [opts] * @returns {Promise<{ok:boolean, sent:string, response?:any, error?:string, note?:string}>} */ export function sendGcode(line, { timeoutMs = 4000 } = {}) { const text = String(line ?? '').trim(); if (!text) { return Promise.reject(Object.assign(new Error('Leere G-Code-Zeile'), { statusCode: 400 })); } if (!DRIVER_WS_URL) { return Promise.reject(Object.assign( new Error('DRIVER_WS_URL ist nicht konfiguriert'), { statusCode: 501 })); } return new Promise((resolve, reject) => { // Self-signed Cert am Driver → Zertifikatsprüfung deaktivieren (interner Hop). const ws = new WebSocket(DRIVER_WS_URL, { rejectUnauthorized: false }); let settled = false; const finish = (fn, arg) => { if (settled) return; settled = true; clearTimeout(timer); try { ws.close(); } catch { /* egal */ } fn(arg); }; // Gesendet, aber keine Antwort rechtzeitig: kein harter Fehler — der Befehl // ist raus, der Driver antwortet nur evtl. nicht broadcastfähig. const timer = setTimeout(() => { finish(resolve, { ok: true, sent: text, response: null, note: 'keine Antwort (Timeout)' }); }, timeoutMs); ws.on('open', () => ws.send(text)); ws.on('message', (data) => { const raw = data.toString(); let parsed; try { parsed = JSON.parse(raw); } catch { parsed = raw; } if (parsed && typeof parsed === 'object' && parsed.type === 'error') { finish(resolve, { ok: false, sent: text, error: parsed.message || raw, response: parsed }); } else { finish(resolve, { ok: true, sent: text, response: parsed }); } }); ws.on('error', (err) => finish(reject, Object.assign( new Error(`Driver-WS-Fehler: ${err.message}`), { statusCode: 502 }))); }); }