ToDos anpassen
This commit is contained in:
@@ -144,9 +144,14 @@ Architektur- und Refactoring-Aufgaben sind in `doc/ToDo_*.md` dokumentiert:
|
||||
| `doc/ToDo_4_GCode.md` | G-Code- und Datei-Handling trennen |
|
||||
| `doc/ToDo_5_API.md` | WebSocket-Antwortlogik strukturieren |
|
||||
| `doc/ToDo_6_RobotController.md` | RobotController-Klasse einführen |
|
||||
| `doc/ToDo_6a_Speed.md` | Speed-Steuerung: `calculateSpeeds()` reparieren, per-Achse Feedrate |
|
||||
| `doc/ToDo_6b_FileHandling.md` | File-Handling: fehlende Befehle, Cursor im Speicher, Fehler-Feedback |
|
||||
| `doc/ToDo_7_Tests.md` | Testabdeckung und Stabilität |
|
||||
| `doc/ToDo_8_Bugs.md` | Bekannte konkrete Bugs |
|
||||
| `doc/ToDo_9_HardwareFeedback.md` | Hardware-Feedback-Loop (GRBL-Antworten, Command-Queue, Positionsabgleich) |
|
||||
| `doc/ToDo_10_VerbindungsVerlust.md` | Verbindungsverlust erkennen, Watchdog, UI-Statusanzeige |
|
||||
| `doc/ToDo_12_InverseKinematikConfig_ROADMAP.md` | Austauschbare Kinematik: RobotBase + Robot7M, Env-Konfiguration |
|
||||
| `doc/ToDo_49_Cleanup.md` | Pre-Release-Cleanup: tote Code, Zertifikate, ToDos, README |
|
||||
|
||||
### Empfohlene Bearbeitungsreihenfolge
|
||||
|
||||
@@ -156,8 +161,11 @@ ToDo_3 Config — Fundament für alles Weitere
|
||||
ToDo_1 Parser ┐
|
||||
ToDo_6 RobotController ┘ zusammen, da eng verzahnt
|
||||
ToDo_4 Datei-Handling — danach, klar abgrenzbar
|
||||
ToDo_6a Speed-Steuerung — calculateSpeeds bugfix, dann Sender-Integration
|
||||
ToDo_6b File-Handling Detail — fehlende F-Befehle, Cursor im Speicher
|
||||
ToDo_2 Sender-Interface — mit Entscheidung: Telnet vs. FluidNC-WebSocket
|
||||
ToDo_9 Hardware-Feedback — baut auf ToDo_2 auf
|
||||
ToDo_10 Verbindungsverlust — baut auf ToDo_2 auf, parallel zu ToDo_9 möglich
|
||||
ToDo_5 API — parallel zu ToDo_2/4 möglich
|
||||
ToDo_7 Tests — begleitend zu allen obigen
|
||||
```
|
||||
|
||||
77
doc/ToDo_10_VerbindungsVerlust.md
Normal file
77
doc/ToDo_10_VerbindungsVerlust.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# ToDo 10 — Verbindungsverlust erkennen und anzeigen
|
||||
|
||||
## Problem
|
||||
|
||||
Wenn der Telnet-Server (oder später die WS-Verbindung) wegbricht — kein Strom, Netzwerkausfall, Firewall-Drop — wird das weder im Container-Log noch auf der Info-Webseite angezeigt. Die Status-Seite zeigt dauerhaft „alles grün", obwohl kein Sender mehr erreichbar ist.
|
||||
|
||||
Die Ursache: TCP-Sockets können „still sterben". Der Socket ist auf Node.js-Seite noch offen, das `close`-Event feuert nicht (z. B. bei Firewall-Drop oder Netzwerk-Timeout), und der aktuelle Health-Check in `InfoServer.js` prüft nur `tSocket != null` — das bleibt `true`.
|
||||
|
||||
## Abgrenzung zu anderen ToDos
|
||||
|
||||
| ToDo | Thema | Überschneidung |
|
||||
|---|---|---|
|
||||
| **ToDo_2** | Reconnect-Strategie, Sender-Interface, `state`-Property | Fundament — ToDo_10 baut darauf auf und ergänzt den Watchdog |
|
||||
| **ToDo_5** | Health-Checks in `/api/status` | Dort angelegt; ToDo_10 füllt die fehlende Tiefe |
|
||||
| **ToDo_9** | Timeout bei ausbleibenden GRBL-`ok`-Antworten | Ergänzend: ToDo_9 = Protokoll-Ebene, ToDo_10 = Verbindungs-Ebene |
|
||||
|
||||
**Was hier neu ist:** aktiver Watchdog-Heartbeat (auch bei scheinbar offenem Socket), vollständige State-Machine mit sichtbarem Reconnect-Zyklus, und UI-Visualisierung in `public/app.js` — das deckt kein anderes ToDo ab.
|
||||
|
||||
**Voraussetzung:** ToDo_2 muss abgeschlossen sein (`state`-Property und Sender-Interface existieren).
|
||||
|
||||
---
|
||||
|
||||
## Hinweis: Das Problem betrifft Telnet und WebSocket gleichermaßen
|
||||
|
||||
Das „stille Sterben" ist kein Telnet-spezifisches Problem — es ist ein TCP-Problem. WebSocket läuft ebenfalls über TCP und hat dieselbe Schwachstelle, wenn kein aktiver Keepalive verwendet wird.
|
||||
|
||||
Der Unterschied liegt im verfügbaren Gegenmittel:
|
||||
|
||||
| Transport | Klasse | Mechanismus |
|
||||
|---|---|---|
|
||||
| **Telnet** | `TelnetSenderGRBL.js` | Applikations-Watchdog: Timer + Schreib-Probe (kein Protokoll-Support) |
|
||||
| **WebSocket** | `WSSenderGrbl.js` | WebSocket Ping/Pong (RFC 6455, Protokollebene) — sauberer und standardisiert; aktuell **nicht** aktiviert |
|
||||
|
||||
`WSSenderGrbl.js` verwaltet das `ws`-WebSocket-Objekt direkt und hat bereits eine State-Machine (`state`, `reconnectAttempt`, `reconnectTimer`). Die Reconnect-Logik bei `close`- und `error`-Events ist implementiert. Was noch fehlt, ist der aktive Ping/Pong-Heartbeat für den Fall des stillen TCP-Todes.
|
||||
|
||||
`FluidNCClient.js` ist ein älterer, paralleler Ansatz und wird von `WSSenderGrbl.js` **nicht** verwendet.
|
||||
|
||||
---
|
||||
|
||||
## Paket 1: Watchdog im Sender
|
||||
|
||||
`WSSenderGrbl.js` hat bereits State-Machine und Reconnect-Logik. Offen bleibt:
|
||||
|
||||
- [ ] Aktiven Heartbeat einführen — nicht nur auf das `close`-Event warten
|
||||
- **WebSocket (`WSSenderGrbl.js`):** WebSocket Ping/Pong aktivieren — `ws.ping()` alle 30 s, bei ausbleibendem Pong Verbindung als tot markieren und Reconnect auslösen
|
||||
- **Telnet (`TelnetSenderGRBL.js`):** Timer + applikationsseitige Schreib-Probe, da kein Protokoll-Support
|
||||
- erkennt „stille" Socket-Tode, bei denen kein `close`-Event feuert
|
||||
- [ ] State-Machine im Sender konsolidieren
|
||||
- Zustände: `connected` | `connecting` | `reconnecting` | `disconnected`
|
||||
- `reconnectAttempt` (Zähler) und `reconnectDelay` (aktuelle Wartezeit) als Properties
|
||||
- bei Verbindungsverlust: `state` → `disconnected`, Reconnect-Zyklus starten (1 min / 2 min Intervall)
|
||||
- [ ] Fehlerzustand und Reconnect-Fortschritt loggbar machen
|
||||
- im Container-Log erkennbar: welcher Sender, welcher Zustand, nächster Versuch wann
|
||||
|
||||
## Paket 2: Status-API vertiefen
|
||||
|
||||
- [ ] `InfoServer.js` `/api/status` liefert echten Sender-Zustand aus der State-Machine
|
||||
- nicht mehr nur `tSocket != null`, sondern `sender.getStatus()` mit `state`, `reconnectAttempt`, `lastSeen`
|
||||
- bei `disconnected` oder `reconnecting`: `health`-Summary auf `degraded` setzen
|
||||
- [ ] `lastSeen`-Timestamp pro Sender führen
|
||||
- wann wurde zuletzt erfolgreich gesendet oder eine Antwort empfangen?
|
||||
- hilft Diagnose: Sender tot seit X Sekunden
|
||||
|
||||
## Paket 3: UI-Visualisierung
|
||||
|
||||
- [ ] `public/app.js` wertet den Sender-Status aus `/api/status` aus
|
||||
- Farb-Indikator pro Sender: grün / gelb (reconnecting) / rot (disconnected)
|
||||
- bei `reconnecting`: Anzeige von Versuchs-Nummer und nächstem Retry-Zeitpunkt
|
||||
- [ ] Statuswechsel sofort sichtbar machen
|
||||
- `/api/status` wird periodisch (z. B. alle 10 s) abgefragt oder per WebSocket-Push aktualisiert
|
||||
|
||||
## Betroffene Dateien
|
||||
|
||||
- `robot/TelnetSenderGRBL.js` (oder künftiger Sender) — Watchdog-Logik, State-Machine
|
||||
- `server/InfoServer.js` — tieferer Status-Abruf via `getStatus()`
|
||||
- `public/app.js` — Visualisierung des Verbindungszustands
|
||||
- `startRobot.js` — keine Änderung erwartet, Sender-Interface bleibt gleich
|
||||
154
doc/ToDo_12_InverseKinematikConfig_ROADMAP.md
Normal file
154
doc/ToDo_12_InverseKinematikConfig_ROADMAP.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# Roadmap 12 — Austauschbare Kinematik
|
||||
|
||||
## Ziel
|
||||
|
||||
Der `appRobotDriver` soll als generischer G-Code-Treiber für beliebige Roboterarme
|
||||
funktionieren. Die Kinematik ist der einzige arm-spezifische Teil — alles andere
|
||||
(G-Code, WebSocket, Sender, Konfiguration) ist roboter-unabhängig.
|
||||
|
||||
## Konventionen (festgelegt)
|
||||
|
||||
- **Workspace-Koordinaten:** `x, y, z, phi, theta, psi` + `e` (Greifer) — gilt für alle
|
||||
6-DOF-Arme mit Greifer. Das ist der implizite Vertrag dieses Frameworks.
|
||||
- **Motoranzahl:** 7 Slots (`x, y, z, a, b, c, e` in `RobotMotorPosition`) bleiben fest.
|
||||
Das Framework ist für 6-DOF + Greifer ausgelegt.
|
||||
- **Ordner:** `robot/kinematics/` (nicht `inverseKinematics/` — enthält beide Richtungen)
|
||||
- **Klassenname:** beschreibend für die physikalische Struktur, z. B. `Arm3SegmentLinearX`
|
||||
|
||||
---
|
||||
|
||||
## Dateistruktur (Zielzustand)
|
||||
|
||||
```
|
||||
robot/
|
||||
├── RobotBase.js ← Interface + generische Infrastruktur
|
||||
├── Robot.js ← dauerhafter Alias: module.exports = RobotBase
|
||||
├── KinematicsFactory.js ← lädt Kinematik-Klasse anhand Env-Variable
|
||||
└── kinematics/
|
||||
├── Arm3SegmentLinearX.js ← aktuelle Implementierung
|
||||
└── <NextRobot>.js ← zukünftige Implementierungen
|
||||
```
|
||||
|
||||
## Wo ist das Interface?
|
||||
|
||||
**`RobotBase` ist das Interface** — als abstrakte Basisklasse (JavaScript-Idiom).
|
||||
Es definiert zwei Dinge:
|
||||
1. Die **Infrastruktur**, die jede Implementierung erbt (State, `sendCommand`, ...)
|
||||
2. Den **Vertrag**: zwei Methoden, die jede Implementierung überschreiben *muss*
|
||||
|
||||
`RobotBase` ist die einzige Klasse, die alle anderen Module (`GCode.js`, `InputWS.js`,
|
||||
Sender, ...) kennen müssen. Sie importieren nie eine konkrete Kinematik.
|
||||
|
||||
## Wie greift der restliche Code auf den Roboter zu?
|
||||
|
||||
**Heute und während der Transition:**
|
||||
```
|
||||
startRobot.js → require('./robot/Robot') → Robot.js (Alias) → Arm3SegmentLinearX
|
||||
GCode.js → bekommt robot als Parameter → sieht nur RobotBase-Methoden
|
||||
InputWS.js → bekommt robot als Parameter → sieht nur RobotBase-Methoden
|
||||
```
|
||||
|
||||
`GCode.js`, `InputWS.js` und alle Sender erhalten `robot` bereits als Parameter —
|
||||
sie importieren `Robot.js` gar nicht. **Für sie ändert sich nichts.**
|
||||
|
||||
**Langfristig (nach Abschluss Phase 2):**
|
||||
```
|
||||
startRobot.js → KinematicsFactory → instantiiert Arm3SegmentLinearX (oder anderen)
|
||||
robot/Robot.js → dauerhafter Alias für RobotBase (nicht mehr für eine Implementierung)
|
||||
```
|
||||
|
||||
`robot/Robot.js` bleibt erhalten, zeigt aber auf `RobotBase`:
|
||||
```js
|
||||
// robot/Robot.js — dauerhaft
|
||||
module.exports = require('./RobotBase');
|
||||
```
|
||||
|
||||
Damit kann externer Code weiterhin `require('./robot/Robot')` schreiben und bekommt
|
||||
die Basisklasse — z. B. für `instanceof`-Checks oder zum Ableiten in Tests.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — `RobotBase` und Interface-Vertrag
|
||||
|
||||
`Robot.js` enthält heute zwei Dinge: generische Infrastruktur und arm-spezifische Kinematik.
|
||||
Der Schnitt:
|
||||
|
||||
**`RobotBase` (generisch, nie überschreiben):**
|
||||
- Zustandsvariablen: `x, y, z, phi, theta, psi, e, feedrate, moveRelative`
|
||||
- Motor-Zustand: `xMotor, alpha, beta, a, b, c, eMotor` + Changed-Flags
|
||||
- `sendCommand()`, `createMotorPosition()`, `calculateSpeeds()`
|
||||
- `cmdReceivers`, `savedPoints`
|
||||
|
||||
**Interface-Vertrag (abstrakt, muss überschrieben werden):**
|
||||
```js
|
||||
calculateAngles3D() // Workspace → Motorwinkel (schreibt auf this.*)
|
||||
calculatePositionFromMotorAngles() // Motorwinkel → Workspace (schreibt auf this.*)
|
||||
```
|
||||
|
||||
- [ ] `robot/RobotBase.js` anlegen — generische Infrastruktur aus `Robot.js`
|
||||
- [ ] Beide Kinematik-Methoden in `RobotBase` als Stub mit `throw new Error('not implemented')`
|
||||
- [ ] JSDoc: Interface-Vertrag dokumentieren
|
||||
- [ ] `rotateAroundAxis()` wandert in `RobotBase` als geschützte Hilfsmethode
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — `Arm3SegmentLinearX` als erste Implementierung
|
||||
|
||||
```
|
||||
robot/
|
||||
├── RobotBase.js
|
||||
└── kinematics/
|
||||
└── Arm3SegmentLinearX.js ← bisheriger Robot.js-Kinematik-Teil
|
||||
```
|
||||
|
||||
- [ ] `robot/kinematics/Arm3SegmentLinearX.js` anlegen
|
||||
- `class Arm3SegmentLinearX extends RobotBase`
|
||||
- Konstruktor: `constructor(l1, l2, l3)` → `super()` + Längen
|
||||
- `calculateAngles3D()` — unverändert übernommen
|
||||
- `calculatePositionFromMotorAngles()` — unverändert übernommen
|
||||
- [ ] `robot/Robot.js` wird zum Kompatibilitäts-Alias für die Übergangsperiode:
|
||||
```js
|
||||
module.exports = require('./kinematics/Arm3SegmentLinearX');
|
||||
```
|
||||
- [ ] Alle bestehenden Tests müssen grün bleiben — kein Verhalten ändert sich
|
||||
(`Robot.Kinematics.RoundTrip.test.js` ist das primäre Sicherheitsnetz)
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Konfiguration über Umgebungsvariable
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
environment:
|
||||
ROBOT_KINEMATICS: arm3segmentlinearx
|
||||
ROBOT_KINEMATICS_PARAMS: '{"l1": 250, "l2": 264, "l3": 100}'
|
||||
```
|
||||
|
||||
- [ ] `ROBOT_KINEMATICS` — Bezeichner der Kinematik-Klasse (Default: `arm3segmentlinearx`)
|
||||
- [ ] `ROBOT_KINEMATICS_PARAMS` — JSON mit Konstruktor-Parametern
|
||||
- [ ] `KinematicsFactory.js` oder direkt in `startRobot.js`:
|
||||
```js
|
||||
const kin = loadKinematics(process.env.ROBOT_KINEMATICS, params);
|
||||
const robot = new kin(params.l1, params.l2, params.l3);
|
||||
```
|
||||
- [ ] Unbekannte Kinematik → klare Fehlermeldung beim Start, kein silent fail
|
||||
- [ ] Neue Variablen ins zentrale Config-Modul aufnehmen (koordinieren mit `ToDo_3_Config`)
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Zweite Kinematik-Implementierung
|
||||
|
||||
Erst wenn ein konkreter zweiter Roboter definiert ist.
|
||||
|
||||
- [ ] Physikalische Spezifikation dokumentieren (DOF, Achsen, Gelenkreihenfolge)
|
||||
- [ ] `robot/kinematics/<Name>.js` anlegen — nur die zwei Kinematik-Methoden
|
||||
- [ ] RoundTrip-Tests für die neue Implementierung schreiben
|
||||
- [ ] Prüfen ob die 7 Motor-Slots ausreichen; falls nicht → `RobotMotorPosition` anpassen
|
||||
|
||||
---
|
||||
|
||||
## Abhängigkeiten
|
||||
|
||||
- Phase 1 ist unabhängig von allen anderen ToDos — kann sofort angegangen werden
|
||||
- Phase 2 koordiniert mit `ToDo_3_Config`
|
||||
- Phase 3 hat keine zeitliche Vorgabe — wird bei Bedarf aufgenommen
|
||||
126
doc/ToDo_49_Cleanup.md
Normal file
126
doc/ToDo_49_Cleanup.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# ToDo 49 — Cleanup vor Auslieferung
|
||||
|
||||
Dieser Cleanup wird als letzter Schritt vor einer Auslieferung durchgeführt, nachdem
|
||||
die funktionalen ToDos abgeschlossen sind. Einige Punkte sind bereits jetzt erledigbar,
|
||||
andere hängen von vorgelagerten ToDos ab.
|
||||
|
||||
---
|
||||
|
||||
## 1. ToDo-Dateien aufräumen
|
||||
|
||||
**Vollständig erledigte ToDos löschen** (alle Punkte `[x]`, keine offenen):
|
||||
|
||||
- [ ] `doc/ToDo_1_Parsing.md` — vollständig erledigt
|
||||
- [ ] `doc/ToDo_2_Anbindung.md` — vollständig erledigt
|
||||
- [ ] `doc/ToDo_5_API.md` — vollständig erledigt
|
||||
- [ ] `doc/ToDo_8_Bugs.md` — vollständig erledigt (nutzt `✅`-Marker statt `[x]`)
|
||||
|
||||
**Teilweise erledigte ToDos bereinigen:**
|
||||
|
||||
- [ ] `doc/ToDo_7_Tests.md` — erledigte Punkte entfernen, nur offene behalten
|
||||
|
||||
**README.md-Tabelle anpassen:**
|
||||
|
||||
- [ ] Zeilen der gelöschten ToDo-Dateien aus der Tabelle entfernen
|
||||
- [ ] Prioritätsreihenfolge im README auf den aktuellen Stand bringen
|
||||
|
||||
---
|
||||
|
||||
## 2. README.md Review
|
||||
|
||||
- [ ] Alle Abschnitte auf Aktualität prüfen — insbesondere nach den Refactorings aus
|
||||
ToDo_1, ToDo_2, ToDo_5
|
||||
- [ ] `robot/Robot.js` beschreiben was es nach ToDo_12 ist (Alias → `RobotBase`)
|
||||
- [ ] Env-Variablen-Liste: `ROBOT_KINEMATICS` und `ROBOT_KINEMATICS_PARAMS` ergänzen
|
||||
(nach ToDo_12 Phase 2)
|
||||
- [ ] Tippfehler `GRBL_ELLBOW_IP` prüfen: ist das Legacy-Absicht oder korrigierbar?
|
||||
- [ ] Laufzeitvoraussetzungen: `logs/`-Verzeichnis-Hinweis prüfen (nach ToDo_3 evtl. obsolet)
|
||||
- [ ] Einmal vollständig lesen und gegen den tatsächlichen Code abgleichen
|
||||
|
||||
---
|
||||
|
||||
## 3. Toter Code entfernen
|
||||
|
||||
Folgende Felder in `robot/Robot.js` werden nirgendwo im Projekt gelesen oder gesetzt
|
||||
(Grep über alle `.js` ohne `node_modules` ergibt nur die Deklaration selbst):
|
||||
|
||||
- [ ] `this.speedX`, `this.speedY`, `this.speedZ` — nie referenziert
|
||||
- [ ] `this.doAnimate` — nie referenziert
|
||||
- [ ] `this.showFunctions` — nie referenziert
|
||||
- [ ] `this.lastCommandSend` / `this.oldCommandTime` — gesetzt, nie gelesen
|
||||
- [ ] `this.savedPoints`, `this.atPointNr` — gesetzt, nie gelesen
|
||||
|
||||
Folgende Methoden in `robot/GCode.js` werden nie aufgerufen:
|
||||
|
||||
- [ ] `containsMCode()` — hat einen Test, wird im Produktivcode aber nie aufgerufen;
|
||||
entweder integrieren oder entfernen (inklusive Test)
|
||||
- [ ] `receiveMCode()` — nie aufgerufen, kann entfernt werden
|
||||
|
||||
---
|
||||
|
||||
## 4. `robot/Robot.js` Alias aufräumen *(abhängig von ToDo_12 Phase 2)*
|
||||
|
||||
- [ ] `robot/Robot.js` zeigt nach Phase 1 auf `Arm3SegmentLinearX` (Übergangsalias)
|
||||
- [ ] Nach Phase 2: auf `RobotBase` umzeigen:
|
||||
```js
|
||||
module.exports = require('./RobotBase');
|
||||
```
|
||||
- [ ] Kommentar in `robot/Robot.js` aktualisieren: Zweck des Alias erklären
|
||||
|
||||
---
|
||||
|
||||
## 5. Sicherheit: Zertifikate und private Schlüssel aus Git entfernen
|
||||
|
||||
Die folgenden Dateien liegen aktuell im Repository und sollten dort **nicht** sein:
|
||||
|
||||
- [ ] `https/localhost.key` — privater Schlüssel
|
||||
- [ ] `https/localhost2.key` — privater Schlüssel
|
||||
- [ ] `https/key.pem` — privater Schlüssel
|
||||
- [ ] `https/localhost.pem` — Zertifikat
|
||||
- [ ] `https/cert_abcd.pfx` — enthält privaten Schlüssel
|
||||
- [ ] Diese Dateien aus Git-History entfernen (`git filter-repo` oder `BFG`)
|
||||
- [ ] `.gitignore` um `https/*.key`, `https/*.pem`, `https/*.pfx`, `https/*.cer` ergänzen
|
||||
- [ ] Eine `https/README.md` oder `https/EXAMPLE.md` anlegen mit Anleitung zur
|
||||
Zertifikatserzeugung (z. B. `openssl` self-signed für lokale Entwicklung)
|
||||
|
||||
---
|
||||
|
||||
## 6. GCodeFiles/ aus Git entfernen
|
||||
|
||||
`GCodeFiles/` enthält Nutzungsprotokolle aus 2025 — das ist Laufzeitdata, kein Quellcode.
|
||||
|
||||
- [ ] `.gitignore` um `GCodeFiles/*.gcode` ergänzen (oder `GCodeFiles/log*.gcode`)
|
||||
- [ ] Eine Beispieldatei `GCodeFiles/example.gcode` ins Repository nehmen
|
||||
- [ ] Bestehende Log-Dateien aus Git-History entfernen (optional, je nach Sensitivität)
|
||||
|
||||
---
|
||||
|
||||
## 7. Naming-Inkonsistenz: `WSSenderGrbl.js`
|
||||
|
||||
- [ ] `WSSenderGrbl.js` exportiert eine Klasse namens `TelnetSenderGRBL` — das ist
|
||||
der alte Name aus der Telnet-Ära
|
||||
- [ ] Klasse in `WSSenderGrbl.js` umbenennen auf `WSSenderGrbl`
|
||||
- [ ] Alle Importe prüfen (aktuell: `startRobot.js`, Tests)
|
||||
|
||||
---
|
||||
|
||||
## 8. Hilfsdateien prüfen
|
||||
|
||||
- [ ] `install.bat` / `runTest.bat` — aktuell und korrekt? Oder löschen?
|
||||
- [ ] `sendTest_Client.py` — gepflegt? Ins README erwähnen oder löschen
|
||||
- [ ] `https/info.txt` — Inhalt prüfen; falls nur interne Notizen: löschen
|
||||
- [ ] `robot/fluidnc/FluidNCClient.js` — wird von `WSSenderGrbl.js` nicht mehr verwendet;
|
||||
entweder löschen oder als "experimentell/archiviert" kennzeichnen
|
||||
|
||||
---
|
||||
|
||||
## 9. Console.log-Hygiene
|
||||
|
||||
Der Code hat viele `console.log`-Aufrufe — einige sind wichtig, andere Debugging-Reste.
|
||||
|
||||
- [ ] `robot/Robot.js` `sendCommand()`: ausführliches Log bei jedem Befehl — für Produktion
|
||||
eher auf `debug`-Level oder hinter eine `verbose`-Flag
|
||||
- [ ] `robot/GCode.js`: Motor-Position-Log in `receiveMCode` (ohnehin toter Code)
|
||||
- [ ] Sinnvolle Logs behalten (Verbindungsaufbau, Fehler, Start-Info)
|
||||
- [ ] Kandidat für späteren Ausbau: leichtgewichtiger Logger statt nacktem `console.log`
|
||||
(z. B. Log-Level via Env-Variable)
|
||||
114
doc/ToDo_6a_Speed.md
Normal file
114
doc/ToDo_6a_Speed.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# ToDo 6a — Speed-Steuerung
|
||||
|
||||
## Ist-Zustand und Defizite
|
||||
|
||||
Die Feedrate-Logik ist auf zwei Ebenen defekt:
|
||||
|
||||
### Ebene 1: `calculateSpeeds()` berechnet NaN
|
||||
|
||||
`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.
|
||||
|
||||
```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;
|
||||
```
|
||||
|
||||
### Ebene 2: `motorSpeeds` werden von keinem Sender gelesen
|
||||
|
||||
Selbst wenn `calculateSpeeds()` korrekte Werte lieferte, ignorieren beide Sender die
|
||||
berechneten Geschwindigkeiten vollständig:
|
||||
|
||||
- **`TelnetSenderGRBL`**: sendet `mNew.feedrate` (Kartesisch, mm/min) an alle Achsen gleich
|
||||
- **`WSSenderGrbl`**: sendet immer `this.maxSpeedF`, ignoriert `mNew.feedrate` komplett
|
||||
|
||||
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.
|
||||
|
||||
### 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`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Konzept: Korrekte Feedrate-Verteilung
|
||||
|
||||
Gesamtziel: Die Kartesische Feedrate `F` (mm/min) des G-Code-Befehls bestimmt, wie lange
|
||||
der Bewegungsschritt dauert. Diese Zeit wird auf alle Achsen aufgeteilt.
|
||||
|
||||
```
|
||||
Zeit = kartesische_Distanz / F_mm_per_min
|
||||
|
||||
Achsen-Feedrate[i] = Achsen-Delta[i] / Zeit
|
||||
(in Grad/min, da GRBL-Achsen typisch in Grad konfiguriert)
|
||||
```
|
||||
|
||||
Der Sender kennt seine Achse (`xAxisGrbl`, `yAxisGrbl`, `zAxisGrbl`) und kann die passende
|
||||
Geschwindigkeit aus `motorPosition.speeds` lesen, wenn diese korrekt befüllt sind.
|
||||
|
||||
---
|
||||
|
||||
## Pakete
|
||||
|
||||
### Paket 1: `calculateSpeeds()` reparieren
|
||||
|
||||
- [ ] 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
|
||||
|
||||
### Paket 2: `motorSpeeds` in den Sender durchreichen
|
||||
|
||||
- [ ] `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)
|
||||
|
||||
### Paket 3: `WSSenderGrbl` Feedrate-Handling vereinheitlichen
|
||||
|
||||
- [ ] `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
|
||||
|
||||
### Paket 4: Kleinere Korrekturen
|
||||
|
||||
- [ ] `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
|
||||
|
||||
## 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
|
||||
113
doc/ToDo_6b_FileHandling.md
Normal file
113
doc/ToDo_6b_FileHandling.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# ToDo 6b — File-Handling
|
||||
|
||||
## Ist-Zustand
|
||||
|
||||
`GCode.receiveFC()` implementiert nur einen Bruchteil der erkannten Befehle:
|
||||
|
||||
| Befehl | Erkannt | Implementiert | Anmerkung |
|
||||
|---|---|---|---|
|
||||
| `FPoint` | ✅ | ✅ | Speichert aktuelle Position |
|
||||
| `FPlus` | ✅ | ✅ | Cursor vorwärts |
|
||||
| `FMinus` | ✅ | ✅ | Cursor rückwärts |
|
||||
| `FShow` | ✅ | ✅ | Gibt Dateiinhalt zurück |
|
||||
| `FFirst` | ✅ | ❌ | Cursor auf erste Zeile |
|
||||
| `FLast` | ✅ | ❌ | Cursor auf letzte Zeile |
|
||||
| `FList` | ✅ | ❌ | Listet verfügbare G-Code-Dateien |
|
||||
| `FLoad <file>` | ✅ | ❌ | Lädt eine andere Datei als aktive |
|
||||
| `FSave <file>` | ✅ | ❌ | Speichert aktive Datei unter neuem Namen |
|
||||
| `FClear` | ✅ | ❌ | Leert die aktive Log-Datei |
|
||||
| `M20` | ✅ | ❌ | List SD/Files |
|
||||
| `M23` | ✅ | ❌ | Select file |
|
||||
| `M28` | ✅ | ❌ | Start write |
|
||||
| `M29` | ✅ | ❌ | Stop write |
|
||||
|
||||
Zusätzliche strukturelle Probleme:
|
||||
|
||||
- **Cursor lebt in der Datei** (`';!'`-Marker): jedes `FPlus`/`FMinus` liest und schreibt die ganze Datei neu
|
||||
- **Synchrones File-IO** (`readFileSync`/`writeFileSync`) blockiert den Node.js-Event-Loop
|
||||
- **Kein Fehler-Feedback** an den WebSocket-Client — Fehler gehen nur auf `console.error`
|
||||
- **Aktive Datei ist statisch**: `static fileName = "GCodeFiles/log.gcode"` — `FLoad` kann das nicht ändern, weil es nicht implementiert ist
|
||||
- **`toPiMultiple()`** ist eine String-Manipulation aus der Vor-Parser-Ära, inkonsistent mit `GCodeParser`
|
||||
- **`FPoint` hardcodiert `f1000`** — sollte `robot.feedrate` verwenden (auch in ToDo_6a)
|
||||
- **Kein Directory-Listing für `FList`** — welche Dateien sind gültig?
|
||||
|
||||
---
|
||||
|
||||
## Konzept
|
||||
|
||||
Die gesamte Datei-Logik wird in eine eigene Klasse `GCodeFileManager` ausgelagert
|
||||
(gemäß ToDo_4). Diese ToDo beschreibt die **inhaltliche Umsetzung** — unabhängig davon,
|
||||
ob sie noch in `GCode.js` oder schon in `GCodeFileManager` landet.
|
||||
|
||||
Der **Cursor** wird aus der Datei in den Speicher verlagert: `currentLineIndex` als
|
||||
Instanzvariable, die nur beim Laden einer Datei initialisiert wird. Das spart permanentes
|
||||
Neu-Schreiben der Datei.
|
||||
|
||||
---
|
||||
|
||||
## Pakete
|
||||
|
||||
### Paket 1: Fehlende Befehle implementieren
|
||||
|
||||
- [ ] **`FFirst`** — `currentLineIndex` auf 0 setzen, erste Zeile anfahren
|
||||
- [ ] **`FLast`** — `currentLineIndex` auf letzte Zeile setzen, anfahren
|
||||
- [ ] **`FClear`** — aktive Log-Datei leeren, `currentLineIndex` auf 0
|
||||
- [ ] **`FList`** — Inhalte von `GCodeFiles/` auflesen, Liste zurückgeben
|
||||
```
|
||||
XYZ__FList__XYZ
|
||||
log.gcode
|
||||
log_2025_10_04.gcode
|
||||
...
|
||||
```
|
||||
- [ ] **`FLoad <dateiname>`** — benannte Datei aus `GCodeFiles/` laden, `currentLineIndex`
|
||||
auf den `;!`-Marker setzen (Rückwärtskompatibilität), oder auf 0 falls kein Marker
|
||||
- [ ] **`FSave <dateiname>`** — aktiven Puffer unter `GCodeFiles/<dateiname>` speichern
|
||||
- [ ] `M20`/`M23`/`M28`/`M29` — Marlin-kompatible Aliases für `FList`/`FLoad`/`FSave`/Stop,
|
||||
oder explizit als „nicht unterstützt" mit Fehlermeldung antworten
|
||||
|
||||
### Paket 2: Cursor in den Speicher verlagern
|
||||
|
||||
Aktuell wird der Cursor als `';!'` direkt in die Datei geschrieben. Das zwingt bei
|
||||
jedem `FPlus`/`FMinus` die gesamte Datei neu zu schreiben.
|
||||
|
||||
- [ ] `currentLineIndex` als Instanzvariable einführen (startet bei 0 oder am `';!'`-Marker beim Laden)
|
||||
- [ ] `FPlus`/`FMinus` nur noch `currentLineIndex` ändern, Datei bleibt unverändert
|
||||
- [ ] `';!'`-Marker beim **Speichern** (`FSave`) optional mitschreiben (für externe Kompatibilität)
|
||||
- [ ] Beim Laden einer bestehenden Datei: Zeile mit `';!'` suchen → `currentLineIndex` setzen,
|
||||
dann `';!'`-Marker aus der Datei entfernen (einmalige Migration)
|
||||
|
||||
### Paket 3: `toPiMultiple` durch Parser-Integration ersetzen
|
||||
|
||||
Gespeicherte Dateien enthalten Winkel in Grad (`a45.00`), der Parser erwartet
|
||||
Zahlenwerte ohne Umrechnungspflicht. Die Konversion muss explizit und testbar sein.
|
||||
|
||||
- [ ] Beim Lesen einer Zeile aus der Datei: erkennen, ob Winkel in Grad oder Rad vorliegen
|
||||
- Marker-Ansatz: `FPoint` könnte einen Einheiten-Kommentar schreiben (`;unit=deg`)
|
||||
- Oder: Konvention festlegen und dokumentieren (Dateien immer in Grad, Anzeige in Grad)
|
||||
- [ ] `toPiMultiple()` durch eine klar benannte Funktion `degreesToRadians(gCodeString)` ersetzen,
|
||||
die intern `GCodeParser.parse()` nutzt statt raw-String-Manipulation
|
||||
- [ ] `FPoint` sollte die Einheit konsistent speichern — Grad ist sinnvoller für Lesbarkeit
|
||||
|
||||
### Paket 4: Fehler-Feedback und Validierung
|
||||
|
||||
- [ ] Bei Dateifehlern (nicht gefunden, Leserechte) → strukturierte Fehlermeldung an den
|
||||
WebSocket-Client zurückgeben (gemäß Fehler-Envelope aus ToDo_5)
|
||||
- [ ] `FLoad` validiert, dass die Zieldatei existiert und mindestens eine gültige G-Code-Zeile enthält
|
||||
- [ ] Dateinamen in `FLoad`/`FSave` sanitizen: keine `../`-Pfade, nur `GCodeFiles/`-Verzeichnis
|
||||
|
||||
### Paket 5: Asynchrones File-IO (optional, später)
|
||||
|
||||
Alle aktuellen `readFileSync`/`writeFileSync`-Aufrufe blockieren den Event-Loop.
|
||||
Bei kleinen Dateien (< 1 MB) ist das tolerierbar. Bei größeren Programmen oder
|
||||
häufigem `FPoint`-Schreiben wird es spürbar.
|
||||
|
||||
- [ ] `FPoint`-Schreiben auf `fs.appendFile` (async) umstellen — Cursor ist im Speicher,
|
||||
kein Lesen nötig
|
||||
- [ ] `FShow`/`FList` auf `fs.readFile` (async) umstellen
|
||||
- [ ] `FLoad`/`FSave` async
|
||||
|
||||
## Betroffene Dateien
|
||||
|
||||
- `robot/GCode.js` (oder künftig `robot/GCodeFileManager.js` nach ToDo_4)
|
||||
- `server/InputWS.js` — Fehler-Routing für File-Befehle
|
||||
- `test/GCode.FileOperation.test.js` — stark erweitern
|
||||
@@ -1 +0,0 @@
|
||||
npm test
|
||||
0
sendTest_Client.py → test/sendTest_Client.py
Executable file → Normal file
0
sendTest_Client.py → test/sendTest_Client.py
Executable file → Normal file
Reference in New Issue
Block a user