# robot.json – Zugriff via appRobotDriver

> Stand: 2026-06-15  
> Beschreibt die geplante Umstellung: robot.json kommt vom appRobotDriver, nicht
> mehr aus einer lokalen Datei.

---

## Ist-Zustand

`appRobotHoming` liest und schreibt die Roboter-Konfiguration direkt aus einer
lokalen Datei:

```
ROBOT_JSON = process.env.ROBOT_JSON
          || 'scripts/robot_1781069752019.json'
```

Die Python-Skripte erhalten den Dateipfad als CLI-Argument (`-robot`, `--robot`).
Alle Kalibrierungs-Endpoints schreiben ebenfalls in diese Datei.

**Problem:** Der appRobotDriver besitzt die maßgebliche Konfiguration — nicht das
Homing-System. Nach einem Neustart könnten Konfiguration und Driver auseinanderlaufen.

---

## Ziel-Zustand

```
Startup               GET {ROBOT_URL}/api/robot/config  →  robot.json laden
Kalibrierung schreiben  →  lokal anpassen                →  POST {ROBOT_URL}/api/robot/config
Python-Skripte          →  weiterhin lokale Datei (Cache)  (unverändert)
```

`appRobotDriver` ist die **Single Source of Truth**.  
`appRobotHoming` hält eine **lokale Kopie** (Cache-Datei) nur für die Dauer eines
Laufs — Python-Skripte müssen nicht angepasst werden.

---

## appRobotDriver API (Platzhalter)

Die genaue API ist noch zu klären. Annahmen:

| Aktion | Endpoint | Body / Antwort |
|--------|----------|----------------|
| Konfiguration lesen | `GET {ROBOT_URL}/api/robot/config` | → JSON (robot.json-Inhalt) |
| Konfiguration schreiben | `POST {ROBOT_URL}/api/robot/config` | Body: JSON (robot.json-Inhalt), → `{ ok: true }` |

Sobald die echten Endpoints bekannt sind, diese Tabelle und die Implementierung
(`server/robotConfig.js`) entsprechend anpassen.

---

## Implementierungsplan

### Schritt 1 — `server/robotConfig.js` (neu)

Kapselt den gesamten robot.json-Zugriff. `server.js` und `editRobot.js` importieren
nur noch diese Funktionen — kein direktes `fsPromises.readFile` / `writeFile` mehr.

```javascript
// server/robotConfig.js  (ESM)
import fsPromises from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const ROBOT_URL  = process.env.ROBOT_URL || '';
// Lokale Cache-Datei: bleibt als Fallback und für Python-Skripte
const ROBOT_JSON = process.env.ROBOT_JSON
  || path.join(__dirname, '..', 'scripts', 'robot_1781069752019.json');

/**
 * Lädt robot.json.
 * Reihenfolge: (1) ROBOT_URL/api/robot/config, (2) lokale Datei als Fallback.
 * Schreibt das Ergebnis immer in die lokale Cache-Datei (für Python-Skripte).
 */
export async function fetchRobot() {
  if (ROBOT_URL) {
    const res = await fetch(new URL('/api/robot/config', ROBOT_URL));
    if (!res.ok) throw new Error(`Driver ${res.status}: ${await res.text()}`);
    const data = await res.json();
    // Cache für Python-Skripte aktualisieren
    await fsPromises.writeFile(ROBOT_JSON, JSON.stringify(data, null, 2), 'utf8');
    return data;
  }
  // Fallback: lokale Datei (Entwicklung ohne Driver)
  return JSON.parse(await fsPromises.readFile(ROBOT_JSON, 'utf8'));
}

/**
 * Speichert robot.json.
 * Schreibt immer in lokale Cache-Datei; sendet zusätzlich an Driver wenn konfiguriert.
 */
export async function pushRobot(data) {
  await fsPromises.writeFile(ROBOT_JSON, JSON.stringify(data, null, 2), 'utf8');
  if (ROBOT_URL) {
    const res = await fetch(new URL('/api/robot/config', ROBOT_URL), {
      method:  'POST',
      headers: { 'Content-Type': 'application/json' },
      body:    JSON.stringify(data),
    });
    if (!res.ok) throw new Error(`Driver ${res.status}: ${await res.text()}`);
  }
}

/** Pfad zur lokalen Cache-Datei – wird an Python-Skripte als -robot-Argument übergeben. */
export const robotCachePath = ROBOT_JSON;
```

---

### Schritt 2 — `server/editRobot.js` anpassen

`readRobot()` und `writeRobot()` sind die einzigen I/O-Primitiven in `editRobot.js`.
Sie müssen auf `fetchRobot()` / `pushRobot()` umgestellt werden.

**Aktuell:**
```javascript
async function readRobot(robotPath) {
  return JSON.parse(await fsPromises.readFile(robotPath, 'utf8'));
}
async function writeRobot(robotPath, data) {
  await fsPromises.writeFile(robotPath, JSON.stringify(data, null, 2), 'utf8');
}
```

