# Cocktailmaschine – Dokumentation für Einsteiger

Diese Anleitung erklärt dir Schritt für Schritt, wie deine Cocktailmaschine funktioniert, wo du was einstellst (insbesondere das WLAN!) und wie der Code aufgebaut ist. Du brauchst keine Rust-Vorkenntnisse, um die wichtigsten Dinge anzupassen.

---

## 1. Was ist die Cocktailmaschine?

Dieses Projekt ist die Steuerung deiner Cocktailmaschine. Ein ESP32-Mikrocontroller bewegt über zwei Schrittmotoren einen Schlitten (X-Achse) und eine zweite Achse (Z-Achse). Auf dem Schlitten steht das Glas. Der Schlitten fährt unter einen von **6 Getränke-Slots**; dort drückt die Z-Achse gegen einen Release-Mechanismus, der pro Hub ca. **20 ml** ins Glas abgibt. Slot 6 kann stattdessen eine **Pumpe** sein (über ein Relais an GPIO 19 geschaltet). Am Ende fährt die Maschine zurück zum Nullpunkt (Referenzfahrt) – dort entnimmst du das Glas.

Bedient wird die Maschine über eine Webseite, die der ESP32 selbst ausliefert – du brauchst also keine App, nur einen Browser auf Handy oder PC im selben WLAN. Die komplette Konfiguration (Slot-Positionen, Z-Höhe, Getränke-Rezepte) lernst du über die Webseite ein; sie wird **dauerhaft auf dem ESP32 gespeichert** (NVS-Flash) und überlebt Stromausfall und Neustart.

So spielt alles zusammen:

```
   Dein Handy / PC (Browser)
            │  WLAN
            ▼
   ┌─────────────────────────┐
   │  ESP32 (Steuerung)      │
   │                         │
   │  Kern 0: WLAN + Web-    │
   │          server         │
   │       │ "Briefkasten"   │
   │       ▼ (Channel)       │
   │  Kern 1: Motorsteuerung │
   └─────┬──────────┬────────┘
         │          │
     X-Motor     Z-Motor
    (Schlitten)  (2. Achse)
     + Endschalter je Achse
```

Der ESP32 hat zwei Prozessorkerne. Kern 0 kümmert sich um WLAN und Webserver, Kern 1 ausschließlich um die Motoren – so kommen die Schritt-Impulse präzise getaktet, auch wenn gerade jemand die Webseite lädt.

---

## 2. Projektstruktur – welche Datei macht was?

| Datei | Aufgabe |
|---|---|
| `src/main.rs` | Startpunkt des Programms: WLAN verbinden, Webserver mit allen Endpunkten starten, Pins zuordnen, gespeicherte Konfiguration laden, Motorsteuerung starten. |
| `src/servo.rs` | Die Motorsteuerung: arbeitet Befehle ab, erzeugt die Schritt-Impulse, schaltet die Pumpe, enthält die Sicherheitslogik (Endlagen, Homing-Abbruch, NOT-AUS …). |
| `src/settings.rs` | Datenmodell der einlernbaren Konfiguration (Slots, Z-Höhe, Ausgabe, Rezepte) und der Bauplan, der aus einem Rezept die Befehlsfolge für ein Getränk erzeugt. |
| `src/config.rs` | **Deine zentrale Einstellungsdatei**: Verfahrwege, Geschwindigkeits-Limits, Sicherheits-Grenzen, Rezept-Limits. |
| `src/servo-control-migrated.html` | Die Bedienoberfläche (Webseite). Sie wird beim Kompilieren fest in die Firmware eingebaut. |
| `.cargo/config.toml` | Build-Einstellungen – **und hier trägst du dein WLAN ein!** |
| `Cargo.toml` | Liste der verwendeten Bibliotheken (vergleichbar mit einer Zutatenliste). |
| `sdkconfig.defaults` | Grundeinstellungen des ESP32-Betriebssystems (ESP-IDF). |
| `build.rs`, `rust-toolchain.toml` | Technische Helferlein für den Build – musst du nie anfassen. |

---

## 3. WLAN einrichten (die häufigste Frage!)

Die WLAN-Zugangsdaten stehen **nicht** im Rust-Code, sondern in der Datei **`.cargo/config.toml`** im Abschnitt `[env]`:

```toml
[env]
WIFI_SSID = "DEIN_WLAN_NAME"
WIFI_PASS = "DEIN_WLAN_PASSWORT"
```

