110 lines
4.7 KiB
JavaScript
110 lines
4.7 KiB
JavaScript
// 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 };
|