y war falsch

This commit is contained in:
chk
2026-06-26 08:27:50 +02:00
parent 29b5f2ae4b
commit 7205b9d913
12 changed files with 382 additions and 140 deletions

View File

@@ -48,12 +48,15 @@ G92 X158.14 Y4.19 Z57.74 A91.85 B-45.46 C-69.92 E21.20
### Y = α (Oberarm) und Z = β (Unterarm) — ABSOLUT
Beide werden **absolut gegen die Horizontale (+y)** gemessen, **nicht** relativ zueinander:
Beide werden **absolut gegen die Horizontale** gemessen, **nicht** relativ zueinander.
Seit **Phase 1** (Weg 2, siehe doc/Info_Koordinaten.md) zeigt **0° nach y**
(Arbeitsrichtung); verifiziert: `FK(α=0, β=0, gerade Hand) → y = 590`.
| Wert | Oberarm (Y) bzw. Unterarm (Z) |
|------|-------------------------------|
| 0° | waagerecht nach +y |
| 90° | senkrecht nach oben |
| Wert | Oberarm (Y) bzw. Unterarm (Z) |
|-------|----------------------------------------|
| 0° | waagerecht nach **y** (Grundstellung) |
| 90° | senkrecht nach oben |
| 180° | waagerecht nach +y |
⚠️ **Z ist der absolute Unterarmwinkel**, nicht der Ellbogen-Knick gegen den Oberarm.
Misst appRobotHoming den Ellbogen relativ zum Oberarm: `Z = Oberarmwinkel + Ellbogen_relativ`.
@@ -75,8 +78,9 @@ Der Driver (IK) erzeugt B nur im Bereich [0°, 180°].
### A = Unterarm-Dreher (Ellbogen-Roll)
A dreht die **Richtung**, in die das Handgelenk knickt, um die Unterarm-Längsachse —
die Knick-**Größe** bleibt dabei gleich. Verifiziert (α=0, β=90, B=90, C=0):
A=0 → Fingerspitze auf der y-Seite, A=90 → x-Seite, A=180 → +y-Seite; Knick konstant 90°.
die Knick-**Größe** bleibt dabei gleich. Verifiziert (α=0, β=90, B=90, C=0), nach Phase 1:
A=0 → Fingerspitze Richtung Schulter (y=160), A=90 → x-Seite (x=90),
A=180 → von der Schulter weg (y=340); Knick konstant 90°.
### C = Hand-Dreher (Roll)

149
doc/Info_Koordinaten.md Normal file
View File

