// robot/FCodeClient.js // Translates FCode messages (FPoint, FPlus, FList, …) into REST calls to appRobotFileservice. // The Driver is the only gateway — controllers never contact the fileservice directly. function _baseUrl() { return process.env.FILESERVICE_URL || 'http://appRobot_Fileservice:2100'; } /** Returns true when the message is an FCode (F + uppercase letter + lowercase, at start). */ function isFCode(message) { return /^F[A-Z][a-z]+/.test(String(message).trim()); } /** * Dispatch an FCode message to the appropriate fileservice endpoint. * @param {object} robot Current robot state (x,y,z,phi,theta,psi,e,feedrate). * @param {string} message Raw FCode string, e.g. "FPoint", "FPlus", "FLoad demo". * @returns {Promise<{type: string, data?: string, line?: string}>} * type 'step' → line (driver-native GCode, Radian) ready to execute * type 'list'|'show'|'point'|'ok' → data (JSON string) to broadcast */ async function handle(robot, message) { const msg = String(message).trim(); if (msg.startsWith('FList')) { const data = await _req('GET', '/api/programs'); return { type: 'list', data: JSON.stringify(data) }; } if (msg.startsWith('FShow')) { const id = msg.slice('FShow'.length).trim(); const data = id ? await _req('GET', `/api/programs/${encodeURIComponent(id)}`) : await _req('GET', '/api/active'); return { type: 'show', data: JSON.stringify(data) }; } if (msg.startsWith('FLoad')) { const id = msg.slice('FLoad'.length).trim(); const data = await _req('PUT', '/api/active', { id }); return { type: 'ok', data: JSON.stringify(data) }; } if (msg.startsWith('FSave')) { const name = msg.slice('FSave'.length).trim() || 'unnamed'; const data = await _req('POST', '/api/programs', { name, fromActive: true }); return { type: 'ok', data: JSON.stringify(data) }; } if (msg.startsWith('FClear')) { const data = await _req('POST', '/api/active/clear'); return { type: 'ok', data: JSON.stringify(data) }; } if (msg.startsWith('FPoint')) { // Driver attaches the current pose (Radian) — fileservice converts to degrees for storage. const pose = { x: robot.x, y: robot.y, z: robot.z, a: robot.phi, b: robot.theta, c: robot.psi, e: robot.e, }; const feedrate = robot.feedrate || 1000; const data = await _req('POST', '/api/active/points', { pose, feedrate }); return { type: 'point', data: JSON.stringify(data) }; } if (msg.startsWith('FPlus')) { const d = await _req('POST', '/api/active/next'); return { type: 'step', line: d.line }; } if (msg.startsWith('FMinus')) { const d = await _req('POST', '/api/active/prev'); return { type: 'step', line: d.line }; } if (msg.startsWith('FFirst')) { const d = await _req('POST', '/api/active/first'); return { type: 'step', line: d.line }; } if (msg.startsWith('FLast')) { const d = await _req('POST', '/api/active/last'); return { type: 'step', line: d.line }; } if (msg.startsWith('FGoto')) { const index = parseInt(msg.slice('FGoto'.length).trim(), 10); const d = await _req('POST', '/api/active/goto', { index }); return { type: 'step', line: d.line }; } if (msg.startsWith('FPlay')) { const d = await _req('POST', '/api/active/play'); return { type: 'ok', data: JSON.stringify(d) }; } if (msg.startsWith('FStop')) { const d = await _req('POST', '/api/active/stop'); return { type: 'ok', data: JSON.stringify(d) }; } throw new Error(`Unbekannter FCode: ${msg}`); } async function _req(method, path, body) { const url = `${_baseUrl()}${path}`; const opts = { method }; if (body !== undefined && body !== null) { opts.headers = { 'content-type': 'application/json' }; opts.body = JSON.stringify(body); } console.log(`[FCode] → ${method} ${url}${body ? ' ' + JSON.stringify(body) : ''}`); let res; try { res = await fetch(url, opts); } catch (netErr) { // Fileservice nicht erreichbar (DNS/Connection refused): klare Meldung statt "fetch failed". console.error(`[FCode] ✖ ${method} ${url}: Fileservice nicht erreichbar (${netErr.message})`); const e = new Error(`Fileservice nicht erreichbar: ${netErr.message}`); e.code = 'FILESERVICE_UNREACHABLE'; throw e; } if (!res.ok) { const err = await res.json().catch(() => ({})); console.error(`[FCode] ✖ ${res.status} ${method} ${path}: ${err.code || ''} ${err.message || res.statusText}`); const e = new Error(err.message || res.statusText); e.code = err.code; e.status = res.status; throw e; } const data = await res.json(); console.log(`[FCode] ← ${res.status} ${method} ${path} ${JSON.stringify(data).slice(0, 160)}`); return data; } module.exports = { isFCode, handle };