G92-Grad + E-Korrektur

This commit is contained in:
chk
2026-06-25 18:58:55 +02:00
parent 8deb7bb8a6
commit b96a538b89
18 changed files with 1369 additions and 34 deletions

View File

@@ -113,5 +113,22 @@ describe("Robot G92", () => {
// ("Wenn nur G92 x3 gegeben wird, dann wird trotzdem auch y und z gesendet. schlecht." );
});
test("G92 E: Greifer-Öffnung (mm) → eMotor über Kopplung e - b - c", () => {
const robot = new Robot(300, 300, 20);
const D = 180 / Math.PI;
// B/C in Grad rein (→ intern Radiant), E in mm. eMotor muss aus e, b, c abgeleitet
// werden — die Greifer-Sehne läuft durchs Handgelenk (Arm3SegmentLinearX-Kopplung).
GCode.receiveGCode(robot, "G92 B30 C-45 E10");
expect(robot.b).toBeCloseTo(30 / D, 6); // 30° → rad
expect(robot.c).toBeCloseTo(-45 / D, 6); // -45° → rad
expect(robot.e).toBe(10); // mm, unverändert
expect(robot.eMotor).toBeCloseTo(10 - robot.b - robot.c, 6); // = e - b - c
// Konsistenz: identische Kopplung wie der reguläre Bewegungspfad (calculateAngles3D).
expect(robot.eMotor).toBeCloseTo(robot.gripperMotorFromOpening(robot.e), 12);
});
});

View File

@@ -204,7 +204,8 @@ describe('InfoServer', () => {
const httpsOptions = { key, cert, passphrase: 'abcd' };
const sharedState = { connectedClients: [], lastCommands: [], lastPings: [] };
const robot = { x: 10, y: 20, z: 30, phi: 0.1, theta: 0.2, psi: 0.3, xMotor: 0, alpha: 0, beta: 0, a: 0, b: 0, c: 0 };
// e = Greifer-Öffnung (mm) → position.e; eMotor = Greifer-Motorwert → motorCounts.e.
const robot = { x: 10, y: 20, z: 30, phi: 0.1, theta: 0.2, psi: 0.3, xMotor: 0, alpha: 0, beta: 0, a: 0, b: 0, c: 0, e: 2.5, eMotor: 7 };
const senders = [];
server = createInfoServer(httpsOptions, sharedState, robot, GCode, senders);
@@ -214,7 +215,8 @@ describe('InfoServer', () => {
expect(statusCode).toBe(200);
const json = JSON.parse(body);
expect(json.position).toEqual({ x: 10, y: 20, z: 30, a: 0.1, b: 0.2, c: 0.3 });
expect(json.position).toEqual({ x: 10, y: 20, z: 30, a: 0.1, b: 0.2, c: 0.3, e: 2.5 });
expect(json.motorCounts.e).toBe(7); // Motorwert (eMotor), nicht die mm-Öffnung
});
test('returns 404 for unknown endpoints', async () => {

View File

@@ -101,7 +101,7 @@ describe('InputWS API response routing', () => {
a.send('M114');
const parsed = JSON.parse(await aReply);
expect(parsed.position).toEqual({ x: 5, y: 6, z: 7, a: 0, b: 0, c: 0 });
expect(parsed.position).toEqual({ x: 5, y: 6, z: 7, a: 0, b: 0, c: 0, e: 0 });
expect(await bSilent).toBe(true);
a.close();
@@ -123,8 +123,8 @@ describe('InputWS API response routing', () => {
const aParsed = JSON.parse(await aReply);
const bParsed = JSON.parse(await bReply);
expect(aParsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0 });
expect(bParsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0 });
expect(aParsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0, e: 0 });
expect(bParsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0, e: 0 });
expect(robot.sendCommand).toHaveBeenCalled();
a.close();

View File

@@ -88,7 +88,7 @@ describe('InputWS', () => {
const message = await messagePromise;
const parsed = JSON.parse(message);
expect(parsed.position).toEqual({ x: 12, y: 34, z: 56, a: 1, b: 2, c: 3 });
expect(parsed.position).toEqual({ x: 12, y: 34, z: 56, a: 1, b: 2, c: 3, e: 0 });
expect(parsed.motorCounts).toBeDefined();
client.close();
@@ -108,7 +108,7 @@ describe('InputWS', () => {
const message = await messagePromise;
const parsed = JSON.parse(message);
expect(parsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0 });
expect(parsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0, e: 0 });
expect(robot.sendCommand).toHaveBeenCalled();
client.close();

View File

@@ -65,20 +65,38 @@ describe('RobotController (ToDo_6)', () => {
expect(robot.sendCommand).toHaveBeenCalledWith('G92');
});
test('applyCommand: G92 verhält sich identisch zu M92 (Bug 3)', () => {
test('applyCommand: G92 interpretiert Winkel als Grad (→ rad), X bleibt mm', () => {
const robot = createDummyRobot();
robot.createMotorPosition = jest.fn();
// G92 nutzt die G-Code-Konvention (Grad). Intern landen die Winkel in Radiant,
// X (lineare Schiene) bleibt unverändert in mm. Vgl. M92 oben (Roh-Radiant).
RobotController.applyCommand(robot, { command: 'G92', params: { X: 5, Y: 0.5, A: 0.3 } });
const DEG2RAD = Math.PI / 180;
expect(robot.createMotorPosition).toHaveBeenCalledTimes(1);
expect(robot.xMotor).toBe(5);
expect(robot.alpha).toBe(0.5);
expect(robot.a).toBe(0.3);
expect(robot.alpha).toBeCloseTo(0.5 * DEG2RAD, 10);
expect(robot.a).toBeCloseTo(0.3 * DEG2RAD, 10);
expect(robot.calculatePositionFromMotorAngles).toHaveBeenCalled();
expect(robot.sendCommand).toHaveBeenCalledWith('G92');
});
test('applyCommand: G92 E setzt Greifer-Öffnung (mm) und leitet eMotor ab', () => {
const robot = createDummyRobot();
robot.createMotorPosition = jest.fn();
robot.b = 0.2; // Handgelenk-Knick
robot.c = -0.5; // Hand-Dreher
// E ist mm (keine Grad/rad-Umrechnung). eMotor wird über die Greifer-Kopplung
// aus e, b, c abgeleitet — sonst bliebe der an FluidNC gesendete Wert stale.
RobotController.applyCommand(robot, { command: 'G92', params: { E: 10, B: 0.2 * (180 / Math.PI), C: -0.5 * (180 / Math.PI) } });
expect(robot.e).toBe(10);
expect(robot.eMotor).toBeCloseTo(10 - robot.b - robot.c, 10); // = 10 - 0.2 - (-0.5) = 10.3
expect(robot.sendCommand).toHaveBeenCalledWith('G92');
});
test('applyCommand: ungültiger Befehl wird ignoriert', () => {
const robot = createDummyRobot();
RobotController.applyCommand(robot, null);

View File

@@ -20,12 +20,17 @@ function createDummyRobot() {
a: 0,
b: 0,
c: 0,
eMotor: 0,
// Geometrie
l1: 10,
l2: 10,
l3: 10,
// Greifer-Kopplung (RobotBase-Default: keine Kopplung). Konkrete Kinematiken
// überschreiben dies; siehe Arm3SegmentLinearX.gripperMotorFromOpening.
gripperMotorFromOpening(e) { return e - this.b - this.c; },
// Methoden → jest.fn erlaubt Call-Tracking
calculateAngles3D: jest.fn(),
calculatePositionFromMotorAngles: jest.fn(),