# noahROBOT Serial Protocol v1

Status: Implementiert in `noah_robot_gateway/protocol.py` und
`noah_robot_uno.ino`; gemeinsame Vektoren liegen in
`noah/robot/protocol-vectors.json`. Die STATUS-only-Firmware 1.2.0 und die
motor-gated Variante kompilieren fuer Uno, wurden aber nicht auf reale Hardware
geflasht. Der fruehere newline-JSON-Entwurf wird
fuer reale Uno-Hardware nicht verwendet.

## 1. Ziele

- kleine, feste Speicherbelegung ohne dynamisches JSON oder Arduino `String`
- leicht mit Serial-Monitor und Test-Harness diagnostizierbar
- Framing, Versionsfeld, Sequenznummer und CRC
- idempotenter STOP/E-Stop
- kurze Command-Lease mit autonomem Firmware-STOP
- keine ungeprueften Browser- oder LLM-Daten

## 2. Transport

Fest implementierte Laborparameter:

- USB-Serial
- Windows-Zuordnung ueber bestaetigte VID/PID/Seriennummer und COM-Port; die
  konkrete Zuordnung bleibt bis zur Vor-Ort-Inventur offen
- 115200 Baud, 8 Datenbits, keine Paritaet, 1 Stopbit (`8N1`)
- ASCII, genau eine Nachricht pro Zeile
- maximale Frame-Laenge: 96 Bytes inklusive Newline
- Empfangspuffer statisch; Pufferueberlauf fuehrt zu STOP und `NACK`

Die Baudrate bleibt Teil des Hardware-Abnahmetests und darf erst danach als
produktiv bestaetigt werden.

## 3. Frame

```text
@V1,<SEQ>,<TYPE>[,<ARG1>...]*<CRC16>\n
```

- `@`: Startbyte
- `V1`: Protokollversion
- `SEQ`: dezimal 0..65535
- `TYPE`: Allowlist-Token in Grossbuchstaben
- Argumente: nur fest definierte Ganzzahlen oder kurze Hex-Tokens
- `*`: Ende des CRC-Bereichs
- `CRC16`: vier Hex-Zeichen in Grossbuchstaben
- `\n`: Frame-Ende; optionales `\r` davor wird verworfen

CRC-Definition: CRC-16/CCITT-FALSE, Polynom `0x1021`, Initialwert `0xFFFF`,
keine Reflexion, `xorout=0x0000`. Geprueft werden alle Bytes nach `@` bis direkt
vor `*`.

Kanonische, in Python und Firmware gepruefte Beispiele:

```text
@V1,1,HELLO,A1B2C3D4*F20F
@V1,2,STATUS*327C
@V1,3,STOP*3A52
@V1,4,DRIVE,25,-25,250*1C9F
@V1,5,ESTOP*06E4
```

## 4. Host -> Funduino

| TYPE | Argumente | Zweck | Zulaessiger Zustand |
| --- | --- | --- | --- |
| `HELLO` | `SESSION_HEX` | neue Hostsession; fordert Status an | alle |
| `HEARTBEAT`/`PING` | keine | Kommunikationsheartbeat | aktive Session |
| `STATUS` | keine | vollstaendigen Zustand anfordern | alle |
| `ARM` | `SESSION_HEX` | Arm-Anfrage; nur bei lokaler physischer Freigabe | `DISARMED` |
| `DRIVE` | `LEFT,RIGHT,TTL_MS` | Dead-Man-Differentialfahrt | `ARMED_IDLE/MOVING` |
| `STOP` | keine | Sollwerte null, bleibt betriebsbereit | alle |
| `ESTOP` | keine | digitalen E-Stop verriegeln | alle |
| `RESET_ESTOP`/`FAULT_CLEAR` | `SESSION_HEX` | nur bei freiem physischen E-Stop und lokalem Armkontakt | `FAULT/ESTOP` |
| `VERSION` | keine | Firmwareversion abfragen | alle |

### DRIVE

- `LEFT` und `RIGHT`: signed Integer im Protokollbereich `-255..255`.
- Gateway begrenzt Phase 4 wesentlich enger; Firmware besitzt eine zweite
  unabhaengige Maximalgrenze.
- `TTL_MS`: `100..300`; ausserhalb des Bereichs wird abgelehnt und gestoppt.
- `0,0` ist gleichbedeutend mit STOP-Sollwert, aber `STOP` bleibt der explizite
  Sicherheitsbefehl.
- Neue Sollwerte werden nur nach Sequenz-, CRC-, Zustands- und Grenzpruefung
  atomar uebernommen.
- Ein Vorzeichenwechsel auf mindestens einem Kanal erzwingt in der
  Funduino-Firmware zunächst PWM 0 und alle Richtungspins LOW. Erst nach 3 ms
  Totzeit wird die neue Richtung mit begrenztem PWM aktiviert.

Distanz- und Winkelbefehle sind ohne Encoder nicht belastbar und deshalb nicht
Teil von v1.

## 5. Funduino -> Host

| TYPE | Argumente | Bedeutung |
| --- | --- | --- |
| `BOOT` | `FW_HEX,RESET_CODE` | Reset erkannt; Motoren aus, disarmed |
| `READY` | `FW_VERSION,STATE` | `HELLO` akzeptiert; neuer sicherer Hostzustand |
| `ACK` | `COMMAND,STATE` | Befehl akzeptiert |
| `NACK` | `ERROR_CODE,STATE` | Befehl verworfen |
| `STATE` | `STATE,ESTOP,LEFT,RIGHT,AGE_MS,FAULTS` | kompakte Telemetrie |
| `SENSOR` | `SENSOR_ID,VALUE,FLAGS` | spaetere begrenzte Sensordaten |
| `ERROR` | `ERROR_CODE,STATE` | sicherheitsrelevanter Timeout/E-Stop |
| `VERSION` | `FW_VERSION` | Firmwareversion |