So gehst du vor: Im Projekt liegt eine Vorlage **`.cargo/config.toml.example`**. Kopiere sie einmalig zu `.cargo/config.toml` (also ohne `.example`), trage dort bei `WIFI_SSID` den Namen deines WLANs und bei `WIFI_PASS` das Passwort ein, speichere die Datei und flashe die Firmware neu (`cargo run --release`). Die Werte werden beim Kompilieren fest in die Firmware "eingebrannt" – deshalb reicht es nicht, nur die Datei zu ändern, es muss immer neu kompiliert und geflasht werden.

```bash
cp .cargo/config.toml.example .cargo/config.toml   # danach WIFI_SSID / WIFI_PASS ausfüllen
```

Im Code (`main.rs`) werden die Werte an dieser Stelle gelesen:

```rust
const SSID: &str = env!("WIFI_SSID");
const PASSWORD: &str = env!("WIFI_PASS");
```

`env!` bedeutet: "Lies diesen Wert beim Kompilieren aus der Umgebungsvariable" – und genau diese Variablen setzt der `[env]`-Abschnitt der `.cargo/config.toml`.

**Wichtig:** Lade die `.cargo/config.toml` mit eingetragenem Passwort nicht öffentlich hoch (z. B. auf GitHub), denn dein WLAN-Passwort steht dort im Klartext. Die mitgelieferte `.gitignore` schließt `.cargo/config.toml` deshalb bereits aus; veröffentliche nur die Vorlage `.cargo/config.toml.example`.

### Was passiert nach dem Einschalten?

Der ESP32 versucht bis zu 5-mal, sich mit deinem Router zu verbinden. Danach ist die Maschine erreichbar unter `http://cockimate/` oder unter ihrer IP-Adresse, die beim Start im Seriellen Monitor angezeigt wird (Zeile "WLAN verbunden! IP-Adresse: …").

**Notfall-WLAN:** Klappt die Verbindung nicht (falsches Passwort, Router aus), öffnet die Maschine automatisch ein eigenes WLAN namens **"Cockimate"** (Passwort: `cocktail123`). Verbinde dich damit und öffne `http://192.168.71.1/` – so kommst du immer an die Steuerung, auch ohne Router. Name und Passwort dieses Notfall-WLANs kannst du in `src/config.rs` ändern.

**Standalone-Modus (ganz ohne Router):** Du kannst die Maschine auch dauerhaft ihr eigenes WLAN aufspannen lassen – ideal fürs Tablet, wenn kein Router in der Nähe ist. Dazu in der Web-App unter **Einrichtung → Netzwerk** den WLAN-Modus auf „Standalone" stellen und „Speichern & neu starten" drücken. Nach dem Neustart kommt die Maschine sofort (ohne Router-Suche) als WLAN „Cockimate" hoch, erreichbar unter `http://192.168.71.1/`. Zurückstellen auf „Router" geht genauso. Die Einstellung wird dauerhaft im ESP32 gespeichert.

---

## 4. Kompilieren und Flashen

Voraussetzungen (einmalig): Rust mit der ESP-Toolchain (`espup install`) sowie `espflash` und `ldproxy` (`cargo install espflash ldproxy`). Eine ausführliche Anleitung findest du im offiziellen "Rust on ESP"-Buch: https://docs.espressif.com/projects/rust/book/

Dann genügt im Projektordner:

```
cargo run --release
```

Das kompiliert die Firmware, flasht sie über USB auf den ESP32 und öffnet direkt den Seriellen Monitor, in dem du alle Log-Meldungen siehst (WLAN-Status, IP-Adresse, Motor-Befehle, Sicherheits-Warnungen).

---

## 5. Bedienung der Web-App

Öffne `http://cockimate/` (oder die IP-Adresse) im Browser. Standardmäßig – so wie es ein Gast am Tablet sieht – ist **nur die Getränke-Auswahl sichtbar**, dazu in der Kopfzeile der Verbindungsstatus, der Maschinenzustand (Bereit / In Bewegung / Pausiert / Referenzfahrt nötig), ein dezentes **Zahnrad ⚙️** und der NOT-AUS. Das Verwaltungsmenü (Einrichtung, Manuell, Skripte) ist bewusst versteckt. Erst ein Tipp auf das Zahnrad und die Eingabe des Passworts (Standard `1234`) blenden die vier Reiter **🍹 Getränke**, **⚙️ Einrichtung**, **🎮 Manuell** und **📜 Skripte** ein. Das Zahnrad wird dann zum Schloss 🔒 – damit sperrst du das Menü wieder und bist zurück in der aufgeräumten Gästeansicht (ideal fürs Tablet).

### 5.1 Reiter „Getränke" – der Alltag

