Files
appRobotFileservice/doc/draft_filehandeling.md
chk b68bdfa9b4 Initiales Projekt-Skelett appRobotFileservice
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>
2026-06-14 10:12:41 +02:00

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)