Files
appRobotDriver/doc/Info_Koordinaten.md
2026-06-26 11:39:20 +02:00

227 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Koordinatensystem, Roboter-Aufstellung & Nullstellung
Diese Datei beschreibt
1. das Koordinatensystem,
2. wie der Roboter darin steht,
3. die angestrebte **ideale Nullstellung** und
4. die Schritte, um den Driver auf diese Konvention zu bringen (**Weg 2: Modell auf Y**).
> **Status:** **Phase 1 (y-Flip) ist umgesetzt und am Roboter verifiziert.** α/β werden
> jetzt von **y** aus gemessen (α=0 → Arm zeigt nach y), passend zu robot.json und
> appRobotHoming. Offen ist **Phase 2** (Handgelenk-/Finger-Nullstellung, B/C).
---
## 1. Koordinatensystem
Aus robot.json (`coordinateSystem`): **rechtshändig**, Längen **mm**, Winkel **Grad**.
| Achse | Richtung | Bedeutung im Aufbau |
|-------|------------|----------------------------------------------|
| x | rechts | entlang der Linearschiene |
| y | „backward" | Arm-Arbeitsrichtung: Arm streckt nach **y** |
| z | oben | Höhe |
**Seitenansicht** (Blick entlang +x, also die y/z-Ebene):
```
z
| ■═══════════════●===========●---o Arm1 ═══ , Arm2 === und Finger --- waagerecht
| Schulter (Ursprung, z=0) ausgestreckt → Fingerspitze o
└───────────────────────────────► y (Arbeitsrichtung)
```
In der y/z-Seitenansicht liegen beide Handgelenk-Achsen **end-on** (als Punkt ●), weil
sie entlang x verlaufen:
- linkes ● = **a-Achse** (Unterarm-Dreher, zwischen Ellbogen und Arm2)
- rechtes ● = **b-Achse** (Hand-Knick-Achse)
**Draufsicht** (Blick von oben auf z; x nach oben, y nach rechts):
```
x
▲ │ │
│ ■════╪═══ Arm1 ═════════╪══ Arm2 ══----o o = Fingerspitze (y ≈ 590)
│ Schulter
└────────────────────────────────────────────► y
│ = a-Achse (Ellbogen↔Arm2) bzw. b-Achse (Hand-Knick).
Bei a=0 laufen BEIDE parallel zur x-Achse (hier als senkrechte Striche).
```
---
## 2. Wie der Roboter darin steht
Gelenk-Kette (robot.json `links`):
**Board → Base (Schiene x) → Arm1 (Oberarm) → Ellbow → Arm2 (Unterarm) → Hand → Palm → Finger.**
- **Linearschiene** entlang x; die Base fährt darauf (`xMotor`, mm).
- **Schultergelenk** (Base→Arm1, `variable y`): Drehung in der y-z-Ebene → **α**.
Bei Gelenkwinkel 0 zeigt Arm1 entlang **y** (`skeleton.to = [0,-250,0]`).
- **Ellbogen** (`variable z`) → **β**; **Unterarm-Dreher** (`variable a`) → **a**;
**Handgelenk-Knick** (`variable b`) → **b**; **Hand-Roll** (`variable c`) → **c**;
**Greifer** (`variable e`) → **e**.
- **z = 0 im Driver-Modell = Schulterachse.** (In der Welt liegt sie ~45 mm über dem
Brett — robot.json Joint1-Origin z=45 —, der Driver rechnet schulter-relativ.)
- Armlängen (aus `links` abgeleitet): **l1 = 250** (Oberarm), **l2 = 250** (Unterarm),
**l3 ≈ 90** (Hand). Σ ≈ **590 mm**.
---
## 3. Ideale Nullstellung (Grundstellung)
**Definition (deine Vorgabe):**
1. **Arm1 entlang y** (Schulter-/y-Gelenk α) → **α = 0**.
2. **Ellbogen so angewinkelt, dass auch Arm2 entlang y** (z-Gelenk β) → **β = 0**
(β ist absolut, also auch direkt entlang y, nicht relativ zum Oberarm).
3. **a-Achse so gedreht, dass die Hand-Knick-Achse genau in x-Richtung läuft****a = 0**.
Diese drei Bedingungen erfüllt der Driver **nach Phase 1 bereits** (verifiziert, s.u.):
| Achse | Nullwert | Bedeutung | Status |
|--------|----------|---------------------------------------------|-------------------------|
| X | (frei) | Schienenposition `xMotor` in mm | — |
| Y (α) | **0°** | Oberarm waagerecht entlang y | ✅ Phase 1 |
| Z (β) | **0°** | Unterarm waagerecht entlang y (gestreckt) | ✅ Phase 1 |
| A (a) | **0°** | Hand-Knick-Achse ∥ x | ✅ (Code erfüllt es) |
| B (b) | 0° (Ziel) | Hand gerade — **derzeit ist gerade = 180°** | ⏳ Phase 2 |
| C (c) | 0° (Ziel) | kein Hand-Roll — **derzeit neutral ≠ 0** | ⏳ Phase 2 |
| E (e) | **0** | Greifer geschlossen / Referenz | — |
→ Resultierende Fingerspitze (α=β=a=0, gerade Hand): **(xMotor, (l1+l2+l3), 0) ≈ (x, 590, 0)**.
(Beobachtet ~550; Differenz steckt in der l3-Ableitung / Resthandstellung.)
---
## 4. Ist-Zustand (nach Phase 1) vs. Ideal
| Aspekt | nach Phase 1 (jetzt) | Ideal (nach Phase 2) |
|------------------------------|-----------------------------|----------------------|
| α=0 zeigt nach | **y** ✅ | y |
| β=0 Unterarm | **y** ✅ | y |
| a=0 Hand-Knick-Achse | **∥ x** ✅ | ∥ x |
| G92 der Grundstellung → y | **590** ✅ | ≈ 590 |
| gerade Hand | **b = 180°** | **b = 0°** |
| neutraler Roll | **c: ψ = 90° C** (posenabh.) | **c = 0°** |
Verifiziert per FK: `FK(α=0, β=0, gerade Hand) → (0, 590, 0)`; bei `a=0` bleibt
`Fingerspitze.x = xMotor` konstant, während b variiert (Knick in der y-z-Ebene).
---
## 5. Schritte (Weg 2)
Reihenfolge nach Workflow: **erst Tests (rot), dann Code, dann grün.**
### Phase 1 — y-Flip ✅ ERLEDIGT (am Roboter verifiziert)
Umgesetzt:
- **Spiegelung an der x-z-Ebene** in `Arm3SegmentLinearX` (`_mirrorWorkspaceY`): die
interne Mathematik (`_ikPlusY`/`_fkPlusY`) rechnet weiter in +y, die öffentlichen
Methoden spiegeln die Workspace-Pose (y, pY, φ, ψ; θ bleibt) → α=0 zeigt nach y.
- **G28** (Home) auf y umgestellt **und Singularität behandelt**: die voll ausgestreckte
Stellung (`|y| = l1+l2+l3`) ist eine Handgelenk-Singularität, in der die IK `a`/`c` nicht
bestimmen kann (Müll wie `a=135°, c=45°` → Finger schräg). G28 setzt dort die Motorwerte
**direkt** (`alpha=beta=a=c=0`, `b=π` = gerade Hand) und füllt die Pose per FK.
- **Tests:** `test/Robot.Kinematics.NegativeY.test.js` (Grundstellung 590, Homing-Pose
in y, Round-Trip in y, a=0 → Knick-Achse ∥ x).
- **Migration:** `Robot.02_UpperArm` und der G28-Test auf y umgestellt.
- **Doku:** `doc/Info_G92.md` Y/Z (und C/A nach der Spiegelung) aktualisiert.
### Phase 2 — Handgelenk-/Finger-Nullstellung (B, C, Greifer) — OFFEN
> **Voraussetzung (User):** Erst die Finger visualisieren/prüfen — dort werden noch Fehler
> vermutet. **a-Achse ist bereits korrekt** (a=0 → Knick-Achse ∥ x). Phase 2 betrifft
> **B (Knick)**, **C (Roll)** und die **Greifer-Kopplung**.
**Ziel-Konvention:** gerade Hand → **B = 0°** (statt 180°); neutraler Roll → **C = 0°**;
Greifer-Kopplung konsistent und gegen die echte Mechanik kalibriert.
#### Vorab-Erkenntnis aus dem Code (wichtig!)
Die b/c/e-Konvention ist an **mehreren** Stellen kodiert, die **gemeinsam** geändert werden
müssen. **Invariante:** solange die Hardware-Nullpunkte nicht neu kalibriert werden, müssen
die an FluidNC gesendeten Port-Werte **gleich bleiben** — eine reine Modell-Umbenennung darf
die Hardware-Bewegung nicht verändern.
Fundstellen:
| Datei / Stelle | aktuelle Kodierung |
|----------------|--------------------|
| `Arm3SegmentLinearX._fkPlusY` | `vHand = rotate(vecUnterarm, n, b)`; `psi = c acos(n.z)`**b=π = gerade** |
| `Arm3SegmentLinearX._ikPlusY` | `b = acos(cosB)` (∈[0,π]); `c = acos(cosC) + psi`**c hat posenabh. Offset** |
| `Arm3SegmentLinearX.gripperMotorFromOpening` | `eMotor = e b c` (b,c in **rad**) — **Greifer-Kopplung #1** |
| `RobotController` G92/M92 | `b = B/D`, `c = C/D`, `eMotor = gripperMotorFromOpening(e)` |
| `RobotController` M1 | `b += B`, `c += C` (relativer Motor-Jog) |
| `RobotController` G28 | `b = π`, `c = 0` (Phase 1) → nach B-Umstellung auf `b = 0` ändern |
| `portInverse.js` | `b = hand.z/D`, `c = hand.x/D + b` (Port→Motor, Hardware-Sync) |
| `TelnetSenderGRBL.execCommand` / `portValue` | Hand-Ports: `z = b·D`, `x = (cb)·D`; **e-Port mit `factorTurnLift=1.2`****Greifer-Kopplung #2** |
#### Aufgaben
1. **Finger visualisieren** (User) → Soll-Bild, gegen das kalibriert wird.
2. **Greifer-Kopplung vereinheitlichen.** Es existieren **zwei widersprüchliche** Kopplungen:
- Kinematik: `eMotor = e b c` (b,c in rad)
- Sender: `e-Port = e + 1.2·b·D c·D` (b,c in Grad, Faktor **1.2** nur auf b)
Eine Quelle der Wahrheit festlegen und gegen die echte Sehnenmechanik messen.
(`factorOpenTurn = 1.92` im Sender ist deklariert, aber **ungenutzt** → klären/entfernen.)
3. **B-Konvention (gerade = 0°).** Durchgängig:
- FK/IK in `Arm3SegmentLinearX` (b-Definition / acos-Zweig),
- `gripperMotorFromOpening` nachziehen,
- G92-Eingabe (`b = B/D`) + M1 + G28,
- `portInverse.js` (Umkehrung),
- **Sender-Formeln so kompensieren, dass die FluidNC-Ports unverändert bleiben** —
ODER bewusst die Hardware-Nullpunkte neu kalibrieren (Entscheidung dokumentieren).
4. **C-Nullpunkt (neutral = 0°).** Der `c↔ψ`-Bezug ist **posenabhängig**
(`ψ = acos(cos β · sin a) c`). Ein konstantes `c=0=neutral` ist **nicht global** möglich,
ohne die Hand-Parametrierung zu ändern. Bewerten: c als reinen Gelenkwinkel führen
(Offset herausrechnen) oder die ψ-Definition anpassen.
5. **l3-Ableitung korrigieren** (`RobotConfig.js`): `l3` kommt aus
`Ellbow.skeleton.to[0] = 90` (Ellbogen-Versatz), **nicht** aus der echten Hand-/Finger-
Länge — das erklärt die beobachteten 550 statt 590. Aus der echten Finger-Geometrie ableiten.
6. **Tests + Doku** nachziehen: Round-Trip mit neuer Konvention, Greifer-Kopplung,
G92-Referenztabellen in `Info_G92.md`, sowie diese Datei.
#### Ansatz-Entscheidung (vor Umsetzung)
- **Klein/lokal:** nur die **G92-Eingabe** umrechnen (appRobotHoming sendet B=0/C=0 für
gerade/neutral, Driver mappt intern auf die alte Konvention). Wenig Risiko, aber das
interne Modell bleibt „unsauber".
- **Groß/sauber:** interne Konvention durchgängig umstellen (alle Fundstellen oben) mit
Hardware-Port-Invariante. Sauberes All-Zero-Home, aber koordinierter Eingriff.
#### Verifikation
Jede B/C/Greifer-Änderung gegen **Visualisierung UND einen Hardware-Test** (eine Achse
isoliert) prüfen — das Modell allein genügt hier nicht, weil es um die Hardware-Abbildung geht.
### Verifikation (Definition of Done)
- **Phase 1 (erfüllt):** G92 der Grundstellung → Driver `y ≈ 590, z ≈ 0`; appRobotHoming
sendet die gemessenen α/β/a **direkt** (ohne Spiegelung); **G28 fährt sauber gestreckt
nach y** (a=0, kein Singularitäts-Müll); volle Suite grün.
- **Phase 2 (Ziel):** Grundstellung mit **allen** Gelenkwinkeln 0 (inkl. B=C=0); Greifer-
Kopplung vereinheitlicht; Finger visuell korrekt.
---
## Anhang: Stand der Kinematik
- **y-Flip (Phase 1):** Spiegelung in `Arm3SegmentLinearX` (`_mirrorWorkspaceY`, genutzt von
`calculateAngles3D` und `calculatePositionFromMotorAngles`). Am Roboter bestätigt.
- **G28-Singularität (Phase 1):** voll ausgestreckt setzt `RobotController` die Motorwerte
direkt (statt der singulären IK) → Finger sauber entlang y.
- **atan2-Fix** in der IK (`gamma = Math.atan2(pZ, pY)`): macht die interne IK für y
mathematisch korrekt — Voraussetzung des y-Flips.
- **Winkel-Konventionen** (Y/Z/A/B/C/E) sind in [doc/Info_G92.md](Info_G92.md) dokumentiert
und nach Phase 1 aktualisiert.