Line Sender

This commit is contained in:
chk
2026-06-15 09:22:41 +02:00
parent b7d742b9a4
commit 635b8bd1f3
14 changed files with 1050 additions and 217 deletions

183
README.md
View File

@@ -2,55 +2,51 @@
Programm-/File-Handling-Service für den AppRobot. Speichert **G-Code-Programme**
(`.gcode` + `.json`-Sidecar), hält das **aktive Programm + Cursor** und unterstützt
**Teaching** (Pose aufnehmen) und **Playback** (Programm abspielen).
Dieses Projekt wurde aus dem `appRobotDriver` ausgelagert (vormals `GCode.receiveFC`,
ToDo_4 / ToDo_6b). Konzept und Schnittstelle:
[`doc/draft_filehandeling.md`](doc/draft_filehandeling.md) ·
[`doc/draft_filehandeling_API.md`](doc/draft_filehandeling_API.md).
**Teaching** (Pose aufnehmen), **Playback** (Programm abspielen) sowie einen
**Browser-basierten Datei-Browser** auf Port 2100.
## Rolle in der Architektur
```
Steuerungen → appRobotDriver → appRobotFileservice
(Joystick, …) (Gateway) (dieses Projekt, passiv)
Steuerungen → appRobotDriver → appRobotFileservice ← Browser (Port 2100)
(Joystick, …) (Gateway) (dieses Projekt)
[Senden-Modus, lokales Netz]
└──► appRobotDriver WS
```
- Steuerungen kennen **nur den Driver**. Datei-Befehle (**FCodes** wie `FList`,
- **Steuerungen** kennen nur den Driver. Datei-Befehle (**FCodes** wie `FList`,
`FPoint`, `FPlus`) schickt die Steuerung an den Driver, der sie als REST-Aufrufe
hierher weiterreicht.
- Dieser Service ist **passiv und driver-agnostisch**: er ruft den Driver nie an,
kennt weder dessen URL noch dessen Pose. Beim `FPoint` schickt der **Driver die
Pose mit**.
- **Playback:** dieser Service liefert die nächste Zeile **driver-nativ (Radian)**
zurück; **ausgeführt** wird sie vom Driver.
- **Browser** spricht direkt mit dem Fileservice (Port 2100). Im **Senden-Modus**
sendet der Fileservice die G-Code-Zeile server-seitig an den Driver — der Browser
verbindet sich nie direkt mit dem Driver.
- **Playback**: dieser Service liefert die nächste Zeile **driver-nativ (Radian)**
zurück; ausgeführt wird sie vom Driver.
## Einheiten (wichtig)
- **Gespeichert** wird in **Grad** (standardnahe `.gcode`, lesbar): `a/b/c/e` in Grad,
- **Gespeichert** wird in **Grad** (Standard-G-Code, lesbar): `a/b/c/e` in Grad,
`x/y/z` in mm.
- **Am Wire / zum Driver** ist alles **driver-nativ**: `a/b/c/e` in **Radian**.
- Die Umrechnung passiert **ausschließlich hier** (`src/gcode/units.js`) — der Driver
rechnet nie um.
- Die Umrechnung passiert **ausschließlich hier** (`src/gcode/units.js`).
## Dateiformat
`.gcode` ist die **einzige verbindliche Positions-Abfolge** — reiner Standard-G-Code,
nur der Aufnahme-Zeitstempel steht im **Kommentarfeld** (`;…`, standardkonform):
Zeitstempel und Cursor-Marker stehen im Kommentarfeld:
```
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 z0.5 a90.00 b-90.00 c0.00 e6.88 f1000 ;1759566112!
```
- `;<epoch>` = Aufnahme-Zeitstempel. Sonst nichts Service-Internes in der `.gcode`.
- `<id>.json` ist ein Sidecar mit **Zusatz-Infos**: Name, Zeiten, `lineCount`,
`angleUnit` und der **`cursor`** (Index der zuletzt angefahrenen Zeile).
- Der Cursor lebt zur Laufzeit als In-Memory-Index (schnelles Stepping ohne
Neuschreiben) und wird beim Speichern/Entladen ins `.json` geschrieben — die
`.gcode` bleibt sauber.
- Migration: alte `.gcode`-Dateien mit `!`-Cursor-Marker werden beim ersten Lesen
automatisch übernommen (Marker raus, Cursor ins `.json`).
- `;<epoch>` = Aufnahme-Zeitstempel.
- `;!` (Ausrufezeichen am Ende des Kommentars) = **Cursor-Marker**: zeigt die
zuletzt angefahrene Zeile. Primäre Cursor-Quelle beim Lesen. Direkt im Texteditor
sichtbar.
- `<id>.json` ist ein Sidecar mit Metadaten (Name, Zeiten, `lineCount`, `angleUnit`).
`cursor` im Sidecar dient nur als Fallback für Altdateien ohne `;!`-Marker.
## Start
@@ -60,74 +56,101 @@ npm start # http://localhost:2100
npm test # jest
```
HTTP (kein TLS) — der Service ist intern (Driver → Service). TLS kann später analog
zum Driver-`InfoServer` ergänzt werden.
## API (Kurzüberblick)
Vollständig in [`doc/draft_filehandeling_API.md`](doc/draft_filehandeling_API.md).
| FCode (am Driver) | Endpoint |
|---|---|
| `FList` | `GET /api/programs` |
| `FShow [id]` | `GET /api/programs/:id` |
| `FSave <name>` | `POST /api/programs` |
| `FLoad <id>` | `PUT /api/active` |
| `FClear` | `POST /api/active/clear` |
| `FPoint` | `POST /api/active/points` |
| `FPlus` / `FMinus` | `POST /api/active/next` / `/prev` |
| `FFirst` / `FLast` | `POST /api/active/first` / `/last` |
| `FGoto <n>` | `POST /api/active/goto` |
| `FPlay` / `FStop` | `POST /api/active/play` / `/stop` |
Beispiel:
```bash
# Teaching: leeres Programm aktiv, Pose aufnehmen, speichern
curl -X PUT localhost:2100/api/active -H 'content-type: application/json' -d '{"id":"demo_c"}'
curl -X POST localhost:2100/api/active/points -H 'content-type: application/json' \
-d '{"pose":{"x":0,"y":300,"z":0,"a":1.5708,"b":-1.5708,"c":0,"e":0},"feedrate":1000}'
curl -X POST localhost:2100/api/programs -H 'content-type: application/json' -d '{"name":"Demo C","fromActive":true}'
# Playback: laden, erste Zeile (Radian) holen → der Driver führt sie aus
curl -X PUT localhost:2100/api/active -H 'content-type: application/json' -d '{"id":"demo_c"}'
curl -X POST localhost:2100/api/active/first
```
> Hinweis: `PUT /api/active` legt ein nicht existierendes Programm **leer an** (für
> Teaching). `EMPTY_PROGRAM`/`CURSOR_OUT_OF_RANGE` betreffen nur das Stepping/Playback.
HTTP (kein TLS) — der Service ist intern. Konfiguration via Env-Variablen (s. u.).
## Konfiguration (Env)
| Variable | Default | Zweck |
|---|---|---|
| `FILE_SERVICE_PORT` | `2100` | Port |
| `STORAGE_DIR` | `./GCodeFiles` | Verzeichnis für `.gcode` + `.json` |
| `STORAGE_DIR` | `./GCodeFiles` | Wurzel-Verzeichnis für `.gcode` + `.json` |
| `FILE_EXT` | `gcode` | `gcode` oder `ngc` |
| `STORE_ANGLE_UNIT` | `deg` | Speichereinheit der Winkel |
| `FILE_API_KEY` | | Bearer-Token für Schreibzugriffe (fehlt → offen, Dev) |
| `DRIVER_WS_URL` | | WS-URL des appRobotDriver (z. B. `wss://appRobotDriver:2095`); leer → Senden-Modus deaktiviert |
| `DRIVER_TIMEOUT_MS` | `10000` | Maximale Wartezeit auf Driver-Antwort (ms) |
## API (Kurzüberblick)
### Programme
| Endpoint | Methode | Zweck |
|---|---|---|
| `GET /api/programs?dir=` | GET | Programme auflisten |
| `POST /api/programs` | POST | Programm anlegen |
| `DELETE /api/programs/:id?dir=` | DELETE | Programm löschen |
| `GET /api/programs/:id/download` | GET | `.gcode`-Datei herunterladen |
### Ordner
| Endpoint | Methode | Zweck |
|---|---|---|
| `GET /api/folders?dir=` | GET | Unterordner auflisten |
| `POST /api/folders` | POST | Unterordner anlegen |
| `DELETE /api/folders/:name?dir=` | DELETE | Unterordner (rekursiv) löschen |
### Aktives Programm
| FCode (am Driver) | Endpoint | Hinweis |
|---|---|---|
| `FList` | `GET /api/programs` | |
| `FLoad <id>` | `PUT /api/active` | `{ id, dir }` |
| `FClear` | `POST /api/active/clear` | |
| `FPoint` | `POST /api/active/points` | |
| `FPlus` / `FMinus` | `POST /api/active/next` / `/prev` | `?execute=true` → auch an Driver senden |
| `FFirst` / `FLast` | `POST /api/active/first` / `/last` | `?execute=true` → auch an Driver senden |
| `FGoto <n>` | `POST /api/active/goto` | |
| `FPlay` / `FStop` | `POST /api/active/play` / `/stop` | |
| — | `DELETE /api/active/lines/:index` | Zeile löschen |
### Senden-Modus (`?execute=true`)
`POST /api/active/next?execute=true` bewegt den Cursor UND sendet die G-Code-Zeile
an den Driver (server-seitig). Der Endpoint wartet auf die Driver-Antwort:
- **Erfolg** (M114-Broadcast) → HTTP 200 `{ cursor, line, driverPos }`
- **Driver-Fehler** → HTTP 502 `{ error, message }`
- **Timeout** → HTTP 504
### Konfiguration
| Endpoint | Zweck |
|---|---|
| `GET /api/health` | Liveness-Check |
| `GET /api/config` | `{ driverConfigured: bool }` — ob `DRIVER_WS_URL` gesetzt ist |
## Projektstruktur
```
index.js Einstiegspunkt (startet den Server)
index.js Einstiegspunkt (startet den Server)
src/
config.js Env-Konfiguration
server.js Express-App (createApp)
errors.js Fehler-Envelope + Middleware
auth.js Bearer-Auth (für Schreibzugriffe)
gcode/units.js Grad↔Radian, Zeilenformat, Cursor-/Kommentar-Helfer
store/fileStore.js .gcode + .json Persistenz (id-basiert, kein Pfad-Zugriff)
active/activeState.js aktives Programm + Cursor (Single Source of Truth)
routes/programs.js /api/programs*
routes/active.js /api/active*
test/ jest (units, fileStore, activeState)
doc/ Konzept + API (Drafts)
GCodeFiles/ Programm-Storage (zur Laufzeit)
config.js Env-Konfiguration (inkl. driverWsUrl, driverTimeoutMs)
server.js Express-App (createApp, /api/config, /api/health)
errors.js Fehler-Envelope + Middleware
auth.js Bearer-Auth (für Schreibzugriffe)
driverClient.js WS-Client zum Driver (Senden-Modus)
gcode/units.js Grad↔Radian, Zeilenformat, Cursor-Marker (;!)
store/fileStore.js .gcode + .json Persistenz (mit dir-Support, max. 5 Ebenen)
active/activeState.js Aktives Programm + Cursor (Singleton, ;!-Persistenz)
routes/programs.js /api/programs* inkl. /download
routes/active.js /api/active* inkl. ?execute=true
routes/folders.js /api/folders*
public/
index.html Browser-UI (Datei-Browser + Stepping + Senden-Modus)
index.css Design-System (dark theme, CSS-Variablen)
test/
units.test.js Einheiten + Cursor-Marker-Funktionen
fileStore.test.js Persistenz-Tests (;!-Marker, dir-Support)
activeState.test.js ActiveState-Tests (Teaching, Stepping, Cursor-Persistenz)
driverClient.test.js WS-Client (Erfolg, Fehler, Timeout)
doc/
fileBrowser.md Browser-UI Ist-Zustand
commandsFromGCodeFile.md Senden-Modus Konzept + Implementierungsplan
draft_filehandeling.md Original-Konzept (historisch)
draft_filehandeling_API.md Original-API-Entwurf (historisch)
GCodeFiles/ Programm-Storage zur Laufzeit
```
## Status
Erste lauffähige Umsetzung. Offen u. a.: WebSocket-Event-Kanal (Live-Cursor),
Playlists („nächste File"), benannte Labels im Sidecar, TLS. Siehe „Offene Fragen"
in [`doc/draft_filehandeling.md`](doc/draft_filehandeling.md).
Produktiv einsetzbar. Offen: Upload via Browser, Inline-Editor, Live-Updates via SSE
(statt 5s-Polling). Siehe [`doc/fileBrowser.md`](doc/fileBrowser.md).