Hier tippst du einfach auf eine Getränke-Kachel (Icon + Name), bestätigst kurz das Rezept – und die Maschine legt los. Ein Banner zeigt den Fortschritt; wenn es „Fertig! 🥂" meldet, kannst du das Glas entnehmen. Ist die Maschine noch nicht referenziert, macht sie die Referenzfahrten automatisch vor der Ausgabe. Fehlt noch etwas an der Einrichtung (Slot nicht eingelernt, Z-Höhe fehlt), bekommst du einen klaren Hinweis statt einer Fahrt ins Blaue.

**Beispiel-Getränke beim ersten Start:** Auf einer frisch geflashten Maschine (leerer Speicher) sind schon ein paar Getränke mit „Beispiel:" im Namen hinterlegt – nur als Vorschlag, damit du siehst, wie Rezepte aussehen. Lerne zuerst unter „Einrichtung" deine Slot-Positionen und die Z-Höhe ein; danach kannst du die Beispiele bearbeiten oder löschen und eigene Getränke anlegen.

**Referenzieren direkt vom Hauptscreen:** Muss die Maschine referenziert werden (nach dem Einschalten oder einem NOT-AUS), erscheint oben ein auffälliger Gold-Balken „Referenzfahrt nötig – hier tippen" und das kleine 🏠-Symbol in der Kopfzeile pulsiert. Ein Tipp darauf fährt **erst Z frei und dann X** zu den Endschaltern – das darf auch ein Gast selbst auslösen, ganz ohne Passwort. Das 🏠-Symbol ist immer da, falls man zwischendurch neu referenzieren möchte.

### 5.2 Reiter „Einrichtung" – einmalig einlernen (passwortgeschützt)

Die Reiter **Einrichtung, Manuell und Skripte sind mit einem Passwort geschützt** (Standard: `1234`). Beim ersten Antippen erscheint eine Passwortabfrage; nach korrekter Eingabe bleiben alle drei Bereiche bis zum nächsten Neuladen der Seite entsperrt. Ohne Passwort sehen Gäste nur die Getränke-Auswahl und den NOT-AUS. Das Passwort änderst du in `src/servo-control-migrated.html` (Konstante `SETUP_PASSWORD`). **Hinweis:** Das ist eine Bedien-Sperre gegen versehentliches Verstellen, kein echter Zugriffsschutz – das Passwort steht im Seiten-Quelltext und die HTTP-Schnittstelle selbst ist nicht gesperrt.

Hier konfigurierst du alles direkt über die Webseite; gespeichert wird **auf der Maschine** (NVS), nicht im Browser:

1. **Referenzieren:** Mit „🏠 X referenzieren" / „🏠 Z referenzieren" beide Achsen auf den Nullpunkt fahren.
2. **Slots einlernen:** Mit dem Fahr-Panel (±10/±100/±1000, auch Pfeiltasten) den Schlitten unter Slot 1 fahren und „📍 X übernehmen" drücken – die aktuelle Position wird übernommen. Das für alle 6 Slots wiederholen. Pro Slot wählst du außerdem den Typ: **Mechanismus** (Z drückt) oder **Pumpe** (z. B. Slot 6). Bei einem Pumpen-Slot erscheint zusätzlich das Feld **„Z-Position der Pumpe"** – fahre Z auf die gewünschte Höhe (z. B. damit die Düse ins Glas taucht) und übernimm sie. Vor dem Pumpen fährt Z dann automatisch dorthin.
3. **Z-Release-Höhe:** Z so weit hochfahren, dass der Mechanismus sicher auslöst, und übernehmen. Diese Höhe gilt für alle Mechanismus-Slots (Z ist ja bei allen etwa gleich).
4. **Ausgabe:** Standardmäßig endet jedes Getränk mit einer Referenzfahrt – der Nullpunkt ist zugleich die Entnahmeposition und gleicht ohne Encoder alle Schrittverluste aus. Optional kannst du eine eigene **Ausgabeposition (Position 7)** einlernen, die nach der Referenzfahrt angefahren wird.
5. **Zeiten & Dosierung:** Haltezeit oben (wie lange Z gegen den Mechanismus drückt), Pause zwischen Hüben, **Rückhub zwischen Hüben** und die Pumpenlaufzeit pro Dosis (≈20 ml) einstellen. Der Rückhub bestimmt, wie weit Z zwischen zwei Dosier-Hüben zurückfährt: Ein kurzer Weg (z. B. 1000 Schritte) genügt, damit der Mechanismus zurückfedert – Z muss dafür **nicht** bis zum Endschalter zurück. Das macht die Dosierung deutlich schneller. Dann **„Einstellungen speichern"**.
6. **Getränke anlegen:** Name, Icon und das Rezept als Liste „Slot × Hübe" (1 Hub ≈ 20 ml, bei Pumpen-Slots läuft stattdessen die Pumpe entsprechend länger). Dann **„Getränkeliste speichern"**. In einem späteren Release können die Icons durch echte Fotos ersetzt werden – die Bedienlogik (Bild antippen) bleibt gleich.