**Neu:**
```javascript
import { fetchRobot, pushRobot } from './robotConfig.js';

async function readRobot(_robotPath) {  // _robotPath ignoriert – Quelle ist Driver
  return fetchRobot();
}
async function writeRobot(_robotPath, data) {
  return pushRobot(data);
}
```

Alle exportierten Funktionen (`assignByZRange`, `setArmMarkerSpin`, `adoptXAxis`,
`setJointOriginYZ`, …) bleiben **unverändert** — sie rufen intern `readRobot` /
`writeRobot` auf.

> Der `robotPath`-Parameter bleibt in den Signaturen erhalten (Kompatibilität),
> wird aber ignoriert. Alternativ: alle Aufrufer in `server.js` bereinigen und
> Parameter entfernen (Folgeschritt).

---

### Schritt 3 — `server/server.js` anpassen

#### 3a — Python-Skripte erhalten weiterhin die Cache-Datei

```javascript
// Vorher:
import { ROBOT_JSON } from './config.js';  // oder const direkt
// '-robot', ROBOT_JSON

// Nachher:
import { robotCachePath } from './robotConfig.js';
// '-robot', robotCachePath
```

Die Pipeline (`runBoardPipeline`, `runHoming`) fetcht robot.json **einmal vor dem
Lauf** via `fetchRobot()`, um den Cache zu aktualisieren:

```javascript
import { fetchRobot, robotCachePath } from './robotConfig.js';

async function runBoardPipeline(runDir, send, refSet) {
  // Cache aktualisieren bevor Python startet
  await fetchRobot();

  // Python-Skripte erhalten robotCachePath wie bisher
  const script1Args = [..., '-robot', robotCachePath, ...];
  // …
}
```

#### 3b — `GET /api/robot` liest via `fetchRobot()`

```javascript
// Vorher:
app.get('/api/robot', async (req, res) => {
  const robot = JSON.parse(await fsPromises.readFile(ROBOT_JSON, 'utf8'));
  return res.json(robot);
});

// Nachher:
import { fetchRobot } from './robotConfig.js';

app.get('/api/robot', async (req, res) => {
  try {
    const robot = await fetchRobot();
    return res.json(robot);
  } catch (err) {
    return res.status(502).json({ error: `Driver nicht erreichbar: ${err.message}` });
  }
});
```

#### 3c — Kalibrierungs-Endpoints: kein Änderungsbedarf

Da `editRobot.js` intern `readRobot` / `writeRobot` verwendet und diese umgestellt
werden (Schritt 2), propagieren sich alle Kalibrierungs-Schreibvorgänge automatisch
zum Driver. Kein Änderungsbedarf in den einzelnen Endpoints.

---

## Startup-Verhalten

Beim Start von `server.js` einmalig robot.json laden und cachen:

```javascript
// server.js – nach HTTPS-Server-Start
try {
  await fetchRobot();
  console.log('✅ robot.json vom Driver geladen und gecacht.');
} catch (err) {
  console.warn(`⚠ Driver nicht erreichbar – nutze lokale Datei: ${err.message}`);
}
```

---

## Fallback-Verhalten

| Szenario | Verhalten |
|----------|-----------|
| `ROBOT_URL` nicht gesetzt | Nur lokale Datei — Entwicklungsmodus, Driver nicht nötig |
| Driver beim Start nicht erreichbar | Warnung, lokale Cache-Datei wird verwendet |
| Driver während Lauf nicht erreichbar | `pushRobot()` wirft Fehler → Kalibrierungs-Endpoint antwortet 502 |
| Python-Skript schlägt fehl | Kein push nötig (Python schreibt nicht in robot.json) |

---

## Datei-Übersicht nach Umbau

| Datei | Rolle |
|-------|-------|
| `server/robotConfig.js` *(neu)* | `fetchRobot()`, `pushRobot()`, `robotCachePath` |
| `server/editRobot.js` | `readRobot` / `writeRobot` delegieren an `robotConfig.js` |
| `server/server.js` | importiert `robotCachePath` statt lokalem `ROBOT_JSON`; ruft `fetchRobot()` vor Pipelines |
| `scripts/robot_1781069752019.json` | Bleibt als lokale Cache-Datei; **nicht** mehr primäre Quelle der Wahrheit |

---

## Offene Fragen

- [ ] Genaue Endpoints des appRobotDriver für GET / POST robot.json bestätigen
- [ ] Soll der Driver eine Versions-/Konflikterkennung haben (z.B. ETag / `updatedAt`)?
- [ ] Soll `pushRobot()` bei Driver-Fehler still auf lokal-only zurückfallen, oder hard fail?
- [ ] Authentifizierung zwischen appRobotHoming und appRobotDriver nötig?