@@ -0,0 +1,149 @@
# Koordinatensystem, Roboter-Aufstellung & Nullstellung
Diese Datei beschreibt
1. das Koordinatensystem,
2. wie der Roboter darin steht,
3. die angestrebte **ideale Nullstellung** und
4. die nötigen Schritte, um den Driver auf diese Konvention zu bringen (**Weg 2:
Modell auf Y drehen**).
> **Hintergrund / Problem:** Das Kinematik-Modell (`Arm3SegmentLinearX`) misst die
> Armwinkel aktuell von **+Y** (α=0 → Arm zeigt nach +y). Der reale Roboter steht und
> arbeitet aber in **Y** (robot.json: `Arm1.skeleton.to = [0,-250,0]`,
> `coordinateSystem.y = "backward"`). Dadurch landet eine fast-waagerechte
> Grundstellung im Driver bei **y ≈ +590** statt **590** — Modell und Hardware sind an
> der y-Achse gespiegelt.
---
## 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):
z
| ■══════════════════════● Arm waagerecht ausgestreckt → Fingerspitze
| Schulter (Ursprung, z=0)
└───────────────────────────────► y (Arbeitsrichtung)
Draufsicht (Blick von oben, z):
y ▲ ● Fingerspitze (Grundstellung y ≈ 590)
│ │
│ │ Arm (Ober- + Unterarm, gestreckt)
│ │
───┼────────────────■─────────────► x (Schiene)
│ Schulter
+y │
```
---
## 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:** Arm **waagerecht voll ausgestreckt entlang y**, Hand **gerade** in
Verlängerung des Unterarms, Greifer geschlossen.
**Ziel:** in dieser Stellung sind **alle Gelenkwinkel = 0°**.
| Achse | Ideal-Wert | Bedeutung |
|--------|-----------|-------------------------------------------|
| X | (frei) | Schienenposition `xMotor` in mm |
| Y (α) | **0°** | Oberarm waagerecht entlang y |
| Z (β) | **0°** | Unterarm waagerecht entlang y (gestreckt) |
| A (a) | **0°** | kein Unterarm-Dreh |
| B (b) | **0°** | Hand gerade |
| C (c) | **0°** | kein Hand-Roll |
| E (e) | **0** | Greifer geschlossen / Referenz |
→ Resultierende Fingerspitze: **(xMotor, (l1+l2+l3), 0) ≈ (x, 590, 0)**.
(Beobachtet ~550; Differenz steckt in der l3-Ableitung / Resthandstellung.)
---
## 4. Ist-Zustand vs. Ideal
| Aspekt | aktuell (Modell +Y) | ideal (Y) |
|----------------------------|---------------------------|-------------------|
| α=0 zeigt nach | +y (FK: y=+410) | y (y=410) |
| Grundstellung Y (α) | ≈175° (=1804,5) | **0°** |
| gerade Hand B (b) | 180° | **0°** |
| neutraler Roll C (c) | 90° (posenabhängig) | **0°** |
| G92 der Grundstellung → y | ≈ +590 | **590** |
Verifiziert per FK: `α=β=0 → y=+410`, `α=β=180 → y=410`. Die +Y/Y-Spiegelung
entspricht `α→180α, β→180β`.
---
## 5. Schritte, um das zu erreichen (Weg 2)
Reihenfolge nach Workflow: **erst Tests (rot), dann Code, dann grün.**
### Phase 1 — y-Flip (behebt den gemeldeten Bug)
1. **Tests schreiben** (zunächst rot):
- G92 der Grundstellung (α=β=a=b=c=0) → erwartet **y ≈ 590** (statt +590).
- Round-Trip: `IK(x=0, y=550, z, φ, θ)``FK` → identische Pose zurück.
2. **`Arm3SegmentLinearX` umstellen** (α/β von y aus messen):
- **FK** (`calculatePositionFromMotorAngles`): y-Komponenten von `vecBizeps` und
`vecUnterarm` negieren; Handgelenk-Vektoren (`n`, `vHand`) und `φ = atan2(vHand.y,…)`
konsistent nachziehen.
- **IK** (`calculateAngles3D`): `pY`, `gamma = atan2(pZ, pY)`, die `n`-Konstruktion
und `φ` entsprechend spiegeln.
3. **Bestehende +Y-Tests auf Y migrieren** (erwartete y-Werte spiegeln).
4. **`doc/Info_G92.md`** aktualisieren: Y/Z-Konvention auf „0° = waagerecht entlang y".
5. Suite grün; Round-Trip + Grundstellung grün.
### Phase 2 — Handgelenk auf Null (optional, für echte All-Zero-Grundstellung)
6. **B-Konvention drehen:** gerade Hand = **0°** statt 180° (`b → 180°b` an der
Schnittstelle; FK/IK + Greifer-Kopplung `eMotor = e b c` mit anpassen).
7. **C-Nullpunkt:** neutral = **0°** statt 90°.
⚠️ Der C↔ψ-Offset ist **posenabhängig** (`acos(cos β · sin a)`); ein global sauberes
`c=0=neutral` braucht ggf. eine tiefere Umparametrierung des Handgelenks — **vor**
der Umsetzung bewerten.
8. Tests + `Info_G92.md` nachziehen.
### Verifikation (Definition of Done)
- G92 der Grundstellung (alle Winkel 0) → Driver meldet **y ≈ 590, z ≈ 0**.
- appRobotHoming kann die **physisch gemessenen Winkel direkt** senden (ohne Spiegelung).
- Volle Test-Suite grün, inkl. Round-Trip und Grundstellungs-Tests.
---
## Anhang: Stand der bisherigen Arbeit
- Der atan2-Fix in der IK (`gamma = Math.atan2(pZ, pY)`, [Arm3SegmentLinearX.js:59](../robot/kinematics/Arm3SegmentLinearX.js#L59))
ist umgesetzt und macht die IK mathematisch für Y-Eingaben korrekt — eine
Voraussetzung für Phase 1. Er betrifft **nicht** den G92/FK-Pfad.
- Die Winkel-Konventionen (B/C/E) sind in [doc/Info_G92.md](Info_G92.md) dokumentiert;
die Y/Z-Konvention dort wird in Phase 1, Schritt 4 angepasst.

View File

@@ -1,21 +0,0 @@
# ToDo 1 — Parsing
## Ziel der Verbesserung
Klare Trennung zwischen G-Code-Parsing und Robotersteuerlogik. Der Parser soll nur lesen und strukturieren, nicht direkt den Roboterzustand verändern.
## Aufgaben
- [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

@@ -1,98 +0,0 @@
# ToDo 8 — Bekannte Bugs
## Ziel
Konkrete, im Code identifizierte Fehler beheben — unabhängig von den Architektur-Refactorings in den anderen ToDo-Dateien.
---
## Bug 1: `TelnetSenderGRBL` — `close`-Event verliert `this`-Kontext ✅ ERLEDIGT
> Behoben im ToDo-2-Refactoring: Der `close`-Handler nutzt jetzt eine Arrow-Function
> (`robot/TelnetSenderGRBL.js`), `this` zeigt korrekt auf die Sender-Instanz.
**Datei:** `robot/TelnetSenderGRBL.js`, Zeile 5457
**Problem:** Das `close`-Event verwendet eine reguläre `function()` statt einer Arrow Function. Dadurch zeigt `this` innerhalb des Handlers auf das EventEmitter-Objekt, nicht auf die `TelnetSenderGRBL`-Instanz. `this.tSocket = null` hat keinen Effekt — nach einer Verbindungstrennung bleibt `tSocket` auf dem alten, ungültigen Objekt.
```js
// Falsch:
this.tSocket.on("close", function () {
this.tSocket = null; // 'this' ist hier NICHT TelnetSenderGRBL
});
// Richtig:
this.tSocket.on("close", () => {
this.tSocket = null;
});
```
---
## Bug 2: `FFirst` und `FLast` sind nicht implementiert
**Datei:** `robot/GCode.js`
**Problem:** `ContainsFilesCommand()` erkennt `FFirst` und `FLast` und leitet sie an `receiveFC()` weiter. `receiveFC()` behandelt sie aber nicht — die Befehle werden stillschweigend ignoriert und es wird nur `getM114` zurückgegeben.
**Erwartetes Verhalten:**
- `FFirst` — Cursor auf den ersten Eintrag der Log-Datei setzen und die Position anfahren
- `FLast` — Cursor auf den letzten Eintrag setzen und die Position anfahren
---
## Bug 3: G92/M92-Mismatch
**Datei:** `robot/GCode.js`
**Problem:** `containsCommand()` erkennt `G92`, aber `receiveGCode()` prüft auf `g[0] == "M92"`. Ein eingehender Befehl `G92 X10` wird als G-Code erkannt, fällt dann aber durch alle Bedingungen in `receiveGCode()`, und löst unbeabsichtigt `calculateAngles3D()` + `sendCommand()` aus, ohne die Position zu setzen.
**Klärungsbedarf:** Ist G92 oder M92 der korrekte Eingabe-Befehl? Beides konsistent machen.
---
## Bug 4: `logs/`-Verzeichnis wird nicht sichergestellt ✅ ERLEDIGT
> Behoben: `initInputWS()` ruft `ensureLogDir()` (`fs.mkdirSync('./logs', { recursive: true })`)
> beim Start auf. `ensureLogDir` ist exportiert und idempotent.
> Test: `test/InputWS.logDir.test.js`.
**Datei:** `server/InputWS.js`, Zeilen 6667 und 7778
**Problem:** `fs.appendFileSync('./logs/gcode_commands.log', ...)` und `fs.appendFileSync('./logs/pings.log', ...)` crashen beim ersten Aufruf, wenn das `logs/`-Verzeichnis nicht existiert.
**Fix:** Beim Start `fs.mkdirSync('./logs', { recursive: true })` aufrufen, z. B. in `startRobot.js` oder am Anfang von `initInputWS`.
---
## Bug 5: Falscher Finitude-Check in `TelnetSenderGRBL.execCommand`
**Datei:** `robot/TelnetSenderGRBL.js`, Zeile 161
**Problem:**
```js
if(this.aAxisGrbl == "x" && mNew.xMotorChanged && Number.isFinite(mNew.y)){
```
Der Check prüft `mNew.y` statt `mNew.x`. Wenn `mNew.x` `NaN` oder `Infinity` wäre, würde das trotzdem durchgehen.
---
## Bug 6: `containsMCode` matcht zu breit ✅ ERLEDIGT
> Behoben: `containsMCode` nutzt jetzt `s === 'M1' || s.startsWith('M1 ')`.
> Test: `test/GCode.containsMCode.test.js`.
> (Hinweis bleibt: Methode wird im Produktivcode noch nicht aufgerufen.)
**Datei:** `robot/GCode.js`, Zeile 12
**Problem:** `s.indexOf('M1') == 0` trifft auch auf `M10`, `M11`, `M12` usw. zu.
```js
// Aktuell:
static containsMCode(s){ return s.indexOf('M1') == 0 }
// Präziser:
static containsMCode(s){ return s === 'M1' || s.startsWith('M1 ') }
```
Hinweis: Diese Methode wird im aktuellen Code nicht aufgerufen — sie hat keine Wirkung, ist aber irreführend.