Claude: Umbau Robot
This commit is contained in:
@@ -1,114 +1,129 @@
|
||||
# ToDo 6a — Speed-Steuerung
|
||||
|
||||
## Ist-Zustand und Defizite
|
||||
> ## ✅ Status: erledigt (Pakete 1–2)
|
||||
>
|
||||
> - **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
|
||||
|
||||
Reference in New Issue
Block a user