### 5.3 Reiter „Manuell" und „Skripte"

**Manuell** bietet die gewohnte Direktsteuerung: Achse wählen (X/Z/Beide), absolute Position, schrittweises Fahren oder Referenzfahrt, dazu Pause/Fortsetzen/Reset/Sync und Status-Details. **Nach einem NOT-AUS drückst du hier „Fortsetzen".**

Im Reiter **Skripte** kannst du weiterhin freie Befehlsfolgen als JSON bauen, im Browser speichern und abspielen – inklusive des neuen `pump`-Befehls. Für normale Getränke brauchst du das nicht mehr; die Rezepte unter „Einrichtung" sind der bequemere Weg.

Der große rote **NOT-AUS-Knopf** ist immer sichtbar (auch die **ESC-Taste** löst ihn aus). Er stoppt sofort beide Motoren und die Pumpe, verwirft alle wartenden Befehle und pausiert die Achsen. Zum Weiterfahren drückst du danach "Fortsetzen" (Reiter Manuell).

Tastatur-Kürzel: `ESC` = NOT-AUS, `1/2/3/4` = Reiter wechseln, `Pfeiltasten` = Achse fahren (Einrichtung: ←/→ = ±100, ↑/↓ = ±10; Manuell: Schrittweite), `Leertaste` = Pause, `S` = Sync, `H` = Referenzfahrt (im Homing-Modus).

---

## 6. Die HTTP-Schnittstelle (API)

Die Web-App spricht mit der Firmware über einfache Web-Adressen. Du kannst diese auch direkt im Browser oder aus eigenen Programmen aufrufen:

| Adresse | Methode | Bedeutung | Beispiel |
|---|---|---|---|
| `/` | GET | Bedienoberfläche | `http://cockimate/` |
| `/estop` | GET | **NOT-AUS** (beide Achsen) | `/estop` |
| `/moveto` | GET | Position anfahren | `/moveto?axis=x&position=5000&speed=1400` |
| `/home` | GET | Referenzfahrt | `/home?axis=x&direction=-1&speed=800` |
| `/stop` | GET | Achse pausieren | `/stop?axis=z` |
| `/resume` | GET | Achse fortsetzen | `/resume` |
| `/reset` | GET | Warteschlange leeren | `/reset?axis=x` |
| `/wait` | GET | Wartezeit einreihen | `/wait?axis=x&time=2000` |
| `/sync` | GET | Achsen aufeinander warten lassen | `/sync` |
| `/status` | GET | Status als JSON (inkl. `homed`, `busy`, `paused`) | `/status` |
| `/script` | POST | Ganzes Skript (JSON) ausführen | siehe unten |
| `/api/config` | GET | Komplette Konfiguration (Einstellungen, Getränke, Limits) | `/api/config` |
| `/api/settings` | POST | Maschinen-Einstellungen speichern (JSON, wird im NVS abgelegt) | siehe unten |
| `/api/drinks` | POST | Getränkeliste speichern (JSON, wird im NVS abgelegt) | `{"drinks":[...]}` |
| `/dispense` | GET | Gespeichertes Getränk ausgeben | `/dispense?drink=0` |

Lässt du `axis=` weg, gilt der Befehl für **beide** Achsen. Ein Skript für `/script` sieht so aus (neu: `pump` schaltet das Pumpen-Relais für die angegebene Zeit):

```json
{
  "commands": [
    { "axis": "x", "type": "moveto", "position": 5000, "speed": 1400 },
    { "type": "sync" },
    { "type": "wait", "time_ms": 3000 },
    { "axis": "z", "type": "pump", "time_ms": 2000 },
    { "axis": "x", "type": "moveto", "position": 0, "speed": 1400 }
  ]
}
```

`/dispense` prüft vor dem Start: Ist die Maschine frei (nicht `busy`/`paused`)? Sind alle im Rezept verwendeten Slots und die Z-Höhe eingelernt? Sind die Achsen referenziert (sonst wird das Homing automatisch vorangestellt)? Erst dann wird die komplette Befehlsfolge eingereiht.

Die Maschinen-Einstellungen für `/api/settings` sind das JSON-Objekt, das auch `/api/config` unter `settings` liefert – die Web-App füllt es automatisch aus. Alle Werte werden von der Firmware geprüft und auf die Grenzen aus `config.rs` begrenzt, bevor sie gespeichert werden.

---

## 7. Eingebaute Sicherheitsfunktionen

Die Firmware schützt Maschine und Umgebung auf mehreren Ebenen. Alle Grenzwerte stellst du in `src/config.rs` ein.

