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.
This commit is contained in:
2026-08-22 11:41:00 +00:00
commit 3905647b7f
26 changed files with 22414 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
# Proxi — Hardware
Wie der Roboter wirklich verdrahtet ist, und woher diese Angaben stammen.
## Herkunft
Kosmos dokumentiert die Pin-Belegung nirgends: die Anleitung richtet sich an
Kinder, programmiert wird ausschließlich mit den fertigen Blöcken von
[kosmos.de/proxi](https://kosmos.de/proxi). Die Angaben hier sind also nicht
abgeschrieben, sondern rekonstruiert.
Möglich ist das, weil MakeCode jedes Projekt komprimiert an die kompilierte
Firmware anhängt. Die `.hex`-Dateien der Kosmos-Beispielprogramme (Download
über [kosmos.de/proxi](https://kosmos.de/proxi)) enthalten damit ihren eigenen
Quelltext. Herausholen lässt er sich mit dem Werkzeug in diesem Repo:
```bash
python3 tools/hex_source.py 00_Funktionstest.hex -o ./quellen
```
Die interessante Datei darin ist `custom.ts`: die Kosmos-Extension mit allen
Hardware-Zugriffen. Sie liegt hier unverändert als
[`firmware/proxi.ts`](../firmware/proxi.ts) — wer wissen will,
wie sich eine Funktion in Grenzfällen verhält, liest am besten dort nach.
Sie ist eine eingedeutschte Fassung der MIT-lizenzierten Extension
[`kaku111/20190209`](https://github.com/kaku111/20190209) für den **Tobbie II**.
Proxi ist derselbe Roboter unter anderem Namen — Kosmos' eigenes Programm
`12_APP_Fernbedienung` hängt noch direkt an dieser Extension:
```json
"TobbieII": "github:kaku111/20190209#9c728847d1a08815275108ec42009211d57c2f0d"
```
Herkunft und Lizenzen im Detail: [LICENSES.md](LICENSES.md).
## Pin-Belegung
| Pin | Richtung | Funktion |
|---|---|---|
| **P13** | Ausgang | Laufmotor vorwärts |
| **P14** | Ausgang | Laufmotor rückwärts |
| **P15** | Ausgang | Drehmotor links (Oberkörper/Kopf) |
| **P16** | Ausgang | Drehmotor rechts |
| **P12** | Ausgang | IR-Sendediode (Messpuls) |
| **P1** | Analog-Eingang | IR-Empfänger links |
| **P2** | Analog-Eingang | IR-Empfänger rechts |
| **P8** | Digital-Eingang | Enable — wird vor jeder Bewegung geprüft |
| **P0** | Ausgang | Summer (Standard-Audiopin des micro:bit) |
### Motoren
Zwei Motoren, beide an einer H-Brücke mit **zwei digitalen Leitungen**
eine je Drehrichtung:
```
P13=1, P14=0 -> vorwärts
P13=0, P14=1 -> rückwärts
P13=0, P14=0 -> aus
P15=1, P16=0 -> Drehung nach links
P15=0, P16=1 -> Drehung nach rechts
P15=0, P16=0 -> aus
```
**Es gibt keine Geschwindigkeitsregelung.** Kein PWM, kein Servo, keine
Zwischenstufen — ein Motor läuft oder er läuft nicht. Gesteuert wird
ausschließlich über die Dauer, deshalb hat kein Befehl im
[Protokoll](PROTOCOL.md) einen Speed-Parameter.
Der Laufmotor bewegt die Beine, der Drehmotor den Oberkörper. Laufen beide
gleichzeitig, geht Proxi eine Kurve.
### IR-Sensoren
Gemessen wird als Differenz, damit Umgebungslicht herausfällt:
1. Empfänger analog lesen (Grundhelligkeit)
2. Sendediode P12 einschalten
3. 250 µs warten
4. Empfänger erneut lesen
5. Sendediode aus — Messwert ist die Differenz beider Messungen
Hoher Wert = Hindernis nah. Am Gerät gemessen: freie Sicht 5-15, Hand in etwa
10 cm 200-600, Hand direkt davor bis ~980. Beide Seiten erreichen den vollen
Bereich. Die Schwellen 400-600 aus Kosmos' eigenen Programmen entsprechen damit
ungefähr einer Handbreit Abstand.
### Helligkeitssensor: durch die Scheibe blockiert
Den gibt es hier nicht, und zwar aus einem Grund, den keine Software behebt.
`input.lightLevel()` misst über die LED-Matrix — und die sitzt bei Proxi
hinter einer **getönten Scheibe**. Nimmt man die weg, misst der Sensor
einwandfrei. Eingebaut liefert er konstant 0, auch mit einer Taschenlampe
direkt davor.
Deshalb kennt das Protokoll kein `SENSOR:LIGHT`. Wer den Sensor braucht, muss
an der Mechanik ansetzen, nicht am Code.
Die 0 sah lange nach einem Softwarefehler aus: Kosmos' eigenes Programm
`04_Licht-Tracker` benutzt denselben Sensor erfolgreich (mit Schwellen von 1
bis 10 — die Werte sind klein). Widerlegt wurde die Softwarespur erst, als
jemand die Scheibe abgenommen hat.
### P8
`custom.ts` prüft `P8 == 1` vor jeder Motorbewegung, beim Abbremsen der Drehung
und bei der rechten IR-Auswertung. Was genau daran hängt, ist nicht
dokumentiert — plausibel ist der Batterieschalter beziehungsweise eine
Power-Good-Leitung, denn der micro:bit läuft auch dann weiter, wenn Proxis
Schalter auf OFF steht und nur USB-Strom anliegt.
Diese Firmware rät nicht daran herum: alle Motorzugriffe laufen über die
unveränderten Extension-Funktionen, und `SENSOR:PWR` gibt den Rohwert von P8
zurück. **Reagiert Proxi auf Fahrbefehle mit `OK`, bewegt sich aber nicht,
lohnt zuerst ein Blick auf `SENSOR:PWR`** — steht da `0`, ist der Schalter aus.
## micro:bit V1 oder V2
Die Firmware wird als **Universal Hex** gebaut und läuft auf beiden.
Knapp ist es beim V1 trotzdem: der Bluetooth-Stack frisst dort den Großteil des
Flash. Erste Fassungen dieser Firmware passten nicht hinein — Platz geschaffen
hat erst der Verzicht auf den eingebauten Melodie-Player von MakeCode (siehe
`playMelodyBuffer` in [`main.ts`](../firmware/main.ts), der Grund dafür
war allerdings ein anderer). Wer die Firmware erweitert, sollte die V1-Variante
im Blick behalten: `pxt build` bricht ab, sobald sie nicht mehr passt.
V2 erkennt man am goldenen Logo-Taster oben mittig und dem Mikrofon oben rechts.
## Ton: keine Kommazahlen
Die Frequenz für `music.playTone()` **muss** eine Ganzzahl sein.
Sie wurde hier anfangs mit `Math.pow()` gerechnet. Der Wert war korrekt — eine
Diagnoseabfrage lieferte für MIDI 79 sauber 784 —, kam als Kommazahl im
Tonausgang aber falsch an. Der micro:bit machte daraus eine unsinnige
Schwingungsdauer, krachte laut, blieb hängen und bediente den Funk nicht mehr;
nach ein paar Sekunden starb die BLE-Verbindung an `Connection Timeout (0x08)`.
Ein Fehler, drei Symptome, die wie drei verschiedene Probleme aussahen. Gefunden
wurde er durch den direkten Vergleich zweier Aufrufe, die sich nur in diesem
einen Punkt unterschieden:
```
gerechnete 784 Hz -> Krachen, hängt, Verbindung weg
übergebene 784 -> sauberer Ton
```
`midiToFreq()` in `main.ts` rechnet deshalb rein ganzzahlig über eine Tabelle.
`tools/check_melodies.py` prüft das bei jedem Build mit.
Zwei Dinge, die dabei **nicht** die Ursache waren, obwohl sie lange danach
aussahen: der eingebaute Melodie-Player von MakeCode und der interne
Lautsprecher des micro:bit V2. Beides wurde gegengeprüft und entlastet.
## Nicht vorhanden
Damit niemand erneut danach sucht — Proxi hat **keine** Touch-Sensoren, **keinen**
Servo und **keine** regelbaren Motoren. Früher lagen in diesem Repo
`Source/*.py`-Dateien, die genau das behaupteten. Sie waren KI-generiert und
frei erfunden; ihre Pin-Belegung (P0P3 als PWM-Motoren, P4/P10 als IR, P6/P7 als
Touch, P8 als Servo) stimmt an keiner Stelle mit der Hardware überein. Sie sind
durch die echten, aus den `.hex`-Dateien extrahierten Quellen ersetzt.
Nebenbei: MicroPython auf dem micro:bit hat überhaupt kein Bluetooth. Wer BLE
will, muss über MakeCode gehen — daher TypeScript statt Python.
+78
View File
@@ -0,0 +1,78 @@
# Lizenzen und Herkunft
## Eigener Code
**MIT**, Copyright (c) 2026 Christian Gärtner — siehe [`../LICENSE`](../LICENSE).
Betrifft alles in diesem Ordner außer den unten aufgeführten Teilen:
```
firmware/main.ts bridge/proxi_bridge.py tools/hex_source.py
firmware/build.sh bridge/*.txt tools/check_melodies.py
agent/proxi-control.md docs/
```
Damit steht das gesamte Projekt unter MIT — auch die beiden fremden
Bestandteile sind MIT-lizenziert, es gibt also keine Reibung zwischen den
Lizenzen. Die fremden Copyright-Vermerke müssen bei der Weitergabe erhalten
bleiben; sie stehen dafür in
[`../THIRD-PARTY-NOTICES`](../THIRD-PARTY-NOTICES).
## Fremder Code
### `firmware/proxi.ts` — Hardware-Extension
Die komplette Hardware-Anbindung (Motoren, IR-Sensoren) stammt nicht von uns.
| | |
|---|---|
| Ursprünglich | Extension **TobbieII** von [kaku111](https://github.com/kaku111/20190209) |
| Lizenz | MIT (`LICENSE.txt` im Repo, `"license": "MIT"` in dessen `pxt.json`) |
| Weg hierher | Kosmos liefert eine eingedeutschte Fassung als `custom.ts` in den `.hex`-Dateien der Beispielprogramme mit; von dort mit `tools/hex_source.py` extrahiert |
| Änderungen | keine — nur ein Herkunftshinweis als Kommentar im Kopf |
Proxi und Tobbie II sind derselbe Roboter unter verschiedenen Namen. Kosmos'
eigenes Programm `12_APP_Fernbedienung` hängt noch direkt an der
Original-Extension:
```json
"TobbieII": "github:kaku111/20190209#9c728847d1a08815275108ec42009211d57c2f0d"
```
Der MIT-Lizenztext des Originals liegt im Wortlaut in
[`../THIRD-PARTY-NOTICES`](../THIRD-PARTY-NOTICES).
### Notendaten in `firmware/main.ts`
Die Melodien in `melodyBuffer()` sind die 20 eingebauten Melodien von MakeCode,
Byte für Byte übernommen aus
[`pxt-microbit`](https://github.com/microsoft/pxt-microbit)
(`libs/core/melodies.ts`, MIT, Copyright Microsoft Corporation).
Warum kopiert statt benutzt: der eingebaute Melodie-Player spielt in einem
eigenen Fiber ab und stürzt ab, sobald ein anderer Fiber gleichzeitig den
Audio-Pin anfasst. Die Begründung steht ausführlich im Kopf des
Musik-Abschnitts von `main.ts`.
## Nicht mitveröffentlichen
Der Rest dieses Repos gehört Kosmos und ist **kein** Teil des
Proxi-AI-Projekts:
```
Kosmos-Programme/ Beispielprogramme (.hex und extrahierter Quelltext)
Proxi_Manual.pdf Bedienungsanleitung
logo.png proxi.png Produktbilder
```
Für ein öffentliches Repo wandert dieser Ordner am Stück mit — er ist in
sich geschlossen:
```
firmware/ bridge/ agent/ docs/ tools/
LICENSE THIRD-PARTY-NOTICES README.md
```
`docs/HARDWARE.md` ist so geschrieben, dass es auch ohne die übrigen
Kosmos-Programme verständlich bleibt, und `README.md` in diesem Ordner taugt
als Startseite des neuen Repos.
+226
View File
@@ -0,0 +1,226 @@
# 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
```
+185
View File
@@ -0,0 +1,185 @@
# Einrichtung
Zwei Schritte: Firmware auf den micro:bit, Brücke auf irgendeinen Rechner mit
Bluetooth.
```
KI / LLM --HTTP--> proxi_bridge.py --BLE--> micro:bit im Proxi
(Pi Zero W, Laptop, ...)
```
## 1. Firmware flashen
Läuft auf micro:bit **V1 und V2** — die `.hex` ist ein Universal Hex und
enthält beides.
1. micro:bit per USB anschließen — er meldet sich als Laufwerk `MICROBIT`
2. [`firmware/proxi-ai.hex`](../firmware/proxi-ai.hex) darauf kopieren
3. Die gelbe LED blinkt, das Laufwerk verschwindet und kommt zurück — fertig
Danach zeigt Proxi ein schlafendes Gesicht: Firmware läuft, wartet auf
Bluetooth.
Ein Nachflashen ist nie nötig. Alles Weitere passiert über Befehle.
### Selbst bauen
Nur nötig, wenn du am Code etwas änderst.
```bash
cd firmware/proxi-ai
npm install pxt
./node_modules/.bin/pxt target microbit
./node_modules/.bin/pxt install
./build.sh # -> proxi-ai.hex
```
Der C++-Teil wird vom MakeCode-Build-Service kompiliert, dafür braucht der
erste Build Internet. `build.sh` prüft am Ende, ob die erzeugte `.hex`
tatsächlich den aktuellen `main.ts` enthält.
Auf dem V1 ist der Flash mit Bluetooth-Stack fast voll. Kommt beim Erweitern
`program too big by ... bytes`, betrifft das die V1-Variante.
Alternativ im Browser: [makecode.microbit.org](https://makecode.microbit.org)
öffnen, neues Projekt, in der JavaScript-Ansicht `main.ts` einfügen, über den
Explorer eine Datei `proxi.ts` anlegen und deren Inhalt einfügen. Die
Bluetooth-Extension muss hinzugefügt und in den Projekteinstellungen
`bluetooth.open` auf `1` gesetzt werden — sonst verlangt der micro:bit Pairing
und die Brücke kommt nicht rein.
## 2. Brücke einrichten
Läuft auf allem mit Linux und Bluetooth. Ein Pi Zero W reicht.
```bash
pip3 install bleak
cd bridge
python3 proxi_bridge.py --scan
```
Ausgabe etwa:
```
C4:1F:2B:AA:BB:CC BBC micro:bit [tuvig]
```
Testen:
```bash
python3 proxi_bridge.py --ping
python3 proxi_bridge.py "FACE:HEART" "MOVE:FWD:1000" "DANCE:2"
python3 proxi_bridge.py --script demo.txt
python3 proxi_bridge.py -i # interaktiv
```
Die Adresse lässt sich festnageln, das spart den Suchlauf:
```bash
export PROXI_ADDRESS=C4:1F:2B:AA:BB:CC
```
## 3. HTTP-API für die KI
```bash
python3 proxi_bridge.py --serve 8080
```
| Route | Zweck |
|---|---|
| `POST /command` | `{"command": "MOVE:FWD:1000"}` |
| `POST /commands` | `{"commands": ["FACE:HAPPY", "DANCE:2"]}` |
| `GET /status` | Verbindungszustand und die letzten Meldungen vom Roboter |
```bash
curl -X POST localhost:8080/command \
-H 'Content-Type: application/json' \
-d '{"command":"DANCE:3"}'
```
```json
{"command": "DANCE:3", "response": "OK:DANCE:3", "ok": true}
```
> ### ⚠️ Nur im eigenen Netz betreiben
>
> Die API hat **keine Authentifizierung**. Wer sie erreicht, fährt den Roboter —
> ohne Passwort, ohne Rückfrage.
>
> Im heimischen WLAN ist das unproblematisch und bewusst so gehalten: es soll
> ohne Hürden funktionieren. **Aber gib den Port nicht nach außen frei** und
> richte keine Portweiterleitung darauf ein. Sonst lässt sich ein Roboter, der
> im Kinderzimmer herumfährt, von jedem beliebigen Fremden steuern.
>
> Soll er über das Heimnetz hinaus erreichbar sein, gehört ein VPN davor —
> nicht eine Portfreigabe.
Im `--serve`-Modus startet der HTTP-Server **sofort** und sucht den Roboter
danach im Hintergrund, alle 10 Sekunden. Die API ist damit auch erreichbar,
wenn Proxi gerade aus ist — `GET /status` meldet dann `"connected": false`,
Befehle antworten mit `ERR:not_connected`. Sobald Proxi eingeschaltet wird,
verbindet sich die Brücke von selbst; niemand muss sich einloggen.
Das ist der Unterschied zu den interaktiven Modi: die brechen sofort ab, wenn
der Roboter nicht da ist, weil man dort auf eine Antwort wartet.
Als Autostart auf einem Pi:
```ini
# /etc/systemd/system/proxi-bridge.service
[Unit]
Description=Proxi BLE Bridge
After=bluetooth.target
[Service]
ExecStart=/usr/bin/python3 /home/pi/proxi/proxi_bridge.py --serve 8080
Environment=PROXI_ADDRESS=C4:1F:2B:AA:BB:CC
Restart=always
RestartSec=5
User=pi
[Install]
WantedBy=multi-user.target
```
## 4. KI anbinden
[`agent/proxi-control.md`](../agent/proxi-control.md) ist ein fertiger
Agent-Skill: er enthält das Protokoll und die Regeln, nach denen aus
"lass ihn tanzen" eine Befehlsfolge wird.
Der Skill enthält **keine feste Adresse** — er erwartet sie in der
Umgebungsvariablen `PROXI_URL`. Die muss in der Umgebung des Agenten gesetzt
sein:
```bash
export PROXI_URL=http://<hostname-des-pi>.local:8080
# oder, falls der Name nicht auflöst:
export PROXI_URL=http://<ip-des-pi>:8080
```
Absichtlich so: die Adresse hängt vom eigenen Netz ab und hat in einer Datei,
die weitergegeben wird, nichts verloren.
**mDNS-Namen (`*.local`) funktionieren nur im selben Netz.** Sitzt der Agent in
einem anderen VLAN als der Pi, nimm die IP-Adresse — oder aktiviere im Router
die mDNS-Weiterleitung.
Der Agent braucht **nur HTTP-Zugriff**, weder das Repo noch Python. Er soll
`proxi_bridge.py` ausdrücklich nicht selbst starten: Bluetooth LE erlaubt nur
eine Verbindung zum Roboter, und die hält der Dienst.
## Wenn etwas klemmt
| Symptom | Ursache |
|---|---|
| `--scan` findet nichts | Proxi aus, oder schon mit einem anderen Gerät verbunden — BLE erlaubt nur eine Verbindung |
| Verbindung bricht sofort ab | Firmware ohne `bluetooth.open=1` gebaut, der micro:bit verlangt dann Pairing |
| Befehle kommen mit `OK` zurück, Proxi bewegt sich nicht | `SENSOR:PWR` prüfen. `0` heißt: Enable-Leitung P8 ist low, meist der Batterieschalter |
| Antworten bleiben aus, Befehle wirken | Auf dem falschen Characteristic gelauscht. Host schreibt auf `…0003`, der micro:bit meldet sich auf `…0002` |
| Lange `TEXT:`-Zeilen abgeschnitten | BLE-Attribute sind 20 Byte, längere Zeilen müssen gestückelt werden — `proxi_bridge.py` macht das |
| Dienst startet immer wieder neu, Port nie offen | Alte Fassung: die Brücke verband sich zuerst und startete den Server danach. Ab Werk behoben — im `--serve`-Modus kommt der Server zuerst |
| Pi nach dem Abziehen des Kabels nicht mehr da | Er hat per WLAN eine **andere** Adresse als per Kabel. `avahi-daemon` installieren, dann geht `<hostname>.local`. mDNS bleibt aber im eigenen Netz — über VLAN-Grenzen hinweg braucht es einen mDNS-Repeater im Router |
| Display bleibt schwarz | Firmware ohne Bluetooth-Extension gebaut, `bluetooth.startUartService()` stirbt dann still beim Start |
| Ton kracht, Proxi hängt, Verbindung weg | Eine Frequenz kam als Kommazahl in `music.playTone()`. Siehe HARDWARE.md, "Ton: keine Kommazahlen" |
| Ein behobener Fehler tritt wieder auf | Vermutlich läuft noch die alte Firmware. `STATUS` schicken und die Build-Kennung dahinter mit der Ausgabe von `build.sh` vergleichen |
Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB