Files
proxi-ai/docs/PROTOCOL.md
T
christian 3905647b7f Proxi AI — Spielzeug-Roboter mit natürlicher Sprache steuern
Firmware, Bluetooth-Brücke und Agent-Skill für den KOSMOS-Proxi: einmal
flashen, danach hört der Roboter über Bluetooth LE auf Textbefehle. Was die
Befehle schickt, ist ihm egal — ein Skript, ein curl, oder ein KI-Agent, der
aus "mach mal was Lustiges" eine Choreografie baut.

  firmware/   MakeCode-Projekt (TypeScript) und fertige .hex für V1 und V2
  bridge/     BLE-Brücke und HTTP-API, einzige Abhängigkeit ist bleak
  agent/      Skill-Definition: Alltagssprache -> Befehle
  tools/      Quelltext aus .hex extrahieren, Musik-Tabellen prüfen
  docs/       Protokoll, Einrichtung, Hardware, Lizenzen

Zwei Dinge, die sonst nirgends stehen und in docs/HARDWARE.md dokumentiert
sind: die Pin-Belegung des Roboters ist nicht öffentlich, sie wurde aus den
mitgelieferten .hex-Dateien rekonstruiert (tools/hex_source.py holt sie
heraus). Und der Ton braucht zwingend eine ganzzahlige Frequenz sowie Pin P0
— sonst kracht der Lautsprecher, der Aufruf hängt und die Bluetooth-
Verbindung stirbt.

Am echten Roboter durchgetestet: Verbindung, Sensoren, Display, 41 Sekunden
Musik am Stück, alle Bewegungsrichtungen, Musik und Fahren gleichzeitig,
Tanzfiguren, Not-Stopp mitten in der Bewegung, Autostart nach Neustart.

MIT-Lizenz. Die Hardware-Extension stammt von kaku111 (TobbieII) und die
Notendaten aus pxt-microbit, beide ebenfalls MIT — siehe THIRD-PARTY-NOTICES.
2026-08-22 11:41:00 +00:00