**NOT-AUS (`/estop`):** Stoppt sofort beide Motoren, leert alle Warteschlangen und schaltet die Motortreiber stromlos. Die Achsen bleiben pausiert, bis du bewusst "Fortsetzen" drückst.

**Software-Endlagen:** Jede Achse hat einen erlaubten Bereich (`X_MIN_POSITION`/`X_MAX_POSITION` bzw. `Z_...`). Zielpositionen außerhalb werden schon vom Webserver abgelehnt – und falls doch etwas durchrutscht, begrenzt die Motorsteuerung das Ziel zusätzlich selbst (doppelter Boden). So kann der Schlitten nicht mit voller Fahrt gegen das Maschinenende krachen. **Passe diese Werte unbedingt an deine Maschine an** (Standard: großzügige Platzhalter). So findest du sie: Referenzfahrt machen, dann im Schritt-Modus vorsichtig ans andere Ende fahren und die angezeigte Position ablesen.

**Einheitliches Koordinatensystem trotz gegenläufiger Motoren:** Beide Achsen zählen logisch gleich – der Endschalter ist der Nullpunkt, positive Positionen führen vom Schalter weg, und die Referenzfahrt geht in Richtung −1 (zum Schalter hin). Dass der Z-Motor dafür physisch in die andere Richtung drehen muss als X, rechnet die Firmware intern um: In `src/config.rs` steht `Z_DIR_INVERTED = true` (bzw. `X_DIR_INVERTED = false`). Fährt eine Achse nach einem Umbau in die falsche Richtung, drehst du einfach den passenden Wert um – kein Umstecken des Motorsteckers nötig.

**Geschwindigkeits-Limits:** `MAX_SPEED` deckelt die normale Fahrt, `MAX_HOMING_SPEED` die Referenzfahrt (die absichtlich langsamer sein sollte, weil sie gegen einen Schalter fährt).

**Homing-Abbruch:** Erreicht eine Achse beim Homing nach `HOMING_MAX_STEPS` Schritten den Endschalter nicht (Schalter defekt, Kabel ab, falsche Richtung), bricht die Firmware ab, schaltet den Motor aus und pausiert die Achse, statt endlos weiterzudrücken.

**Entprellter Endschalter:** Der Schalter wird zweimal kurz hintereinander gelesen. Ein kurzer elektrischer Störimpuls (z. B. durch die Motorleitungen) kann so kein falsches "Schalter gedrückt" auslösen.

**Begrenzte Warteschlangen und Skripte:** Pro Achse warten maximal `MAX_COMMAND_BUFFER` Befehle, Skripte dürfen maximal `MAX_SCRIPT_COMMANDS` Befehle und `MAX_SCRIPT_BODY_BYTES` Bytes haben. Das schützt den knappen Arbeitsspeicher des ESP32.

**WLAN-Ausfallsicherheit:** Scheitert die Router-Verbindung, öffnet die Maschine ihr eigenes Notfall-WLAN (Kapitel 3) – du verlierst nie die Kontrolle über die Maschine.

**Sicherer Startzustand:** Direkt nach dem Einschalten sind beide Motortreiber deaktiviert und das Pumpen-Relais ist aus; nichts bewegt sich, bevor du einen Befehl gibst.

**Referenzier-Pflicht bei der Getränkeausgabe:** Die Firmware merkt sich pro Achse, ob seit dem Einschalten eine Referenzfahrt erfolgreich war (`homed` im `/status`). `/dispense` stellt fehlendes Homing automatisch voran, und standardmäßig endet jedes Getränk mit einer Referenzfahrt – so können sich Schrittverluste nie von Getränk zu Getränk aufsummieren. Nach NOT-AUS oder Homing-Abbruch gilt die Achse wieder als „nicht referenziert".

**Haltemoment während der Dosierung:** Während einer Befehlsfolge bleibt der Motor bestromt (vorher wurde er nach jeder Einzelbewegung stromlos geschaltet). Die Z-Achse kann also oben am Mechanismus warten, ohne zurückgedrückt zu werden. Stromlos wird erst geschaltet, wenn die Warteschlange leer ist.

**Pumpen-Begrenzung:** Pumpenlaufzeit pro Rezept-Schritt maximal `MAX_PUMP_MS` (60 s); bei NOT-AUS, Pause und Reset geht die Pumpe sofort aus.

**Geprüfte Konfiguration:** Alles, was über `/api/settings` und `/api/drinks` gespeichert wird, wird validiert und auf die Grenzen aus `config.rs` gekappt. Rezepte, die zu viele Befehle erzeugen würden (Warteschlangen-Limit), lehnt `/dispense` mit einer klaren Meldung ab.

