# Commands from GCode File — Konzept & Implementierungsplan
## Ausgangslage
Der Browser-Datei-Browser (`appRobotFileservice:2100`) kann Zeilen im aktiven
Programm navigieren. Der **Driver** (`appRobotDriver`) führt G-Code aus und hat
den Roboter angeschlossen.
Aktuell sind die zwei Pfade strikt getrennt:
| Pfad | Wer | Effekt |
|------|-----|--------|
| Browser → `POST /api/active/next` (Fileservice) | nur Browser | Cursor bewegt sich, Datei gespeichert — **kein Roboter** |
| WS-Client → Driver → FCode → Fileservice | externer Controller | Cursor bewegt sich **und** Zeile wird ausgeführt |
Das Ziel: der Browser bekommt einen Modus-Schalter. Im Modus **Senden**
wandert der Cursor nicht nur, sondern der Roboter fährt auch dorthin.
---
## Sicherheit und Netzwerk-Topologie
```
Internet / User
│
▼
appRobotFileservice (Port 2100, erreichbar von aussen)
│ Browser spricht nur hier hin (HTTP/REST)
│
│ lokales Netz (server-seitig)
▼
appRobotDriver (Port 2095, nur im LAN erreichbar)
│
▼
GRBL/FluidNC-Controller → Roboter-Hardware
```
Der Browser verbindet sich **niemals** direkt mit dem Driver. Der Fileservice
ist die einzige Aussenschnittstelle. Die Verbindung zum Driver läuft
server-seitig im lokalen Netz.
---
## UI-Konzept
### Drei Button-Gruppen in einer Zeile (Links · Mitte · Rechts)
```
[ |◀ First ◀ Prev Next ▶ Last ▶| ] [ ✕ Clear ⬇ .gcode ] [ ↕ Navigieren | ▶ Senden ]
Gruppe Links Gruppe Mitte Gruppe Rechts
```
CSS-Ansatz: `.controls-row { display: flex; justify-content: space-between; }`,
jede Gruppe ist ein `
`.
### Schalter Navigieren / Senden
Zwei Buttons als Radiogruppe (einer immer aktiv):
```html
```
CSS-State `.toggle.active` hebt den aktiven Modus hervor (z. B. Hintergrund
`var(--active)`, Rand `var(--accent)`). Der Senden-Button bleibt ausgegraut,
solange keine `DRIVER_WS_URL` konfiguriert ist.
---
## Architektur im Modus "Senden"
### Warum kein FCode an den Driver?
Wenn der Fileservice dem Driver ein FCode schickte (z. B. `FPlus`), würde
der Driver seinerseits über `FCodeClient` wieder `POST /api/active/next` auf
den Fileservice aufrufen — der Cursor bewegt sich zweimal.
### Richtiger Weg: G-Code-Zeile direkt an den Driver-WS
Der Driver-WebSocket (`InputWS.js`) akzeptiert rohe G-Code-Befehle
(`G90 G1 X… Y… A… F…`) direkt, ohne Fileservice-Rückruf. Die Fileservice-Logik:
1. Cursor-Schritt wie bisher (`_gotoIndex()`), liefert die saubere G-Code-Zeile
2. Diese Zeile wird über eine **server-seitige WS-Verbindung** an den Driver gesendet
3. Driver führt Inverse Kinematik aus, schickt an GRBL-Controller, broadcast M114
### Ablauf im Modus "Senden"
```
Browser klickt Next ▶
│
├─[Navigieren]─► POST /api/active/next (Fileservice)
│ Cursor +1, Datei schreibt ;!, Browser rendert Cursor-Position
│
└─[Senden]──────► POST /api/active/next?execute=true (Fileservice)
Fileservice: Cursor +1, holt line = 'G90 G1 X250 Y0 A45 F1000'
Fileservice: sendet line über lokale WS-Verbindung an Driver
Driver: Inverse Kinematik → GRBL-Controller → Roboter fährt
Driver: broadcast M114 (Position) an alle Driver-WS-Clients
Fileservice antwortet mit { cursor, line } — Browser rendert Cursor
```
---
## Nötige Code-Änderungen
### 1. `public/index.html` — JS
**Neuer Zustand (kein WS im Browser):**
```js
let sendMode = false; // false = Navigieren, true = Senden
```
**`step()`-Funktion — Buttons sperren während Ausführung, Fehler anzeigen:**
```js
const STEP_BUTTONS = ['btn-first', 'btn-prev', 'btn-next', 'btn-last'];
function setStepButtonsEnabled(enabled) {
STEP_BUTTONS.forEach(id => {
document.getElementById(id).disabled = !enabled;
});
}
async function step(endpoint) {
setStatus(elActStatus, sendMode ? '⏳ Sende an Roboter…' : '…');
if (sendMode) setStepButtonsEnabled(false); // während Roboterfahrt sperren
try {
const url = sendMode
? `/api/active/${endpoint}?execute=true`
: `/api/active/${endpoint}`;
const r = await apiFetch('POST', url);
setStatus(elActStatus, sendMode
? `✓ Ausgeführt → Cursor ${r.cursor}`
: `Cursor → ${r.cursor}`);
await refresh();
} catch (err) {
// err.message enthält den Driver-Fehlertext (z. B. 'inverse kinematics failed')
setStatus(elActStatus, `Fehler: ${err.message}`, true);
} finally {
if (sendMode) setStepButtonsEnabled(true); // immer wieder freigeben
}
}
```
**Toggle-Buttons:**
```js
document.getElementById('btn-mode-nav').addEventListener('click', () => {
sendMode = false;
document.getElementById('btn-mode-nav').classList.add('active');
document.getElementById('btn-mode-send').classList.remove('active');
});
document.getElementById('btn-mode-send').addEventListener('click', () => {
sendMode = true;
document.getElementById('btn-mode-send').classList.add('active');
document.getElementById('btn-mode-nav').classList.remove('active');
});
```
**Senden-Button beim Start deaktivieren, wenn kein Driver konfiguriert:**
```js
// Beim Start prüfen ob Driver-URL gesetzt ist
const cfg = await apiFetch('GET', '/api/config');
if (!cfg.driverWsUrl) {
document.getElementById('btn-mode-send').disabled = true;
document.getElementById('btn-mode-send').title = 'Driver WS nicht konfiguriert';
}
```
**Layout — `controls` aufteilen:**
```html