ToDo_1 erledigt

This commit is contained in:
chk
2026-06-08 15:16:53 +02:00
parent 6e98226ae1
commit 2e521c510f
4 changed files with 227 additions and 124 deletions

View File

@@ -6,8 +6,16 @@ Klare Trennung zwischen G-Code-Parsing und Robotersteuerlogik. Der Parser soll n
## Aufgaben
- [ ] `GCodeParser` einführen, das G-Code und Nachrichten in strukturierte Befehlsobjekte übersetzt
- [ ] Parsing-Regeln definieren für `G90`, `G91`, `G1`, `G28`, `G92`, `M1` und `$J=` sowie Parameter `X`, `Y`, `Z`, `A`, `B`, `C`, `E`, `F`
- [ ] Raw-String-Verarbeitung aus `GCode.receiveGCode()` entfernen
- [ ] Parser-Resultate als Objekte an den Controller übergeben, nicht als rohe Textbefehle
- [ ] Parser-Fehlerfälle klar behandeln: ungültige Syntax, fehlende Werte, unbrauchbare Befehle
- [x] `GCodeParser` einführen, das G-Code und Nachrichten in strukturierte Befehlsobjekte übersetzt
- [x] Parsing-Regeln definieren für `G90`, `G91`, `G1`, `G28`, `G92`, `M1` und `$J=` sowie Parameter `X`, `Y`, `Z`, `A`, `B`, `C`, `E`, `F`
- [x] Raw-String-Verarbeitung aus `GCode.receiveGCode()` entfernen
- [x] Parser-Resultate als Objekte an den Controller übergeben, nicht als rohe Textbefehle
- [x] Parser-Fehlerfälle klar behandeln: ungültige Syntax, fehlende Werte, unbrauchbare Befehle
## Status
- [x] Implementierung abgeschlossen
- [x] Tests erfolgreich: `npx jest --runInBand`
Erledigt von VStudio Chatbot unter Aufsicht ChK

View File