Ein Hinweis zur Ehrlichkeit: Ein Software-NOT-AUS ersetzt keinen echten Hardware-Not-Aus-Schalter, der die Motorversorgung physisch trennt. Wenn Personen in die Nähe beweglicher Teile kommen können, ist ein echter Not-Aus-Taster in der Stromzuleitung der Motoren eine sinnvolle Ergänzung.

---

## 8. Der Code erklärt – für Einsteiger

### 8.1 Grundidee: Zwei Kerne, ein Briefkasten

Das Programm besteht aus zwei Teilen, die gleichzeitig laufen. Der **Webserver** (Kern 0) nimmt Anfragen aus dem Browser entgegen. Die **Motorsteuerung** (Kern 1) erzeugt die Schritt-Impulse. Beide reden über einen *Channel* miteinander – stell ihn dir als Briefkasten vor: Der Webserver wirft Befehle ein (`cmd_tx.send(...)`), die Motorsteuerung holt sie heraus (`cmd_rx.try_recv()`). So kann eine langsame Webseite niemals das Timing der Motoren stören.

```rust
let (cmd_tx, cmd_rx) = channel::<AxisCommand>();
```

Für den Rückweg (Positionsanzeige in der App) gibt es den gemeinsamen **Status**: `Arc<Mutex<[AxisStatus; 2]>>`. `Mutex` ist ein Schloss, das dafür sorgt, dass nie zwei Threads gleichzeitig an denselben Daten herumschreiben; `Arc` erlaubt, dass beide Threads dasselbe Objekt besitzen.

### 8.2 main.rs Schritt für Schritt

Der Ablauf in `main()`: Zuerst wird die ESP32-Hardware initialisiert (`Peripherals::take()`), dann das WLAN verbunden (`connect_wifi`, mit Wiederholversuchen und Notfall-WLAN). Danach wird der Webserver gestartet und für jede Web-Adresse (`/moveto`, `/stop`, …) ein sogenannter *Handler* registriert – eine kleine Funktion, die genau dann läuft, wenn jemand diese Adresse aufruft. Ein typischer Handler liest die Parameter aus der URL, **prüft sie** (Sicherheit!) und wirft den passenden Befehl in den Briefkasten:

```rust
match parse_move_params(&query) {
    Ok((axis_opt, position, speed)) => {
        send_command(&cmd_tx_moveto, axis_opt, Command::MoveTo { position, speed });
        // Antwort an den Browser schicken ...
    }
    Err(e) => { /* Fehlermeldung an den Browser */ }
}
```

Anschließend werden die GPIO-Pins den Achsen zugeordnet (Abschnitt "HARDWARE-SETUP" – dort änderst du die Pin-Nummern, falls du anders verkabelt hast):

| Achse | Funktion | GPIO |
|---|---|---|
| X | Step (Schritt-Impuls) | 32 |
| X | Dir (Richtung) | 33 |
| X | Enable (Treiber an/aus) | 14 |
| X | Endschalter | 21 |
| Z | Step | 25 |
| Z | Dir | 26 |
| Z | Enable | 27 |
| Z | Endschalter | 22 |
| – | Pumpen-Relais (HIGH = Pumpe an) | 19 |

**Pumpe anschließen:** Das Relais-Modul der Pumpe kommt an GPIO 19 (plus GND, Versorgung des Relais-Moduls beachten). Die Firmware schaltet den Pin beim Start sofort auf LOW (aus) und begrenzt die Laufzeit pro Rezept-Schritt auf `MAX_PUMP_MS` (Standard: 60 s) – eine fehlerhafte Konfiguration kann die Pumpe also nicht endlos laufen lassen. Bei NOT-AUS, Pause und Reset wird die Pumpe sofort abgeschaltet.

Zum Schluss wird die Motorsteuerung als eigener Thread auf Kern 1 gestartet. Das `core::mem::forget(...)` am Ende sieht seltsam aus, ist aber Absicht: Es verhindert, dass Rust WLAN und Webserver "aufräumt", wenn `main()` endet – sie sollen ja für immer weiterlaufen.

### 8.3 servo.rs Schritt für Schritt

Das Herz ist die Endlosschleife in `axis_thread`. In jeder Runde passiert Folgendes: Erst wird der *Watchdog* gefüttert (ein Wachhund des Betriebssystems, der den Chip neu startet, wenn das Programm hängen bleibt). Dann werden neue Befehle aus dem Briefkasten geholt und verteilt – NOT-AUS wird sofort ausgeführt, Sync landet in beiden Warteschlangen, alles andere in der Warteschlange der jeweiligen Achse. Danach wird der Status für die App aktualisiert, und schließlich berechnet `process_axis_commands` für jede Achse, **wann** ihr nächster Schritt-Impuls fällig ist. Die Schleife wartet bis zu diesem Zeitpunkt und feuert den Impuls mit `execute_pulse`.

