# Roadmap 12 — Austauschbare Kinematik ## Ziel Der `appRobotDriver` soll als generischer G-Code-Treiber für beliebige Roboterarme funktionieren. Die Kinematik ist der einzige arm-spezifische Teil — alles andere (G-Code, WebSocket, Sender, Konfiguration) ist roboter-unabhängig. ## Konventionen (festgelegt) - **Workspace-Koordinaten:** `x, y, z, phi, theta, psi` + `e` (Greifer) — gilt für alle 6-DOF-Arme mit Greifer. Das ist der implizite Vertrag dieses Frameworks. - **Motoranzahl:** 7 Slots (`x, y, z, a, b, c, e` in `RobotMotorPosition`) bleiben fest. Das Framework ist für 6-DOF + Greifer ausgelegt. - **Ordner:** `robot/kinematics/` (nicht `inverseKinematics/` — enthält beide Richtungen) - **Klassenname:** beschreibend für die physikalische Struktur, z. B. `Arm3SegmentLinearX` --- ## Dateistruktur (Zielzustand) ``` robot/ ├── RobotBase.js ← Interface + generische Infrastruktur ├── KinematicsFactory.js ← lädt Kinematik-Klasse anhand Env-Variable └── kinematics/ ├── Arm3SegmentLinearX.js ← aktuelle Implementierung └── .js ← zukünftige Implementierungen ``` > **Update (umgesetzt):** Der ursprünglich geplante dauerhafte Kompatibilitäts-Alias > `robot/Robot.js` wurde **nicht** beibehalten, sondern nach Abschluss von Phase 0–2 > **entfernt**. Tests importieren direkt `kinematics/Arm3SegmentLinearX` bzw. > `RobotBase`; Produktivcode geht über `KinematicsFactory`. Die folgenden Abschnitte > zur Transition über `Robot.js` sind daher historisch. ## Wo ist das Interface? **`RobotBase` ist das Interface** — als abstrakte Basisklasse (JavaScript-Idiom). Es definiert zwei Dinge: 1. Die **Infrastruktur**, die jede Implementierung erbt (State, `sendCommand`, ...) 2. Den **Vertrag**: zwei Methoden, die jede Implementierung überschreiben *muss* `RobotBase` ist die einzige Klasse, die alle anderen Module (`GCode.js`, `InputWS.js`, Sender, ...) kennen müssen. Sie importieren nie eine konkrete Kinematik. ## Wie greift der restliche Code auf den Roboter zu? **Heute und während der Transition:** ``` startRobot.js → require('./robot/Robot') → Robot.js (Alias) → Arm3SegmentLinearX GCode.js → bekommt robot als Parameter → sieht nur RobotBase-Methoden InputWS.js → bekommt robot als Parameter → sieht nur RobotBase-Methoden ``` `GCode.js`, `InputWS.js` und alle Sender erhalten `robot` bereits als Parameter — sie importieren `Robot.js` gar nicht. **Für sie ändert sich nichts.** **Langfristig (nach Abschluss Phase 2):** ``` startRobot.js → KinematicsFactory → instantiiert Arm3SegmentLinearX (oder anderen) robot/Robot.js → dauerhafter Alias für RobotBase (nicht mehr für eine Implementierung) ``` `robot/Robot.js` bleibt erhalten, zeigt aber auf `RobotBase`: ```js // robot/Robot.js — dauerhaft module.exports = require('./RobotBase'); ``` Damit kann externer Code weiterhin `require('./robot/Robot')` schreiben und bekommt die Basisklasse — z. B. für `instanceof`-Checks oder zum Ableiten in Tests. --- ## Phase 0 — `RobotBase` und Interface-Vertrag `Robot.js` enthält heute zwei Dinge: generische Infrastruktur und arm-spezifische Kinematik. Der Schnitt: **`RobotBase` (generisch, nie überschreiben):** - Zustandsvariablen: `x, y, z, phi, theta, psi, e, feedrate, moveRelative` - Motor-Zustand: `xMotor, alpha, beta, a, b, c, eMotor` + Changed-Flags - `sendCommand()`, `createMotorPosition()`, `calculateSpeeds()` - `cmdReceivers`, `savedPoints` **Interface-Vertrag (abstrakt, muss überschrieben werden):** ```js calculateAngles3D() // Workspace → Motorwinkel (schreibt auf this.*) calculatePositionFromMotorAngles() // Motorwinkel → Workspace (schreibt auf this.*) ``` - [x] `robot/RobotBase.js` anlegen — generische Infrastruktur aus `Robot.js` - [x] Beide Kinematik-Methoden in `RobotBase` als Stub mit `throw new Error('not implemented')` - [x] JSDoc: Interface-Vertrag dokumentieren - [x] `rotateAroundAxis()` wandert in `RobotBase` als geschützte Hilfsmethode --- ## Phase 1 — `Arm3SegmentLinearX` als erste Implementierung ``` robot/ ├── RobotBase.js └── kinematics/ └── Arm3SegmentLinearX.js ← bisheriger Robot.js-Kinematik-Teil ``` - [x] `robot/kinematics/Arm3SegmentLinearX.js` anlegen - `class Arm3SegmentLinearX extends RobotBase` - Konstruktor: `constructor(l1, l2, l3)` → `super()` + Längen - `calculateAngles3D()` — unverändert übernommen - `calculatePositionFromMotorAngles()` — unverändert übernommen - [x] `robot/Robot.js` wurde zunächst Kompatibilitäts-Alias und anschließend **entfernt** (siehe Update-Hinweis oben). Tests importieren direkt `./kinematics/Arm3SegmentLinearX`. - [x] Alle bestehenden Tests müssen grün bleiben — kein Verhalten ändert sich (`Robot.Kinematics.RoundTrip.test.js` ist das primäre Sicherheitsnetz) --- ## Phase 2 — Konfiguration über Umgebungsvariable ```yaml # docker-compose.yml environment: ROBOT_KINEMATICS: arm3segmentlinearx ROBOT_KINEMATICS_PARAMS: '{"l1": 250, "l2": 264, "l3": 100}' ``` - [x] `ROBOT_KINEMATICS` — Bezeichner der Kinematik-Klasse (Default: `arm3segmentlinearx`) - [x] `ROBOT_KINEMATICS_PARAMS` — JSON mit Konstruktor-Parametern - [x] `KinematicsFactory.js` (`createRobotFromEnv`), eingebunden in `startRobot.js`: ```js const robot = createRobotFromEnv(processEnv, { l1: 250, l2: 264, l3: 100 }); ``` - [x] Unbekannte Kinematik → klare Fehlermeldung beim Start, kein silent fail - [ ] Neue Variablen ins zentrale Config-Modul aufnehmen (koordinieren mit `ToDo_3_Config`) → **offen:** `ToDo_3_Config` ist noch nicht umgesetzt. Die Factory liest `process.env` vorerst direkt (gleicher Stil wie `ROBOT_DEFAULT_FEEDRATE`); das zentrale Config-Modul kann die beiden Variablen später übernehmen. In `docker-compose.yaml` bereits dokumentiert. --- ## Phase 3 — Zweite Kinematik-Implementierung Erst wenn ein konkreter zweiter Roboter definiert ist. **Umgesetzt: Joy-IT „Grab-It" (Robot02)** als `Arm3SegmentRotaryBase`. - [x] Physikalische Spezifikation dokumentieren (DOF, Achsen, Gelenkreihenfolge) → im JSDoc von `robot/kinematics/Arm3SegmentRotaryBase.js`. 5 Achsen + Greifer: Basis-Yaw, Schulter, Ellbogen, Handgelenk-Pitch, Handgelenk-Roll, Greifer. - [x] `robot/kinematics/Arm3SegmentRotaryBase.js` anlegen — nur die zwei Kinematik-Methoden - [x] RoundTrip-Tests für die neue Implementierung schreiben (`test/Robot.GrabIt.RoundTrip.test.js`) - [x] Prüfen ob die 7 Motor-Slots ausreichen → **ja**: 6 Slots belegt (`xMotor, alpha, beta, a, b, eMotor`), `c` bleibt frei. `RobotMotorPosition` unverändert. - [x] In `KinematicsFactory` registriert (Bezeichner `arm3segmentrotarybase`, Aliase `grabit` / `robot02`). Factory reicht jetzt das vollständige `params`-Objekt als 4. Konstruktor-Argument durch (für `baseHeight`). **Offene Punkte / Annahmen (kein Blocker, aber vor Echtbetrieb zu klären):** - ⚠️ **5-DOF-Constraint:** `phi` (Hand-Azimut) ist an die Position gekoppelt (= Basis-Drehung) und nicht frei. In der Inversen aus `atan2(y,x)` abgeleitet. - ⚠️ **Segmentlängen sind Schätzwerte** (l1=105, l2=98, l3=100, baseHeight=110 mm), abgeleitet aus Reichweite (300 mm) / Höhe (420 mm). Vor Echtbetrieb am Arm messen und per `ROBOT_KINEMATICS_PARAMS` setzen. - ⚠️ **Gelenkmodell** (Pitch/Roll am Handgelenk) folgt der Standardkonfiguration dieser Arm-Klasse; gegen das physische Gerät / die Kalibrieranleitung prüfen. --- ## Abhängigkeiten - Phase 1 ist unabhängig von allen anderen ToDos — kann sofort angegangen werden - Phase 2 koordiniert mit `ToDo_3_Config` - Phase 3 hat keine zeitliche Vorgabe — wird bei Bedarf aufgenommen