Zulaessige `STATE`-Werte:

- `BOOT_SAFE`
- `DISARMED`
- `ARMED_IDLE`
- `MOVING`
- `ESTOP_LATCHED`
- `FAULT_LATCHED`

Beispiel:

```text
@V1,17,STATE,DISARMED,0,0,0,42,0000*<CRC>
```

## 6. Fehlercodes

| Code | Bedeutung | Firmwareaktion |
| --- | --- | --- |
| `BAD_FRAME` | Start/Ende ungueltig | STOP wenn armed |
| `BAD_CRC` | Pruefsumme ungueltig | STOP wenn armed |
| `TOO_LONG` | Pufferueberlauf | sofort STOP |
| `BAD_VERSION` | unbekannte Version | STOP, disarmed |
| `BAD_TYPE` | nicht allowlisted | STOP wenn armed |
| `BAD_ARG` | Anzahl/Format ungueltig | STOP wenn armed |
| `OUT_OF_RANGE` | Grenzwert verletzt | sofort STOP |
| `STALE_SEQ` | alte Sequenz | ignorieren; Status senden |
| `NOT_ARMED` | Bewegung ohne Armstatus | keine Bewegung |
| `PHYSICAL_ESTOP` | Hardware-E-Stop aktiv | E-Stop verriegelt |
| `WATCHDOG` | Lease abgelaufen | sofort STOP, disarmed/fault |
| `BROWNOUT` | Reset-/Spannungsfehler | BOOT_SAFE, keine Auto-Freigabe |

## 7. Sequenzen und Wiederholung

- Host waehlt pro Session eine neue zufaellige `SESSION_HEX` und beginnt mit
  Sequenz 1.
- Funduino merkt die letzte akzeptierte Sequenz.
- Ein exakt wiederholter idempotenter STOP/E-Stop darf erneut bestaetigt werden.
- Ein doppelter DRIVE-Frame wird nicht als neue Lease-Zeit gewertet.
- Nach USB-Reconnect oder Funduino-BOOT ist jede fruehere Session ungueltig.
- Sequenz-Wrap wird nur nach dokumentiertem Rehandshake akzeptiert.

## 8. Watchdog

- Firmware setzt alle Motorpins bereits in `setup()` vor Serialinitialisierung
  auf den sicheren Zustand.
- Nur gueltige, neue `DRIVE`-Frames erneuern die Bewegungslease.
- Jeder `DRIVE`-Frame besitzt eine Lease von 100 bis maximal 300 ms. Nach
  Ablauf: PWM=0, EN aus, Richtungen LOW, Session ungueltig.
- `HEARTBEAT`/`PING` muss im bewaffneten Zustand spaetestens nach 600 ms
  eintreffen; es verlaengert die Verbindung, aber nicht die DRIVE-Lease.
- Hardware-Watchdog startet den Uno bei blockierter Hauptschleife neu; der
  Neustart endet in `BOOT_SAFE`.

## 9. Arm- und E-Stop-Regeln

- `ARM` ist eine Anfrage, keine alleinige Freigabe.
- Firmware akzeptiert ARM nur, wenn physischer E-Stop frei, lokaler
  Arm-/Freigabekontakt bestaetigt, keine Faults aktiv und Sollwerte null sind.
- Ein digitaler oder physischer E-Stop ist verriegelt.
- `FAULT_CLEAR` funktioniert nur nach lokalem menschlichem Vorgang und darf den
  physischen Not-Stopp nicht ueberstimmen.
- Ein Neustart darf E-Stop/Fault nicht still in fahrbereit verwandeln.

## 10. Parseranforderungen

- kein `String`, kein Heap-JSON, keine Rekursion
- feste `char`-Puffer und begrenzte Integerparser
- jedes Feld mit maximaler Laenge
- keine Teilframe-Ausfuehrung
- Ausgaenge erst nach komplett erfolgreicher Validierung aktualisieren
- Fuzztests fuer leere, zu lange, abgeschnittene, doppelte und ungueltige Frames

## 11. Gateway-Abbildung

Der Browser sendet normierte Werte `-1.0..1.0`. Nur der Gateway:

1. prueft Rolle und aktive Lease,
2. prueft Sequenz und TTL,
3. wendet Geschwindigkeitslimit, Totzone und Slew-Rate an,
4. konvertiert in begrenzte Uno-Integer,
5. erzeugt CRC und Frame,
6. wartet auf ACK/STATE,
7. stoppt bei Timeout oder Abweichung.

Rohframes sind weder Browser-API noch LLM-Werkzeug.

## 12. Test-Gates

Vor Firmware-Flash muessen lokal ohne Hardware bestehen:

- gemeinsame CRC-Testvektoren Python/C++
- Parser-Unit- und Fuzztests
- Sequenz-/Replaytests
- Watchdogtest mit simuliertem Zeitgeber
- State-Machine-Tests
- Grenzwert- und Pufferueberlauftests

Mit Funduino, aber ohne L298N/Motoren:

- BOOT/STATUS/STOP/E-Stop
- USB-Abziehen und Reconnect
- 10 Minuten Heartbeat ohne Speicherwachstum
- kein Pin darf beim Reset einen Bewegungsimpuls erzeugen

Erst danach folgt das Hardware-Gate aus `docs/WIRING.md`.