Jede Achse ist dabei in einem von vier Zuständen: `Running` (arbeitet Befehle ab), `Paused` (angehalten, Motor stromlos), `Waiting` (wartet gerade eine Zeit ab) oder `Homing` (Referenzfahrt läuft). Das nennt man eine *Zustandsmaschine* – der Code fragt einfach "in welchem Zustand bin ich?" und verhält sich entsprechend.

Ein paar Begriffe übersetzt: Ein **Schritt (Step)** ist die kleinste Bewegung des Schrittmotors – die Firmware erzeugt dafür einen kurzen Impuls (10 µs) auf dem Step-Pin. Die **Geschwindigkeit** ist "Schritte pro Sekunde"; bei Geschwindigkeit 1000 liegt also 1 Millisekunde zwischen zwei Impulsen. **Beschleunigung**: Damit die Mechanik nicht ruckt und das Glas nicht schwappt, startet jede Fahrt langsam, wird pro Schritt um 5 schneller und bremst vor dem Ziel wieder ab (zu sehen in `execute_pulse`). **Sync** ist ein Treffpunkt: Beide Achsen arbeiten ihre Warteschlangen unabhängig ab, aber an einem Sync-Punkt wartet die schnellere auf die langsamere – wichtig, wenn z. B. erst der Schlitten ankommen soll, bevor die Z-Achse das Glas anhebt.

### 8.4 Die Webseite (HTML/JavaScript)

Die Datei `servo-control-migrated.html` wird per `include_str!` in die Firmware eingebaut und bei einem Aufruf von `/` ausgeliefert. Das JavaScript darin ruft einfach die API-Adressen aus Kapitel 6 mit `fetch(...)` auf und zeigt die Antworten an. Der grüne/rote Punkt oben zeigt, ob die Maschine erreichbar ist; der Status wird jede Sekunde abgefragt.

**Wo ist was gespeichert?** Slot-Positionen, Z-Höhe, Zeiten und die Getränke liegen **auf dem ESP32** (NVS-Flash, Namensraum `cockimate`) – jedes Handy im WLAN sieht also dieselben Getränke. Nur die frei gebauten Skripte aus dem Skripte-Reiter liegen im *localStorage* des jeweiligen Browsers.

### 8.5 settings.rs – vom Rezept zur Befehlsfolge

`build_dispense_commands` übersetzt ein Rezept in die Befehle aus Kapitel 6. Für jedes Getränk entsteht sinngemäß: *(falls nötig: Z referenzieren, dann X referenzieren)* → für jede Zutat: *X zum Slot → Sync → pro Hub: Z hoch auf die Release-Höhe, halten, ein kurzer Rückhub (Release-Höhe − `push_retract`), Pause* (bei Pumpen-Slots stattdessen: *Z auf die Pumpen-Z-Position → Pumpe an für Hübe × Zeit → Rückhub um `push_retract`*) → *Sync* → am Ende: *Z runter auf den Nullpunkt → Z referenzieren → X referenzieren* (→ optional Ausgabeposition). Die Sync-Punkte sorgen dafür, dass Z niemals dosiert, während X noch unterwegs ist. Der Rückhub fährt bewusst nur ein kurzes Stück zurück statt bis zum Endschalter – nur der abschließende Schritt bringt Z wieder ganz nach unten.

---

## 9. Werte anpassen – Spickzettel

| Was willst du ändern? | Wo? |
|---|---|
| Slot-Positionen (X + Pumpen-Z), Z-Höhe, Ausgabe, Zeiten, Rückhub | **Web-App → Einrichtung** (kein Flashen nötig!) |
| Getränke / Rezepte / Icons | **Web-App → Einrichtung → Getränke verwalten** |
| Passwort für Einrichtung/Manuell/Skripte | `src/servo-control-migrated.html` → `SETUP_PASSWORD` (Standard `1234`) |
| WLAN-Name / Passwort | `.cargo/config.toml`, Abschnitt `[env]` |
| Verfahrweg der Achsen (Endlagen) | `src/config.rs` → `X_MAX_POSITION`, `Z_MAX_POSITION` … |
| Maximale Geschwindigkeit | `src/config.rs` → `MAX_SPEED`, `MAX_HOMING_SPEED` |
| Homing-Sicherheitsgrenze | `src/config.rs` → `HOMING_MAX_STEPS` |
| Rezept-Limits (Anzahl Getränke, Hübe, Pumpenzeit) | `src/config.rs` → `MAX_DRINKS`, `MAX_DOSES_PER_STEP`, `MAX_PUMP_MS` … |
| Notfall-WLAN (Name/Passwort) | `src/config.rs` → `FALLBACK_AP_SSID`, `FALLBACK_AP_PASSWORD` |
| Motor-/Pumpen-Pins | `src/main.rs`, Abschnitt "HARDWARE-SETUP" |
| Aussehen/Texte der Web-App | `src/servo-control-migrated.html` |