227 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Proxi Command Protocol 1.0
Textbefehle über Bluetooth-LE-UART. Ein Befehl pro Zeile, eine Antwortzeile
zurück.
```
BEFEHL:PARAM:PARAM\n -> OK:...\n | ERR:...\n | <Wert>\n
```
* Befehlsname und Schlüsselwörter sind **nicht** groß-/kleinschreibungsabhängig
* Parameter trennt `:`
* Jede Zeile endet mit `\n`
* Fehlende Parameter fallen auf den Default zurück, unlesbare ebenso
* Es gibt **keinen Speed-Parameter** — die Motoren können nur an oder aus,
siehe [HARDWARE.md](HARDWARE.md)
## Dauer-Parameter
Alle Bewegungsbefehle nehmen als letzten Parameter eine Dauer in Millisekunden.
| Angabe | Wirkung |
|---|---|
| weggelassen | fährt los und stoppt nach **10 s** von selbst (Sicherheitsnetz) |
| `1..60000` | fährt genau so lange und stoppt |
| `0` | fährt weiter, bis ein Stopp-Befehl kommt |
Die Firmware blockiert während der Fahrt nicht: der Befehl wird sofort mit `OK`
quittiert, ein Watchdog schaltet die Motoren zur Deadline ab. `STOP` kommt
deshalb auch mitten in einer laufenden Bewegung durch.
## Bewegung
| Befehl | Wirkung |
|---|---|
| `MOVE:FWD[:ms]` | vorwärts laufen |
| `MOVE:BWD[:ms]` | rückwärts laufen |
| `MOVE:STOP` | Laufmotor aus |
| `TURN:LEFT[:ms]` | Oberkörper nach links drehen |
| `TURN:RIGHT[:ms]` | nach rechts drehen |
| `TURN:STOP` | Drehmotor aus |
| `STOP` | alles aus: beide Motoren, Ton, Display, laufende Jobs |
Lauf- und Drehmotor sind unabhängig. Beide gleichzeitig ergibt eine Kurve:
```
MOVE:FWD:0
TURN:LEFT:800
```
## Bewegungsfiguren
Laufen im Hintergrund und melden sich mit `DONE:<name>`, wenn sie fertig sind —
oder mit `DONE:<name>:aborted`, wenn `STOP` oder Taste A dazwischenkam.
| Befehl | Default | Wirkung |
|---|---|---|
| `DANCE[:n]` | 2 | n Takte tanzen (vor/zurück + links/rechts) |
| `STAMP[:n]` | 3 | n mal aufstampfen |
| `SHAKE[:n]` | 3 | n mal den Kopf schütteln |
`n` wird auf 1..20 begrenzt.
**Sie laufen nicht parallel zur Musik.** `DANCE`, `STAMP`, `SHAKE`, `TEXT`,
`TONE` und `MELODY` teilen sich einen Arbeiter und werden nacheinander
abgearbeitet — eine Figur startet also erst, wenn die Melodie durch ist.
`MOVE` und `TURN` laufen dagegen sofort und damit echt gleichzeitig zur Musik.
Für eine Choreografie zur Melodie deshalb `MOVE`/`TURN` verwenden; ein
Beispiel liegt in `bridge/nyan-dance.txt`.
## Display
| Befehl | Wirkung |
|---|---|
| `FACE:<name>` | Symbol anzeigen (bleibt stehen) |
| `FACE:RAW:z0:z1:z2:z3:z4` | eigenes 5×5-Bild, je Zeile eine Bitmaske 031 |
| `TEXT:<text>` | Text durchlaufen lassen, danach Display leer |
| `CLEAR` | Display löschen |
**Namen für `FACE`:**
`HAPPY` `SAD` `ANGRY` `CONFUSED` `ASLEEP` `SURPRISED` `SILLY` `FABULOUS` `MEH`
`YES` `NO` `HEART` `SMALLHEART` `SKULL` `GHOST` `DUCK` `HOUSE` `GIRAFFE` `COW`
`RABBIT` `SNAKE` `BUTTERFLY` `TORTOISE` `STICKFIGURE` `TARGET` `DIAMOND`
`SQUARE` `TRIANGLE` `UMBRELLA` `SWORD` `NORTH` `EAST` `SOUTH` `WEST`
Bei `FACE:RAW` ist in jeder Zeilen-Bitmaske Bit 0 die linke LED:
```
FACE:RAW:0:10:0:17:14 # zwei Augen, lächelnder Mund
```
`TEXT` darf `:` enthalten — alles ab dem ersten Parameter gilt als Text.
## Ton
| Befehl | Default | Wirkung |
|---|---|---|
| `TONE:<hz>[:ms]` | 440, 400 | einzelner Ton (505000 Hz, 105000 ms) |
| `MELODY:<name>` | — | eingebaute Melodie, spielt im Hintergrund |
**Namen für `MELODY`:**
`DADADUM` `ENTERTAINER` `PRELUDE` `ODE` `NYAN` `RINGTONE` `FUNK` `BLUES`
`BIRTHDAY` `WEDDING` `FUNERAL` `PUNCHLINE` `BADDY` `CHASE` `BADING` `WAWAWAWAA`
`JUMPUP` `JUMPDOWN` `POWERUP` `POWERDOWN`
**Laufzeiten** (für Choreografien):
| | |
|---|---|
| ~0,51 s | `BADING` `JUMPUP` `JUMPDOWN` `POWERUP` `POWERDOWN` |
| ~2,53,5 s | `WAWAWAWAA` `PUNCHLINE` `RINGTONE` `BADDY` `ENTERTAINER` `FUNERAL` |
| ~4 s | `DADADUM` `FUNK` |
| ~8 s | `CHASE` `WEDDING` `PRELUDE` |
| ~12 s | `BIRTHDAY` `BLUES` |
| ~16 s | `ODE` `NYAN` |
Mehrere `MELODY` hintereinander reihen sich in der Warteschlange und spielen
nahtlos nacheinander.
## Sensoren
| Befehl | Antwort |
|---|---|
| `SENSOR:ALL` | `SENSOR:ALL:IR_L=..,IR_R=..,TEMP=..,PWR=..` |
| `SENSOR:IR_L` | `SENSOR:IR_L:<0..1023>` — höher = Hindernis näher |
| `SENSOR:IR_R` | `SENSOR:IR_R:<0..1023>` |
| `SENSOR:TEMP` | `SENSOR:TEMP:<°C>` (Prozessortemperatur, träge) |
| `SENSOR:PWR` | `SENSOR:PWR:<0\|1>` — Zustand von P8, siehe HARDWARE.md |
Ohne Parameter verhält sich `SENSOR` wie `SENSOR:ALL`.
**IR-Werte, am Gerät gemessen:**
| Situation | Wert |
|---|---|
| freie Sicht | 515 |
| Hand in ~20 cm | 3090 |
| Hand in ~10 cm | 200600 |
| Hand direkt davor | bis ~980 |
Als Hindernis-Schwelle taugen die 400600, die auch Kosmos' eigene Programme
verwenden — das entspricht etwa einer Handbreit Abstand. Beide Sensoren
erreichen den vollen Bereich; ein dauerhaft niedriger Wert auf einer Seite ist
also ein Defekt und keine Eigenart.
## System
`<build>` ist eine siebenstellige Kennung des Quellstands, die `build.sh` aus
main.ts, proxi.ts und pxt.json errechnet. Geflasht wird von Hand — daran sieht
man, welcher Stand tatsächlich auf dem Roboter liegt. Gleicher Code ergibt
immer dieselbe Kennung.
| Befehl | Antwort |
|---|---|
| `PING` | `PONG` |
| `STATUS` | `STATUS:proxi-ai/1.0+<build>:PWR=<0\|1>` |
## `WAIT:<ms>` — Pause in der Brücke
`WAIT` ist **kein Roboter-Befehl**. Die Firmware kennt ihn nicht;
`proxi_bridge.py` fängt ihn ab und legt die angegebene Pause ein, bevor der
nächste Befehl rausgeht (maximal 60000 ms).
Er wird gebraucht, weil Bewegungsbefehle sofort mit `OK` quittieren und dann im
Hintergrund weiterlaufen. Ohne Pause dazwischen überschreibt der nächste
Befehl die noch laufende Bewegung — Proxi zuckt nur kurz:
```
MOVE:FWD:1200 # läuft 1,2 s ...
WAIT:1400 # ... so lange warten
TURN:LEFT:700
WAIT:900
STOP
```
Faustregel: `WAIT` etwas größer wählen als die Dauer davor.
## Unaufgeforderte Meldungen
Diese Zeilen kommen ohne passenden Befehl und sind keine Antwort:
| Zeile | Bedeutung |
|---|---|
| `READY:proxi-ai/1.0+<build>` | BLE verbunden, Firmware bereit — kommt oft nicht an, weil der Client die Indications meist erst kurz nach dem Verbindungsaufbau aktiviert. Verlass dich nicht darauf, nimm `PING` |
| `DONE:<job>` | `DANCE`/`STAMP`/`SHAKE`/`TEXT`/`TONE`/`MELODY` fertig |
| `DONE:<job>:aborted` | derselbe Job, aber vorzeitig gestoppt |
| `EVENT:ESTOP` | Taste A gedrückt — Not-Stopp |
`proxi_bridge.py` hält sie von den Befehlsantworten getrennt.
## Fehler
| Antwort | Bedeutung |
|---|---|
| `ERR:unknown:<CMD>` | Befehl gibt es nicht |
| `ERR:MOVE:bad_dir` | erwartet `FWD`, `BWD` oder `STOP` |
| `ERR:TURN:bad_dir` | erwartet `LEFT`, `RIGHT` oder `STOP` |
| `ERR:FACE:unknown` | Symbolname unbekannt |
| `ERR:MELODY:unknown` | Melodiename unbekannt |
| `ERR:SENSOR:bad_type` | Sensorname unbekannt |
| `ERR:TEXT:empty` | kein Text angegeben |
| `ERR:<CMD>:busy` | Warteschlange voll (8 Aufträge). Betrifft `TEXT`, `TONE`, `MELODY`, `DANCE`, `STAMP`, `SHAKE` — mit `WAIT` entzerren |
## Tasten am Roboter
Funktionieren auch ohne Verbindung.
| Taste | Wirkung |
|---|---|
| **A** | Not-Stopp — alles aus, sendet `EVENT:ESTOP` |
| **B** | Status senden |
Bricht die BLE-Verbindung ab, stoppen die Motoren sofort von selbst.
## Beispiel
```
PING -> PONG
FACE:HAPPY -> OK:FACE:HAPPY
MOVE:FWD:1500 -> OK:MOVE:FWD:1500
SENSOR:IR_L -> SENSOR:IR_L:612
TURN:RIGHT:700 -> OK:TURN:RIGHT:700
MELODY:POWERUP -> OK:MELODY:POWERUP
DANCE:3 -> OK:DANCE:3
-> DONE:DANCE (später, unaufgefordert)
STOP -> OK:STOP
```