Ausgelagertes Programm-/File-Handling (vormals GCode.receiveFC im appRobotDriver, ToDo_4 / ToDo_6b). Express-Service mit .gcode + .json-Storage, aktivem Programm + Cursor, Teaching (FPoint) und Playback. Speicherung in Grad, driver-nativ (Radian) zum Driver. Konzept/API unter doc/draft_filehandeling*.md. Tests: jest (13 gruen). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
293 lines
13 KiB
Markdown
293 lines
13 KiB
Markdown
# Draft — File-Handling als externes Projekt `appRobotFileservice` (Driver als Gateway)
|
|
|
|
> **Status:** Entwurf / Diskussionsgrundlage.
|
|
> **Projekte:** Der **Driver** lebt in `appRobotDriver` (dieses Repo). Das gesamte
|
|
> G-Code-**Programm**-Handling wird in das eigenständige Projekt
|
|
> **`appRobotFileservice`** ausgelagert. Schnittstelle:
|
|
> [`draft_filehandeling_API.md`](draft_filehandeling_API.md).
|
|
> **Verhältnis zu ToDos:** ersetzt den Driver-internen `GCodeFileManager`-Ansatz aus
|
|
> `doc/ToDo_4_GCode.md` und `doc/ToDo_6b_FileHandling.md`.
|
|
> **Übergang darf hart sein** — keine Rückwärtskompatibilität nötig.
|
|
|
|
---
|
|
|
|
## 1. Motivation
|
|
|
|
Heute lebt das Datei-Handling in [`robot/GCode.js`](../robot/GCode.js)
|
|
(`receiveFC`, `ContainsFilesCommand`, `removeStringFromFile`, `toPiMultiple`, der
|
|
`;!`-Cursor) und wird in [`server/InputWS.js`](../server/InputWS.js) gleichberechtigt
|
|
neben den Bewegungs-Befehlen geroutet. Das vermischt zwei Verantwortungen:
|
|
|
|
| | **Bewegung / Hardware** | **Programm-Verwaltung** |
|
|
|---|---|---|
|
|
| Aufgabe | eine G-Code-Zeile → Achsen bewegen | Programme speichern, anzeigen, durchblättern |
|
|
| Zustand | Live-Pose des Roboters | Datei-Inhalte, Cursor, Listen |
|
|
| Echtzeit | ja (Telnet/FluidNC) | nein (Storage-/UI-getrieben) |
|
|
| Gehört zu | **`appRobotDriver`** | **`appRobotFileservice`** |
|
|
|
|
---
|
|
|
|
## 2. Leitprinzip — der Driver ist das einzige Front Door
|
|
|
|
**Vorgabe:** Alle Steuerungen (Joystick, Tastatur, Bilderkennung,
|
|
sensor-gesteuerte Programme …) kennen **nur den Driver**. Sie sprechen die
|
|
appRobotFileservice **niemals direkt** an — nur indirekt, *durch den Driver hindurch*.
|
|
|
|
```
|
|
Steuerungen → Driver → appRobotFileservice
|
|
(nur EINE Verbindung pro Steuerung: zum Driver)
|
|
```
|
|
|
|
Daraus folgt eine **einseitige Abhängigkeit**:
|
|
|
|
```
|
|
Steuerung ──kennt──► Driver ──kennt──► appRobotFileservice
|
|
(Gateway) (passiver Storage-Dienst)
|
|
|
|
• Der Driver hängt von der appRobotFileservice ab (ruft sie).
|
|
• Die appRobotFileservice hängt von NICHTS ab — sie ruft den Driver nie an,
|
|
kennt weder dessen URL noch dessen Pose.
|
|
• Steuerungen brauchen KEINEN neuen Weg: sie reden weiter nur mit dem Driver.
|
|
```
|
|
|
|
> **Abgrenzung:** Gemeint sind **Steuerungen** (Echtzeit-Eingaben). Die
|
|
> **Visualisierungs-/Verwaltungs-UI** der appRobotFileservice ist Teil *jenes*
|
|
> Projekts und darf den Fileservice direkt ansprechen — sie ist keine Steuerung.
|
|
|
|
---
|
|
|
|
## 3. Befehls-Routing im Driver (der „Pass-through")
|
|
|
|
Der Driver klassifiziert jede eingehende Nachricht und routet sie:
|
|
|
|
```
|
|
eingehende Nachricht am Driver (WS :2095 oder POST /api/gcode)
|
|
│
|
|
├─ Bewegung (G…, M1, M92, G92) → lokal ausführen → Pose broadcast
|
|
├─ Status (Ping, M114) → gezielt antworten
|
|
├─ FCode (FShow, FList, FPoint …) → an appRobotFileservice weiterreichen
|
|
└─ sonst → Fehler-Envelope
|
|
```
|
|
|
|
### FCodes — eine Befehlsfamilie wie die G-/M-Codes
|
|
|
|
G-Code kennt `G1`, `G2`, `Gx` und `M1`, `M92`, … . Analog bilden die **FCodes** eine
|
|
eigene Familie für Datei-/Programm-Befehle — **ohne Sonderzeichen**, einfach `F` +
|
|
Wort:
|
|
|
|
| FCode (Steuerung → Driver) | Bedeutung | Driver leitet weiter an |
|
|
|---|---|---|
|
|
| `FList` | Programme auflisten | `GET /programs` |
|
|
| `FShow [id]` | Inhalt anzeigen | `GET /programs/{id}` |
|
|
| `FLoad <id>` | Programm aktiv setzen | `PUT /active` |
|
|
| `FSave <name>` | aktiven Puffer speichern | `POST /programs` |
|
|
| `FClear` | aktives Programm leeren | `POST /active/clear` |
|
|
| `FPoint` | **aktuelle Pose** aufnehmen | `POST /active/points` (Driver hängt Pose an) |
|
|
| `FPlus` / `FMinus` | nächste / vorige Zeile | `POST /active/next` / `/prev` |
|
|
| `FFirst` / `FLast` | an Anfang / Ende | `POST /active/first` / `/last` |
|
|
| `FGoto <n>` | zu Zeile springen | `POST /active/goto` |
|
|
| `FPlay` / `FStop` | durchlaufen / anhalten | `POST /active/play` / `/stop` |
|
|
|
|
**Warum kein Sonderzeichen-Prefix nötig ist:** Eine Bewegungszeile beginnt mit `G`
|
|
oder `M`; ein FCode mit `F`+Buchstabe. Das Feedrate-Wort `F1000` ist `F`+Ziffer und
|
|
steht **nur innerhalb** einer `G`-Zeile, nie am Anfang. Der Router muss also nur
|
|
**am Nachrichtenanfang** prüfen: `F` + Buchstabe → FCode. Damit ist die Familie
|
|
kollisionsfrei — gegen die Lesbarkeit spricht nichts.
|
|
|
|
`FFirst`/`FLast` werden dabei endlich umgesetzt (heute erkannt, aber nicht
|
|
implementiert — vgl. ToDo_6b / Bug 2). Konkrete API:
|
|
[`draft_filehandeling_API.md`](draft_filehandeling_API.md).
|
|
|
|
---
|
|
|
|
## 4. Zwei Datei-Welten — nur eine wandert aus
|
|
|
|
| Welt | Beispiele | Verbleib |
|
|
|---|---|---|
|
|
| **Betriebs-Logs** | `logs/gcode_commands.log`, `logs/pings.log` | **bleibt im Driver** |
|
|
| **G-Code-Programme** | `GCodeFiles/*.gcode` | **wird ausgelagert** (`appRobotFileservice`) |
|
|
|
|
Die Logs betreffen den Hardware-/Verbindungsbetrieb und bleiben. Ausgelagert wird
|
|
ausschließlich `GCodeFiles/` samt Cursor und FCodes.
|
|
|
|
---
|
|
|
|
## 5. Was bleibt im Driver, was wird ausgelagert
|
|
|
|
| Heute (in [`robot/GCode.js`](../robot/GCode.js)) | Ziel | Anmerkung |
|
|
|---|---|---|
|
|
| `receiveGCode` / `containsCommand` / `receiveMCode` | **bleibt** | reine Bewegung |
|
|
| `getM114` / `GET /api/position` | **bleibt** | Pose-Quelle für `FPoint` |
|
|
| `logCommand` / `logPing` | **bleibt** | Betriebs-Logging |
|
|
| Routing der FCodes | **bleibt als dünner Proxy** | neuer Gateway-Zweig in `InputWS` |
|
|
| `receiveFC` (Programm-Logik) | **appRobotFileservice** | Verwaltung |
|
|
| `static fileName`, `;!`-Cursor | **appRobotFileservice** (Cursor: In-Memory-Index, persistiert als `!`-Kommentar) | löst ToDo_6b Paket 2 |
|
|
| `removeStringFromFile` | **entfällt** | nur für den `;!`-Hack nötig |
|
|
| `toPiMultiple` (Grad→Radian) | **entfällt im Driver** → Umrechnung lebt im Fileservice | siehe §7 |
|
|
| Zeilen-String-Bau in `FPoint` | **appRobotFileservice** | Zeilenformat ist Programm-Logik |
|
|
|
|
Im Driver bleibt also: Bewegung, Pose, Logs — **plus ein dünner Proxy-Zweig**, der
|
|
FCodes weiterreicht. Kein `GCodeFiles/`-IO, kein Cursor, **keine** Einheiten-Umrechnung.
|
|
|
|
---
|
|
|
|
## 6. Die zwei Kernabläufe
|
|
|
|
### 6a. Playback (Datei → Roboter)
|
|
|
|
```
|
|
Steuerung → Driver: FPlus
|
|
Driver → Fileservice: POST /active/next (Cursor++)
|
|
Fileservice → Driver: { line: "G90 G1 x310 y444 … a1.5708 …" } (driver-nativ, Radian)
|
|
Driver: receiveGCode(line) → Achsen bewegen
|
|
Driver: Pose-Broadcast an alle WS-Clients
|
|
```
|
|
|
|
Die appRobotFileservice liefert eine **fertig ausführbare, driver-native Zeile**; der
|
|
Driver führt sie über seinen normalen `receiveGCode`-Pfad aus — *keine*
|
|
Sonderbehandlung, *keine* Umrechnung.
|
|
|
|
### 6b. Teaching / Training (Roboter → Datei) — der robotik-spezifische Fall
|
|
|
|
Der Arm wird **per Joystick** bewegt; G-Code ist hier **Ausgabe**. Entscheidend:
|
|
Beim `FPoint` hat der **Driver die Live-Pose bereits lokal**.
|
|
|
|
```
|
|
Steuerung (Joystick) → Driver: G1 …/$J= (Arm bewegen, lokal)
|
|
Steuerung → Driver: FPoint
|
|
Driver: hängt die AKTUELLE Pose an (robot.x … robot.e, feedrate)
|
|
Driver → Fileservice: POST /active/points { pose:{ x,y,z, a,b,c, e }, feedrate }
|
|
Fileservice: Pose → Grad → als G-Code-Zeile persistieren, Cursor ans Ende
|
|
Fileservice → Driver: { index, line }
|
|
Driver → Steuerung: Bestätigung
|
|
```
|
|
|
|
Der Driver ist die Quelle der Wahrheit für die Pose und reicht sie beim Forwarden
|
|
mit. Die appRobotFileservice muss den Driver dafür **nicht** anrufen.
|
|
|
|
---
|
|
|
|
## 7. Einheiten: Driver bleibt Radian, der Fileservice rechnet um
|
|
|
|
Die Datei soll **wie Standard-G-Code aussehen** (Grad, `a-90.00`). Der Driver
|
|
arbeitet intern und am G-Code-Eingang in **Radian** (Beleg: `receiveGCode` setzt
|
|
`robot.phi = A` ohne Umrechnung). Beides ist vereinbar, ohne dass der Driver etwas
|
|
umrechnen muss:
|
|
|
|
| Achse | `.gcode`-Datei (Storage) | Wire Driver ↔ Fileservice | Driver intern |
|
|
|---|---|---|---|
|
|
| `x y z` | mm | mm | mm |
|
|
| `a b c` (φ/θ/ψ) | **Grad** (`a-90.00`) | **Radian** | Radian |
|
|
| `e` (Greifer) | **Grad** | **Radian** | Radian |
|
|
| Umrechnung | — | **in der appRobotFileservice** | **keine** |
|
|
|
|
- **Driver:** rechnet nie um — `toPiMultiple` **entfällt** ersatzlos (harter Übergang).
|
|
- **appRobotFileservice:** konvertiert an ihrer **Storage-Grenze**: beim Lesen für
|
|
Playback Grad→Radian, beim `FPoint`-Schreiben Radian→Grad. Damit liegt die
|
|
Umrechnung an genau **einer** Stelle und ist testbar (löst ToDo_6b Paket 3).
|
|
|
|
So bleibt die Datei standardnah und lesbar, der Hot-Path im Driver aber sauber.
|
|
|
|
---
|
|
|
|
## 8. Storage-Modell der appRobotFileservice: GCode-Datei + JSON-Sidecar
|
|
|
|
Ziel: am Ende stehen **Dateien, die wie G-Code aussehen** (möglichst nah an einem
|
|
Standard). Pro Programm:
|
|
|
|
```
|
|
GCodeFiles/
|
|
besteck_spuelmaschine.gcode ← das Programm, sieht aus wie Standard-G-Code (Grad)
|
|
besteck_spuelmaschine.json ← Sidecar: Metadaten + Verwaltung
|
|
```
|
|
|
|
- **`.gcode`** (alternativ `.ngc`): standardnahe Bewegungszeilen, Winkel in **Grad**.
|
|
Zeitstempel **und** Cursor stehen im **G-Code-Kommentarfeld** (`;…`) — so bleibt die
|
|
Zeile standardkonform (Kommentare sind Teil des G-Code-Standards):
|
|
- jede Zeile endet mit `;<epoch>` (Aufnahme-Zeitstempel),
|
|
- die **Cursor-Zeile** trägt zusätzlich ein `!`: `;<epoch>!`.
|
|
|
|
```
|
|
G90 G1 x0 y300 z0 a90.00 b-90.00 c0.00 e0.00 f1000 ;1759566014
|
|
G90 G1 x310 y444 z0.5 a90.00 b-90.00 c0.00 e6.88 f1000 ;1759566112!
|
|
G90 G1 x310 y444 z30.5 a90.00 b-90.00 c0.00 e6.88 f1000 ;1759566118
|
|
```
|
|
|
|
Damit ist die `.gcode` **ohne Sidecar vollständig** (Bewegung + Zeitstempel + Cursor).
|
|
- **`.json`-Sidecar** (Komfort/Verwaltung): Anzeigename, `createdAt`/`updatedAt`,
|
|
`lineCount`, `angleUnit` (`"deg"`), optional benannte Labels (`"pick"`, `"place"`
|
|
→ Zeilenindex). Quelle der Wahrheit für Bewegung/Zeitstempel/Cursor bleibt die `.gcode`.
|
|
|
|
Nach außen (API) werden Programme über **id/Name** angesprochen, **nie über
|
|
Dateipfade** — `GCodeFiles/` und das Sidecar-Schema bleiben **intern** in der
|
|
appRobotFileservice. Damit entfällt die `../`-Pfad-Problematik (ToDo_6b Paket 4) und
|
|
ein späterer Wechsel des Storage bleibt unsichtbar.
|
|
|
|
---
|
|
|
|
## 9. Gemeinsamer Zustand: aktives Programm + Cursor (im Fileservice)
|
|
|
|
Die appRobotFileservice hält genau einen **„aktives Programm + Cursor"**-Zustand als
|
|
*Single Source of Truth*. Weil alle Steuerungen durch denselben Driver auf denselben
|
|
Fileservice gehen, teilen sie automatisch denselben Cursor — `FPlus` vom Joystick und
|
|
gleich darauf `FPlus` von der Bilderkennung sehen denselben Stand.
|
|
|
|
- `aktivesProgramm` — id/Name (ersetzt `static fileName`).
|
|
- `cursor` — während einer Session **Zeilenindex im Speicher** (schnelles Stepping
|
|
ohne Neu-Schreiben). Beim Laden aus dem `!`-Kommentar gelesen, beim Speichern/
|
|
Entladen als `!` in die Cursor-Zeile zurückgeschrieben — so ist der Cursor
|
|
persistiert, **ohne** bei jedem `FPlus` die ganze Datei neu zu schreiben (löst
|
|
ToDo_6b Paket 2).
|
|
|
|
---
|
|
|
|
## 10. `/api/gcode` & WS — der Steuerungs-Kanal
|
|
|
|
`POST /api/gcode` am Driver (optional, REST-Alternative zur WS) und die WS `:2095`
|
|
sind der **Bewegungs-Eingang für alle Steuerungen**:
|
|
|
|
- **Zugriff: alle Steuerungen** (Joystick, Tastatur, Bilderkennung, Sensorik).
|
|
- **Nicht** die appRobotFileservice — sie pusht nie Bewegung an den Driver; der
|
|
Driver führt Playback-Zeilen selbst aus (§6a). Der Fileservice braucht **keinen**
|
|
Driver-Zugang.
|
|
|
|
---
|
|
|
|
## 11. Durchgereichte Payload-Größen
|
|
|
|
Der Driver reicht bei `FShow`/`FList` ggf. größere Mengen durch (Datei-Inhalt,
|
|
Listen). Das ist akzeptabel: die **appRobotFileservice** hält diese Antworten später
|
|
klein (z. B. Paginierung, Kurz-/Übersichtsform), sodass der Durchreich-Weg über den
|
|
Driver unkritisch bleibt.
|
|
|
|
---
|
|
|
|
## 12. Erforderliche kleine Driver-Ergänzungen
|
|
|
|
1. **`InputWS`-Router:** neuer Zweig „FCode am Anfang (`F`+Buchstabe) → an Fileservice
|
|
forwarden, Antwort zurückreichen". Playback-Zeile lokal ausführen; Verwaltungs-
|
|
Antworten gezielt an den Anfrager, Pose-ändernde Aktionen broadcasten (analog ToDo_5).
|
|
2. **`FPoint`-Pose:** Der Driver muss die **Live-Pose inkl. Greifer `e`** (und φ/θ/ψ)
|
|
mitliefern. Heute setzt `getM114` `e` hart auf `0.0` — sonst geht die
|
|
Greiferstellung beim Aufnehmen verloren.
|
|
3. **`POST /api/gcode`** (optional): REST-Bewegungs-Eingang für Steuerungen ohne WS.
|
|
|
|
---
|
|
|
|
## 13. Offene Fragen
|
|
|
|
- **FCode-Namen:** bestehende Familie (`FPlus`/`FMinus` …) beibehalten oder einzelne
|
|
umbenennen (`FNext`/`FPrev`)? — Empfehlung: bestehende behalten, neue ergänzen.
|
|
- **Cursor-Persistenz:** als `!`-Kommentar in der `.gcode` (gewählt) — Häufigkeit des
|
|
Zurückschreibens (sofort vs. debounced beim Entladen) noch offen.
|
|
- **Sidecar-Umfang:** Metadaten + Labels (Cursor & Zeitstempel liegen in der `.gcode`).
|
|
|
|
---
|
|
|
|
## 14. Verweise
|
|
|
|
- [`draft_filehandeling_API.md`](draft_filehandeling_API.md) — appRobotFileservice-Schnittstelle
|
|
- [`ToDo_4_GCode.md`](ToDo_4_GCode.md) · [`ToDo_6b_FileHandling.md`](ToDo_6b_FileHandling.md) — abgelöst/gelöst
|
|
- [`ToDo_5_API.md`](ToDo_5_API.md) / [`API.md`](API.md) — Routing & Fehler-Envelope
|
|
- [`robot/GCode.js`](../robot/GCode.js) · [`server/InputWS.js`](../server/InputWS.js) · [`server/InfoServer.js`](../server/InfoServer.js)
|