Files
appRobotDriver/doc/ToDo_9a_PortRueckrechnung.md
2026-06-11 07:57:51 +02:00

131 lines
5.1 KiB
Markdown
Raw Permalink 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.
# ToDo 9a — Umkehr-Rechnung GRBL-Port → Motorwerte (Herleitung)
> **Status: durchgerechnet und verifiziert.**
> Baustein für ToDo_9 / Paket 4 (Sync-Command). Verifikation:
> `test/Robot.PortInverse.test.js` (15 Tests grün).
## Zweck
Der Sync-Command (ToDo_9, Paket 4) liest die echten Achs-Positionen (`MPos`) der drei
GRBL/FluidNC-Controller und muss daraus die **sieben Motorwerte** des Roboters
rekonstruieren: `xMotor, alpha, beta, a, b, c, eMotor`. Diese werden dann auf den Roboter
geschrieben und per **Vorwärtskinematik** (`calculatePositionFromMotorAngles()`) in die
Pose `x/y/z/phi/theta/psi` überführt.
Dieses Dokument leitet die Umkehrung her und hält das (verifizierte) Ergebnis fest.
## Kernergebnis (vorweg)
**Für die produktive Verkabelung ist die Abbildung Motorwerte → gesendete GRBL-Achswerte
linear und eindeutig umkehrbar.** Es gibt auf Port-Ebene **keine** Mehrdeutigkeit.
> Die B3-Mehrdeutigkeit (Ellbogen oben/unten) steckt ausschließlich in der **kartesischen**
> Inverskinematik `calculateAngles3D()` (Pose → Gelenkwinkel). Der Sync nutzt diese Richtung
> **nicht** — er geht `MPos → Motorwerte → Vorwärtskinematik → Pose`, und beide Schritte sind
> eindeutige Funktionen. **Damit ist der Sync-Pfad als Ganzes eindeutig.**
---
## Ausgangslage
### Produktiv-Verkabelung (`startRobot.js`)
```js
new TelnetSenderClass(baseIP, 2300, 'x', 'y', 'z') // Base
new TelnetSenderClass(elbowIP, 5000, 'a', null, null) // Elbow
new TelnetSenderClass(handIP, 5000, 'c', 'e', 'b') // Hand
```
### Bedeutung der Motor-Felder (`RobotMotorPosition`)
| Feld | Bedeutung |
|---|---|
| `x` | `xMotor` (Schulterposition auf X-Schiene, **mm**) |
| `y` | `alpha` (Schulterwinkel, **rad**) |
| `z` | `beta` (Unterarm-Neigung, **rad**) |
| `a` | `a` (Ellbogen-Dreher, **rad**) |
| `b` | `b` (Handgelenk-Knick, **rad**) |
| `c` | `c` (Hand-Dreher, **rad**) |
| `e` | `eMotor` (Greifer) |
Mit `D = 180/π` (Grad-Faktor).
### Was jeder Controller real sendet (`execCommand`, Produktiv-Verkabelung)
Aus dem Sende-Pfad (`robot/TelnetSenderGRBL.js`) ergeben sich genau **sieben** GRBL-Achswerte:
| Controller | GRBL-Achse | gesendeter Wert | in Motorwerten |
|---|---|---|---|
| **Base** | `x` | `xMotor` | `xMotor` |
| **Base** | `y` | `alpha·D` | `α·D` |
| **Base** | `z` | `(beta alpha)·D` | `(β α)·D` |
| **Elbow** | `x` | `a·D` | `a·D` |
| **Hand** | `x` | `(c b)·D` | `(c b)·D` |
| **Hand** | `y` | `eMotor·D` | `e·D` |
| **Hand** | `z` | `b·D` | `b·D` |
Drei Ports koppeln je zwei Motorwerte (`base.z`, `hand.x`), aber **jeder gekoppelte Wert
wird mit einem unabhängig gelesenen Wert kombiniert** (`alpha` bzw. `b`). Das System ist
damit *unteres Dreieckssystem* → trivial auflösbar.
---
## Herleitung der Umkehrung
Gegeben die GRBL-Readings `base.{x,y,z}`, `elbow.{x}`, `hand.{x,y,z}` (Grad bzw. mm):
```
xMotor = base.x // direkt
alpha = base.y / D
beta = (base.z + base.y) / D // base.z = (β−α)·D ⇒ β = base.z/D + α
a = elbow.x / D
b = hand.z / D
c = (hand.x + hand.z) / D // hand.x = (cb)·D ⇒ c = hand.x/D + b
eMotor = hand.y / D
```
Als Funktion (siehe Test, kann später 1:1 in Paket 4 übernommen werden):
```js
function motorStateFromPorts(r) { // r = { base:{x,y,z}, elbow:{x}, hand:{x,y,z} }
const D = 180 / Math.PI;
return {
xMotor: r.base.x,
alpha: r.base.y / D,
beta: (r.base.z + r.base.y) / D,
a: r.elbow.x / D,
b: r.hand.z / D,
c: (r.hand.x + r.hand.z) / D,
eMotor: r.hand.y / D,
};
}
```
---
## Verifikation
`test/Robot.PortInverse.test.js` — 15 Tests, fünf repräsentative Motorzustände
(Nullstellung, gemischt, negative/große Winkel, gekoppelt `c≈b`):
- **A) Exaktheit gegen `portValue`** (volle Präzision): Rückgewinnung auf 1e-9 genau.
- **B) Gegen den echten Sende-Pfad `execCommand`** (inkl. 2-Dezimal-Rundung der
G-Code-Werte): Rückgewinnung innerhalb der Rundung (Winkel < 1e-3 rad, `xMotor` < 0.02 mm).
- **C) Voll-Kette Sync**: `Ports → motorStateFromPorts → calculatePositionFromMotorAngles`
liefert dieselbe Pose `x/y/z/phi/theta/psi` wie die Original-Motorwerte (auf 1e-6).
---
## Konsequenzen für ToDo_9 / Paket 4
1. **Keine Zweig-Wahl auf Port-Ebene nötig.** Die frühere Annahme (B3-Disambiguierung im
Baustein) trifft auf die Port-Rückrechnung **nicht** zu — sie ist linear und eindeutig.
2. **Rundung beachten.** `MPos` kommt mit endlicher Präzision; gekoppelte Werte (`beta`, `c`)
summieren zwei gerundete Ports → Toleranz im Sub-Promille-Bereich, unkritisch.
3. **Achszahl je Controller.** FluidNC meldet `MPos` für **alle** in seiner Config definierten
Achsen. Genutzt werden nur: Base `x,y,z` · Elbow `x` · Hand `x,y,z`. Beim Parsen die
übrigen (falls vorhanden) ignorieren.
4. **Verkabelungs-Abhängigkeit.** `motorStateFromPorts()` gilt für die aktuelle Verkabelung.
Ändert sich `startRobot.js` (andere Port-Zuordnung), muss die Umkehrung mitgezogen werden —
der Round-Trip-Test (`portValue(motorStateFromPorts(p)) ≈ p`) schützt davor.