@@ -2,6 +2,7 @@
* Receives GCode, processes it and moves the Data to the Roboter-Class
*/
const fs = require('fs');
const GCodeParser = require('./GCodeParser');
class GCode{
@@ -81,138 +82,103 @@ class GCode{
};
static receiveGCode(robot, g){
const commands = GCodeParser.parse(g);
if (!commands.length) return;
if(g == undefined) return;
if(g.length == 0) return;
commands.forEach(parsed => {
if (!parsed || !parsed.command) {
return;
}
// console.log("🔵 GCode.receiveGCode: Incoming command: " + g); // Moved to InputWS
const cmd = parsed.command;
const params = parsed.params || {};
g = g.toString("utf8");
if (cmd === 'G90') {
robot.moveRelative = false;
return;
}
if(g.startsWith("$J=")) {
// Handle $J= GCode for jogging
g = g.substring(3).trim(); // Remove "$J=" and trim whitespace
}
if (cmd === 'G91') {
robot.moveRelative = true;
return;
}
var multipleCommands = g.split(" G");
var doProcessRest = false;
if(multipleCommands.length > 1){
doProcessRest = true;
g = multipleCommands[0];
}
g = g.split(" ");
var calculateNew = true;
var calculateFromMotorCoordinates = false;
if(g[0] == "G90") {robot.moveRelative = false; calculateNew = false;}
else if(g[0] == "G91") {robot.moveRelative = true; calculateNew = false;}
// G28 - Move to Home Position
else if(g[0] == "G28") {
if (cmd === 'G28') {
robot.x = 0;
robot.y = robot.l1 + robot.l2 + robot.l3;
robot.z = 0;
robot.phi = -Math.PI/2;
robot.theta = Math.PI/2;
robot.phi = -Math.PI / 2;
robot.theta = Math.PI / 2;
robot.psi = 0;
robot.e = 0;
return;
}
else if(g[0] == "G1" && robot.moveRelative){
g.forEach((s) => {
if(s.toUpperCase().includes("X")){ robot.x += Number(s.substring(1, s.length)); robot.xMotorChanged = true;}
if(s.toUpperCase().includes("Y")){ robot.y += Number(s.substring(1, s.length)); robot.yMotorChanged = true;}
if(s.toUpperCase().includes("Z")){ robot.z += Number(s.substring(1, s.length)); robot.zMotorChanged = true;}
// abc in 2Pi-Angles
if(s.toUpperCase().includes("A")){ robot.phi += Number(s.substring(1, s.length)); robot.aMotorChanged = true;}
if(s.toUpperCase().includes("B")){ robot.theta += Number(s.substring(1, s.length)); robot.bMotorChanged = true;}
if(s.toUpperCase().includes("C")){ robot.psi += Number(s.substring(1, s.length)); robot.cMotorChanged = true;}
if(s.toUpperCase().includes("E")){ robot.e += Number(s.substring(1, s.length)); robot.eMotorChanged = true;}
if(s.toUpperCase().includes("F")){ robot.feedrate = Number(s.substring(1, s.length));}
});
}
else if(g[0] == "M1" && robot.moveRelative){
calculateFromMotorCoordinates = true;
g.forEach((s) => {
if(s.toUpperCase().includes("X")){ robot.xMotor += Number(s.substring(1, s.length)); robot.xMotorChanged = true;}
if(s.toUpperCase().includes("Y")){ robot.alpha += Number(s.substring(1, s.length)); robot.yMotorChanged = true;}
if(s.toUpperCase().includes("Z")){ robot.beta += Number(s.substring(1, s.length)); robot.zMotorChanged = true;}
if(s.toUpperCase().includes("A")){ robot.a += Number(s.substring(1, s.length)); robot.aMotorChanged = true;}
if(s.toUpperCase().includes("B")){ robot.b += Number(s.substring(1, s.length)); robot.bMotorChanged = true;}
if(s.toUpperCase().includes("C")){ robot.c += Number(s.substring(1, s.length)); robot.cMotorChanged = true;}
if(s.toUpperCase().includes("E")){ robot.e += Number(s.substring(1, s.length)); robot.eMotorChanged = true;}
});
}
// Absolute-Positioning
else if(g[0] == "G1" && !robot.moveRelative){
g.forEach((s) => {
if(s.toUpperCase().includes("X")){ robot.x = Number(s.substring(1, s.length)); robot.xMotorChanged = true;}
if(s.toUpperCase().includes("Y")){ robot.y = Number(s.substring(1, s.length)); robot.yMotorChanged = true;}
if(s.toUpperCase().includes("Z")){ robot.z = Number(s.substring(1, s.length)); robot.zMotorChanged = true;}
if(s.toUpperCase().includes("A")){ robot.phi = Number(s.substring(1, s.length)); robot.aMotorChanged = true;}
if(s.toUpperCase().includes("B")){ robot.theta = Number(s.substring(1, s.length)); robot.bMotorChanged = true;}
if(s.toUpperCase().includes("C")){ robot.psi = Number(s.substring(1, s.length)); robot.cMotorChanged = true;}
if(s.toUpperCase().includes("E")){ robot.e = Number(s.substring(1, s.length)); robot.eMotorChanged = true;}
if(s.toUpperCase().includes("F")){ robot.feedrate = Number(s.substring(1, s.length)); }
});
}
else if(g[0] == "M1" && !robot.moveRelative){
calculateFromMotorCoordinates = true;
g.forEach((s) => {
if(s.toUpperCase().includes("X")){ robot.xMotor = Number(s.substring(1, s.length)); robot.xMotorChanged = true;}
if(s.toUpperCase().includes("Y")){ robot.alpha = Number(s.substring(1, s.length)); robot.yMotorChanged = true;}
if(s.toUpperCase().includes("Z")){ robot.beta = Number(s.substring(1, s.length)); robot.zMotorChanged = true;}
if(s.toUpperCase().includes("A")){ robot.a = Number(s.substring(1, s.length)); robot.aMotorChanged = true;}
if(s.toUpperCase().includes("B")){ robot.b = Number(s.substring(1, s.length)); robot.bMotorChanged = true;}
if(s.toUpperCase().includes("C")){ robot.c = Number(s.substring(1, s.length)); robot.cMotorChanged = true;}
if(s.toUpperCase().includes("E")){ robot.e = Number(s.substring(1, s.length)); robot.eMotorChanged = true;}
});
robot.calculatePositionFromMotorAngles();
}
else if(g[0] == "M92"){ // G92 - Set Position --- M92 in Radiant
robot.createMotorPosition();
g.forEach((s) => {
if(s.toUpperCase().includes("X")){ robot.xMotor = Number(s.substring(1, s.length)); robot.xMotorChanged = true;}
if(s.toUpperCase().includes("Y")){ robot.alpha = Number(s.substring(1, s.length)); robot.yMotorChanged = true;}
if(s.toUpperCase().includes("Z")){ robot.beta = Number(s.substring(1, s.length)); robot.zMotorChanged = true;}
if(s.toUpperCase().includes("A")){ robot.a = Number(s.substring(1, s.length)); robot.aMotorChanged = true;}
if(s.toUpperCase().includes("B")){ robot.b = Number(s.substring(1, s.length)); robot.bMotorChanged = true;}
if(s.toUpperCase().includes("C")){ robot.c = Number(s.substring(1, s.length)); robot.cMotorChanged = true;}
if(s.toUpperCase().includes("E")){ robot.e = Number(s.substring(1, s.length)); robot.eMotorChanged = true;}
});
robot.calculatePositionFromMotorAngles();
calculateNew = false;
// ToDo: Send Command to update Position of Robot, because G92 should
// set the current Position to the given Coordinates without moving the Robot.
robot.sendCommand("G92");
}
if(calculateNew && !calculateFromMotorCoordinates){
robot.calculateAngles3D();
robot.sendCommand();
}
if(calculateFromMotorCoordinates){
robot.calculatePositionFromMotorAngles();
robot.sendCommand();
}
// verarbeitet alle weiteren Commands in dieser Zeile.
if(doProcessRest){
multipleCommands.forEach((cmd) => {
if(cmd[0] != "G"){
this.receiveGCode(robot, "G"+cmd);
if (cmd === 'G1') {
if (robot.moveRelative) {
if (Number.isFinite(params.X)) { robot.x += params.X; robot.xMotorChanged = true; }
if (Number.isFinite(params.Y)) { robot.y += params.Y; robot.yMotorChanged = true; }
if (Number.isFinite(params.Z)) { robot.z += params.Z; robot.zMotorChanged = true; }
if (Number.isFinite(params.A)) { robot.phi += params.A; robot.aMotorChanged = true; }
if (Number.isFinite(params.B)) { robot.theta += params.B; robot.bMotorChanged = true; }
if (Number.isFinite(params.C)) { robot.psi += params.C; robot.cMotorChanged = true; }
if (Number.isFinite(params.E)) { robot.e += params.E; robot.eMotorChanged = true; }
if (Number.isFinite(params.F)) { robot.feedrate = params.F; }
} else {
if (Number.isFinite(params.X)) { robot.x = params.X; robot.xMotorChanged = true; }
if (Number.isFinite(params.Y)) { robot.y = params.Y; robot.yMotorChanged = true; }
if (Number.isFinite(params.Z)) { robot.z = params.Z; robot.zMotorChanged = true; }
if (Number.isFinite(params.A)) { robot.phi = params.A; robot.aMotorChanged = true; }
if (Number.isFinite(params.B)) { robot.theta = params.B; robot.bMotorChanged = true; }
if (Number.isFinite(params.C)) { robot.psi = params.C; robot.cMotorChanged = true; }
if (Number.isFinite(params.E)) { robot.e = params.E; robot.eMotorChanged = true; }
if (Number.isFinite(params.F)) { robot.feedrate = params.F; }
}
})
}
robot.calculateAngles3D();
robot.sendCommand();
return;
}
if (cmd === 'M1') {
if (robot.moveRelative) {
if (Number.isFinite(params.X)) { robot.xMotor += params.X; robot.xMotorChanged = true; }
if (Number.isFinite(params.Y)) { robot.alpha += params.Y; robot.yMotorChanged = true; }
if (Number.isFinite(params.Z)) { robot.beta += params.Z; robot.zMotorChanged = true; }
if (Number.isFinite(params.A)) { robot.a += params.A; robot.aMotorChanged = true; }
if (Number.isFinite(params.B)) { robot.b += params.B; robot.bMotorChanged = true; }
if (Number.isFinite(params.C)) { robot.c += params.C; robot.cMotorChanged = true; }
if (Number.isFinite(params.E)) { robot.e += params.E; robot.eMotorChanged = true; }
} else {
if (Number.isFinite(params.X)) { robot.xMotor = params.X; robot.xMotorChanged = true; }
if (Number.isFinite(params.Y)) { robot.alpha = params.Y; robot.yMotorChanged = true; }
if (Number.isFinite(params.Z)) { robot.beta = params.Z; robot.zMotorChanged = true; }
if (Number.isFinite(params.A)) { robot.a = params.A; robot.aMotorChanged = true; }
if (Number.isFinite(params.B)) { robot.b = params.B; robot.bMotorChanged = true; }
if (Number.isFinite(params.C)) { robot.c = params.C; robot.cMotorChanged = true; }
if (Number.isFinite(params.E)) { robot.e = params.E; robot.eMotorChanged = true; }
}
robot.calculatePositionFromMotorAngles();
robot.sendCommand();
return;
}
if (cmd === 'M92') {
robot.createMotorPosition();
if (Number.isFinite(params.X)) { robot.xMotor = params.X; robot.xMotorChanged = true; }
if (Number.isFinite(params.Y)) { robot.alpha = params.Y; robot.yMotorChanged = true; }
if (Number.isFinite(params.Z)) { robot.beta = params.Z; robot.zMotorChanged = true; }
if (Number.isFinite(params.A)) { robot.a = params.A; robot.aMotorChanged = true; }
if (Number.isFinite(params.B)) { robot.b = params.B; robot.bMotorChanged = true; }
if (Number.isFinite(params.C)) { robot.c = params.C; robot.cMotorChanged = true; }
if (Number.isFinite(params.E)) { robot.e = params.E; robot.eMotorChanged = true; }
robot.calculatePositionFromMotorAngles();
robot.sendCommand('G92');
return;
}
});
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////77

83
robot/GCodeParser.js Normal file
View File

@@ -0,0 +1,83 @@
class GCodeParser {
static normalizeInput(raw) {
if (raw === undefined || raw === null) {
return '';
}
if (Buffer.isBuffer(raw)) {
raw = raw.toString('utf8');
}
return String(raw).trim();
}
static splitCommands(input) {
if (!input) {
return [];
}
// Preserve leading G/M on each segment.
return input
.trim()
.split(/ (?=G|M)/i)
.map(segment => segment.trim())
.filter(Boolean);
}
static parseCommand(commandString) {
if (!commandString || commandString.length === 0) {
return null;
}
const tokens = commandString.trim().split(/\s+/);
if (tokens.length === 0) {
return null;
}
const commandToken = tokens[0].toUpperCase();
if (!/^[GM]\d+/i.test(commandToken)) {
return null;
}
const params = {};
for (let i = 1; i < tokens.length; i++) {
const token = tokens[i].trim();
if (token.length < 2) {
continue;
}
const key = token.charAt(0).toUpperCase();
const valueString = token.substring(1);
const value = Number(valueString);
if (!Number.isNaN(value)) {
params[key] = value;
}
}
return {
command: commandToken,
params,
raw: commandString.trim()
};
}
static parse(raw) {
const input = this.normalizeInput(raw);
if (!input) {
return [];
}
if (input.startsWith('$J=')) {
return this.parse(input.substring(3).trim());
}
const commands = this.splitCommands(input)
.map(commandString => this.parseCommand(commandString))
.filter(Boolean);
return commands;
}
}
module.exports = GCodeParser;

46
test/GCodeParser.test.js Normal file
View File

@@ -0,0 +1,46 @@
const GCodeParser = require('../robot/GCodeParser');
describe('GCodeParser', () => {
test('parses a simple G1 command with XYZ parameters', () => {
const parsed = GCodeParser.parse('G1 X10 Y20 Z30');
expect(parsed).toEqual([
{
command: 'G1',
params: { X: 10, Y: 20, Z: 30 },
raw: 'G1 X10 Y20 Z30'
}
]);
});
test('parses multiple commands in one line', () => {
const parsed = GCodeParser.parse('G90 G21 G1 X20 Y30');
expect(parsed.map(cmd => cmd.command)).toEqual(['G90', 'G21', 'G1']);
expect(parsed[2].params).toEqual({ X: 20, Y: 30 });
});
test('parses buffer input as UTF-8', () => {
const parsed = GCodeParser.parse(Buffer.from('G90 G1 X12 Y34 Z56', 'utf8'));
expect(parsed.length).toBe(2);
expect(parsed[0].command).toBe('G90');
expect(parsed[1].command).toBe('G1');
expect(parsed[1].params).toEqual({ X: 12, Y: 34, Z: 56 });
});
test('parses jogging prefix and ignores unsupported G-code', () => {
const parsed = GCodeParser.parse('$J=G91 G1 X2 Y3');
expect(parsed).toEqual([
{ command: 'G91', params: {}, raw: 'G91' },
{ command: 'G1', params: { X: 2, Y: 3 }, raw: 'G1 X2 Y3' }
]);
});
test('returns empty array for invalid input', () => {
expect(GCodeParser.parse('X324')).toEqual([]);
expect(GCodeParser.parse('')).toEqual([]);
expect(GCodeParser.parse(undefined)).toEqual([]);
});
});