Claude: Umbau Robot

This commit is contained in:
chk
2026-06-09 15:51:30 +02:00
parent 2da589dfa3
commit 78185b8bdb
14 changed files with 1017 additions and 221 deletions

View File

@@ -1,114 +1,129 @@
# ToDo 6a — Speed-Steuerung
## Ist-Zustand und Defizite
> ## ✅ Status: erledigt (Pakete 12)
>
> - **Schalter `ROBOT_SPEED_MODE`** (`legacy` Default = exakt wie bisher, `correct` = koordiniert)
> - **`calculateSpeeds()`-NaN-Bug behoben** + `moveTime` eingeführt
> - **Koordinierte Feedrate** im `TelnetSenderGRBL` (Korrekt-Modus), Legacy-Pfad unangetastet
> - Tests: `test/Robot.calculateSpeeds.test.js`, `test/Sender.Telnet.speedMode.test.js`,
> `test/Speed.coordination.test.js`
>
> **Offen:** WS-Sender (Paket 3), `FPoint`-Feedrate (Paket 4 → ToDo_6b).
Die Feedrate-Logik ist auf zwei Ebenen defekt:
## Der Schalter: `ROBOT_SPEED_MODE`
### Ebene 1: `calculateSpeeds()` berechnet NaN
Die Speed-Regelung ist über **eine einzige Umgebungsvariable** umschaltbar:
`Robot.calculateSpeeds(oldPos, newPos)` liest `oldPos.xMotor`, `oldPos.alpha`, `oldPos.beta`
diese Properties existieren in `RobotMotorPosition` **nicht**. Dort heißen sie `x`, `y`, `z`.
Ergebnis: alle `motorSpeeds`-Werte sind `NaN`. Die Methode ist seit ihrer Einführung kaputt
und fällt nur nicht auf, weil `ROBOT_USE_SPEED_CALC` standardmäßig `false` ist.
| Wert | Bedeutung |
|---|---|
| `legacy` (Default) | **Alte Speed-Regelung — alles läuft exakt wie bisher.** Jeder Sender sendet die kartesische Feedrate `F` (aus dem G-Code bzw. `robot.feedrate`) unverändert an alle seine Achsen. |
| `correct` | **Korrekte Speed-Regelung.** Jeder Sender erhält eine eigene, koordinierte Feedrate, sodass alle Controller die Bewegung gleichzeitig beenden. |
```js
// Zeile ~214 Robot.js — falsch:
this.motorSpeeds.x = (this.xMotor - oldPos.xMotor) / time; // oldPos.xMotor → undefined
this.motorSpeeds.y = (this.alpha - oldPos.alpha) / time; // oldPos.alpha → undefined
this.motorSpeeds.z = (this.beta - oldPos.beta) / time; // oldPos.beta → undefined
// Richtig (RobotMotorPosition-Felder):
this.motorSpeeds.x = (this.xMotor - oldPos.x) / time;
this.motorSpeeds.y = (this.alpha - oldPos.y) / time;
this.motorSpeeds.z = (this.beta - oldPos.z) / time;
```yaml
# docker-compose.yml
environment:
ROBOT_SPEED_MODE: legacy # oder: correct
```
### Ebene 2: `motorSpeeds` werden von keinem Sender gelesen
### Garantie für `legacy`
Selbst wenn `calculateSpeeds()` korrekte Werte lieferte, ignorieren beide Sender die
berechneten Geschwindigkeiten vollständig:
Im Legacy-Modus wird der **Sende-Pfad der Sender nicht angefasst**. Die Feedrate-Zeile
entsteht über exakt denselben Code wie bisher. Die bestehenden Sender-Tests
(`Sender.Telnet.test.js`, `Sender.Telnet.caseBackward.test.js`) prüfen die Ausgabe
zeichengenau und sind das Sicherheitsnetz: solange sie grün sind, ist Legacy
byte-identisch zu vorher.
- **`TelnetSenderGRBL`**: sendet `mNew.feedrate` (Kartesisch, mm/min) an alle Achsen gleich
- **`WSSenderGrbl`**: sendet immer `this.maxSpeedF`, ignoriert `mNew.feedrate` komplett
Der Korrekt-Modus fügt **nur zusätzlich** eine alternative Feedrate-Berechnung hinzu,
die ausschließlich greift, wenn `ROBOT_SPEED_MODE=correct`.
Das ist grundsätzlich falsch: Der Roboter hat drei unabhängige GRBL-Controller. Damit die
Fingerspitze mit `F1000 mm/min` fährt, muss jede Achse mit einer **anderen** Winkelgeschwindigkeit
drehen — je nachdem, wie weit sie sich für diesen Schritt bewegt. Alle Achsen mit derselben
Feedrate zu befehlen führt zu nicht-linearen Werkzeugbahnen.
### Abgrenzung zu `ROBOT_USE_SPEED_CALC`
### Ebene 3: `FPoint` kodiert Feedrate hart
```js
// GCode.js FPoint — immer f1000, egal was robot.feedrate ist:
var strGCode = `G90 G1 x${robot.x} ... f1000`
```
`ROBOT_USE_SPEED_CALC` bleibt der interne Schalter dafür, ob `Robot.calculateSpeeds()`
überhaupt rechnet. Der Korrekt-Modus aktiviert diese Berechnung automatisch.
`ROBOT_USE_SPEED_CALC` allein ändert **nicht** die Sender-Ausgabe — nur
`ROBOT_SPEED_MODE=correct` tut das. Damit bleibt bestehendes Verhalten erhalten.
---
## Konzept: Korrekte Feedrate-Verteilung
## Hintergrund: Warum die alte Regelung falsch ist
Gesamtziel: Die Kartesische Feedrate `F` (mm/min) des G-Code-Befehls bestimmt, wie lange
der Bewegungsschritt dauert. Diese Zeit wird auf alle Achsen aufgeteilt.
Der Roboter hat drei unabhängige GRBL/FluidNC-Controller. Im Legacy-Modus bekommen alle
dieselbe kartesische Feedrate (z. B. `f1000`) — unabhängig davon, wie weit sich die
jeweilige Achse in diesem Schritt tatsächlich bewegt. Folge: die Achse mit kurzer
Bewegung ist viel früher fertig als die mit langer Bewegung, die Bewegungen laufen nicht
koordiniert, und die Werkzeugbahn der Fingerspitze ist nicht linear.
**Korrekt:** Die kartesische Feedrate `F` bestimmt die Gesamt-Zeit des Schritts:
```
Zeit = kartesische_Distanz / F_mm_per_min
Bewegungszeit = kartesische_Distanz / F
Achsen-Feedrate[i] = Achsen-Delta[i] / Zeit
(in Grad/min, da GRBL-Achsen typisch in Grad konfiguriert)
Sender-Feedrate = Distanz_dieses_Senders / Bewegungszeit
```
Der Sender kennt seine Achse (`xAxisGrbl`, `yAxisGrbl`, `zAxisGrbl`) und kann die passende
Geschwindigkeit aus `motorPosition.speeds` lesen, wenn diese korrekt befüllt sind.
So braucht jeder Controller dieselbe Zeit → die Bewegungen sind koordiniert.
---
## Behobene Defizite (Implementierung dieses ToDos)
### 1. `calculateSpeeds()` berechnete NaN — behoben
`Robot.calculateSpeeds()` las `oldPos.xMotor`, `oldPos.alpha`, `oldPos.beta` — Felder, die
in `RobotMotorPosition` nicht existieren (dort `x`, `y`, `z`). Ergebnis: `NaN`.
Korrigiert auf `oldPos.x/y/z`. Zusätzlich speichert die Methode jetzt die `Bewegungszeit`
(`moveTime`), die der Sender für die koordinierte Feedrate braucht.
### 2. Koordinierte Feedrate im Sender (Korrekt-Modus)
`TelnetSenderGRBL` berechnet im Korrekt-Modus die Feedrate als
`Distanz_dieses_Senders / moveTime`. Die Distanz wird über `portValue()` ermittelt —
eine reine Funktion, die für jede (GRBL-Port, Roboter-Achse)-Zuordnung den gesendeten
Wert liefert (dieselben Formeln wie der Sende-Pfad, aber als isolierte, testbare Funktion).
---
## Pakete
### Paket 1: `calculateSpeeds()` reparieren
### Paket 1: `calculateSpeeds()` reparieren — ✅ ERLEDIGT
- [ ] Property-Namen korrigieren: `oldPos.x/y/z/a/b/c/e` statt `oldPos.xMotor/alpha/beta/...`
- [ ] Einheit klären: `motorSpeeds` in rad/min oder direkt in Grad/min?
- Sender wandeln Motorwinkel bereits von rad → Grad (`* 180/π`) für Positionen
- Einheitlichste Lösung: `motorSpeeds` ebenfalls in Grad/min speichern, oder
die Umrechnung konsistent im Sender vornehmen
- [ ] Grenzfall: Wenn kartesische Distanz null ist (reine Gelenkbewegung), Distanz
über Handgelenk-Punkt verwenden — das ist bereits so angelegt, aber mit den
falschen Property-Namen
- [ ] Unit-Test: `calculateSpeeds` mit bekannten Werten, prüfen dass keine NaN entstehen
- [x] Property-Namen korrigiert: `oldPos.x/y/z/a/b/c/e`
- [x] `moveTime` berechnen und auf `motorPosition` ablegen
- [x] Unit-Tests: NaN-Freiheit, exakte Werte, Handgelenk-/Finger-Zweige, Guards
(`test/Robot.calculateSpeeds.test.js`)
### Paket 2: `motorSpeeds` in den Sender durchreichen
### Paket 2: Schalter + koordinierte Feedrate — ✅ ERLEDIGT
- [ ] `motorPosition.speeds` korrekt befüllen (passiert bereits via `this.motorPosition.speeds = {...this.motorSpeeds}` nach `calculateSpeeds`)
- [ ] Sender-Interface (`execCommand`): wenn `ROBOT_USE_SPEED_CALC` aktiv und
`motorPosition.speeds[achse]` verfügbar und > 0 → diesen Wert als `F` verwenden
- [ ] Jeder Sender (`TelnetSenderGRBL`, `WSSenderGrbl`) kennt seine Achse und holt
die passende Geschwindigkeit aus `speeds`:
```js
// Beispiel: Sender ist für Achse "a" zuständig
const f = (useSpeedCalc && mNew.speeds.a > 0) ? mNew.speeds.a : mNew.feedrate;
data += ` f${f.toFixed(2)}`;
```
- [ ] Fallback: wenn `ROBOT_USE_SPEED_CALC=false` oder Speeds nicht berechnet →
`mNew.feedrate` wie bisher (Rückwärtskompatibilität)
- [x] `ROBOT_SPEED_MODE`-Schalter (`legacy` Default, `correct`)
- [x] `TelnetSenderGRBL`: koordinierte Feedrate im Korrekt-Modus, Legacy-Pfad unverändert
- [x] `portValue()` als isolierte Funktion, per Kreuzprobe gegen den echten Sende-Pfad getestet
- [x] Korrekt-Modus-Tests + Koordinations-Invariante über alle drei Sender
(`test/Sender.Telnet.speedMode.test.js`, `test/Speed.coordination.test.js`)
### Paket 3: `WSSenderGrbl` Feedrate-Handling vereinheitlichen
### Paket 3: `WSSenderGrbl` — ⬜ OFFEN (Folge-Schritt)
- [ ] `WSSenderGrbl.execCommand()` auf dasselbe Feedrate-Schema wie `TelnetSenderGRBL` umstellen
- aktuell: immer `this.maxSpeedF` → ignoriert das `F` aus dem G-Code-Befehl komplett
- richtig: `mNew.feedrate` verwenden (bzw. per-Achse aus Paket 2)
- [ ] `maxSpeedF` als Obergrenze behalten (Clamp), nicht als feste Ausgabe
- [ ] `WSSenderGrbl` auf denselben Schalter + koordinierte Feedrate bringen
- WS-Sender ist aktuell nicht in `startRobot.js` aktiv → kein Produktiv-Risiko
- Legacy-Verhalten des WS-Senders zunächst unverändert lassen
### Paket 4: Kleinere Korrekturen
### Paket 4: Aufräumen — ⬜ OFFEN (Folge-Schritt)
- [ ] `FPoint` in `GCode.receiveFC()`: `robot.feedrate` statt hardcodierten `1000` speichern
- [ ] `ROBOT_USE_SPEED_CALC`-Flag dokumentieren: was ändert sich mit/ohne Flag?
Klarer Kommentar in `Robot.js` und im README
- [ ] `FPoint` in `GCode.receiveFC()`: `robot.feedrate` statt hardcodiertem `1000`
→ gehört thematisch zur Datei-Logik, wird in **ToDo_6b** behandelt
- [ ] `portValue()` perspektivisch auch im Sende-Pfad nutzen (Dedupe der Formeln);
bewusst aufgeschoben, um den Legacy-Pfad in diesem Schritt nicht anzufassen
### Bekannte Grenze des Korrekt-Modus v1
Die koordinierte Feedrate bildet die euklidische Norm über die Achsen eines Senders.
Mischt ein Sender lineare (mm) und winklige (Grad) Achsen, mischt die Norm diese
Einheiten — dieselbe Vereinfachung, die GRBL bei gemischten Achsen ohnehin macht.
Für eine spätere, einheiten-saubere Behandlung ggf. eigenes ToDo.
## Betroffene Dateien
- `robot/Robot.js` — `calculateSpeeds()` bugfix, Einheitenwahl
- `robot/RobotMotorPosition.js` — ggf. `speeds`-Feld klarer benennen
- `robot/TelnetSenderGRBL.js` — per-Achse Feedrate aus `speeds`
- `robot/WSSenderGrbl.js` — Feedrate auf `mNew.feedrate` umstellen + Clamp
- `robot/GCode.js` — `FPoint`: `robot.feedrate` statt `1000`
- `test/GCode.speed.test.js` — Tests für NaN-freie Berechnung ergänzen
- `robot/Robot.js``calculateSpeeds()` Fix, `moveTime`, Schalter→`useSpeedCalc`
- `robot/RobotMotorPosition.js``moveTime`-Feld
- `robot/TelnetSenderGRBL.js``portValue()`, koordinierte Feedrate, Schalter
- `test/Robot.calculateSpeeds.test.js` — neu
- `test/Sender.Telnet.speedMode.test.js` — neu