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:
@@ -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 (P0–P3 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.
|
||||
@@ -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.
|
||||
@@ -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 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
|
||||
```
|
||||
+185
@@ -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 |
Reference in New Issue
Block a user