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.
227 lines
7.5 KiB
Markdown
227 lines
7.5 KiB
Markdown
# 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 0–31 |
|
||
| `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 (50–5000 Hz, 10–5000 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,5–1 s | `BADING` `JUMPUP` `JUMPDOWN` `POWERUP` `POWERDOWN` |
|
||
| ~2,5–3,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 | 5–15 |
|
||
| Hand in ~20 cm | 30–90 |
|
||
| Hand in ~10 cm | 200–600 |
|
||
| Hand direkt davor | bis ~980 |
|
||
|
||
Als Hindernis-Schwelle taugen die 400–600, 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
|
||
```
|