Alles, was du in der Web-App einstellst, gilt sofort und dauerhaft. Nur für Änderungen an den Dateien gilt: neu kompilieren und flashen (`cargo run --release`).

---

## 10. Fehlerbehebung (FAQ)

**Die Maschine taucht nicht im WLAN auf.** Prüfe SSID und Passwort in `.cargo/config.toml` (Groß-/Kleinschreibung!). Der ESP32 kann nur 2,4-GHz-WLAN – reine 5-GHz-Netze funktionieren nicht. Schau in den Seriellen Monitor (`cargo run --release`): Dort steht, ob die Verbindung klappt und welche IP vergeben wurde. Notfalls verbinde dich mit dem Notfall-WLAN "Cockimate" (Kapitel 3).

**`http://cockimate/` funktioniert nicht, die IP-Adresse schon.** Manche Router lösen Hostnamen nicht auf. Nutze einfach die IP-Adresse aus dem Seriellen Monitor, oder vergib im Router eine feste IP für die Maschine.

**Eine Achse bewegt sich gar nicht.** Prüfe die Verkabelung gegen die Pin-Tabelle (Kapitel 8.2), die Stromversorgung des Motortreibers und ob die Achse vielleicht pausiert ist ("Fortsetzen" drücken). Im Seriellen Monitor siehst du, ob die Befehle ankommen (`>>> Befehl empfangen`).

**Die Achse fährt in die falsche Richtung.** Am einfachsten in `src/config.rs` die Konstante `X_DIR_INVERTED` bzw. `Z_DIR_INVERTED` umdrehen und neu flashen – das invertiert die physische Drehrichtung, ohne dass sich am Koordinatensystem etwas ändert. (Alternativ klassisch: Motorstecker stromlos umdrehen.)

**Die Referenzfahrt bricht mit "HOMING ABGEBROCHEN" ab.** Die Achse hat den Endschalter nach `HOMING_MAX_STEPS` Schritten nicht erreicht. Prüfe, ob der Schalter angeschlossen ist und ob die Homing-Richtung stimmt (Standard für beide Achsen: negativ – die gegenläufige Z-Drehrichtung wird intern über `Z_DIR_INVERTED` umgerechnet). Danach "Fortsetzen" drücken und erneut versuchen.

**"Position außerhalb des erlaubten Bereichs".** Die Software-Endlagen greifen. Wenn deine Maschine tatsächlich weiter fahren kann, vergrößere `X_MAX_POSITION`/`Z_MAX_POSITION` in `src/config.rs`.

**Nach NOT-AUS passiert nichts mehr.** Das ist Absicht: Beide Achsen sind pausiert. Drücke "Fortsetzen" (Reiter "Beide Achsen"), dann reagiert die Maschine wieder.

**Positionen stimmen nach dem Einschalten nicht.** Der ESP32 vergisst die *aktuelle Achsposition* beim Ausschalten (die eingelernten Slot-Positionen bleiben natürlich gespeichert). Die Getränkeausgabe referenziert deshalb automatisch, bevor sie losfährt; manuell hilft immer eine Referenzfahrt.

**Ein Getränk lässt sich nicht starten ("Slot X ist noch nicht eingelernt").** Öffne die Einrichtung, lerne die fehlende Position ein und drücke "Einstellungen speichern". Gleiches gilt für die Z-Release-Höhe.

**Die Pumpe läuft nicht.** Prüfe, ob der Slot in der Einrichtung auf "Pumpe" steht, ob das Relais an GPIO 19 hängt und ob "Pumpe: ms pro Dosis" sinnvoll eingestellt ist (z. B. 1000 ms für 20 ml – ausmessen!).

**Getränke/Einstellungen sind nach Firmware-Update weg oder unvollständig.** Neue Firmware-Versionen können neue Felder haben; unbekannte/fehlende Felder werden mit sicheren Standardwerten aufgefüllt. Im Zweifel einmal die Einrichtung öffnen, prüfen und neu speichern.

---

*Stand: Juli 2026 – Version 8: Getränke-Kacheln, Einlernen über die Web-App, Speicherung auf dem ESP32 (NVS), Pumpen-Slot mit eigener Z-Position, kurzer Rückhub zwischen Dosier-Hüben, automatische Referenzfahrten bei der Ausgabe, passwortgeschützte Einrichtung.*
