Ein overhead-kamerabasiertes System zur autonomen Navigation physischer Differentialantriebs-Roboter in Klassenzimmern. Schülerinnen und Schüler programmieren die Roboter über eine grafische Blockly-Oberfläche.
Find a file
2026-04-02 10:57:31 +02:00
static Initial commit: HoloDeck v010 Server + v011 ESP32 Firmware 2026-04-01 22:09:30 +02:00
.gitignore Initial commit: HoloDeck v010 Server + v011 ESP32 Firmware 2026-04-01 22:09:30 +02:00
app.py Initial commit: HoloDeck v010 Server + v011 ESP32 Firmware 2026-04-01 22:09:30 +02:00
apriltag_ws.py Initial commit: HoloDeck v010 Server + v011 ESP32 Firmware 2026-04-01 22:09:30 +02:00
calibration.py Initial commit: HoloDeck v010 Server + v011 ESP32 Firmware 2026-04-01 22:09:30 +02:00
collision.py Initial commit: HoloDeck v010 Server + v011 ESP32 Firmware 2026-04-01 22:09:30 +02:00
config.yaml Initial commit: HoloDeck v010 Server + v011 ESP32 Firmware 2026-04-01 22:09:30 +02:00
logo.png Dateien nach „/“ hochladen 2026-04-01 22:18:38 +02:00
main.py Initial commit: HoloDeck v010 Server + v011 ESP32 Firmware 2026-04-01 22:09:30 +02:00
navlogger.py Initial commit: HoloDeck v010 Server + v011 ESP32 Firmware 2026-04-01 22:09:30 +02:00
README.md README.md aktualisiert 2026-04-02 10:57:31 +02:00
requirements.txt Initial commit: HoloDeck v010 Server + v011 ESP32 Firmware 2026-04-01 22:09:30 +02:00
serverstart.txt Initial commit: HoloDeck v010 Server + v011 ESP32 Firmware 2026-04-01 22:09:30 +02:00
tracker.py Initial commit: HoloDeck v010 Server + v011 ESP32 Firmware 2026-04-01 22:09:30 +02:00

HoloDeck — AprilTag-Robotersteuerung für den Unterricht

Pädagogische Hochschule Weingarten · FrankeLab

Ein overhead-kamerabasiertes System zur autonomen Navigation physischer Differentialantriebs-Roboter in Klassenzimmern. Schülerinnen und Schüler programmieren die Roboter über eine grafische Blockly-Oberfläche.


Inhaltsverzeichnis

  1. Systemübersicht
  2. Hardware-Komponenten
  3. Verdrahtung & Schaltplan
  4. Netzwerk & Infrastruktur
  5. AprilTag-Layout
  6. Software-Architektur
  7. Entwicklungsgeschichte (v005v011)
  8. Deployment (Server)
  9. Firmware flashen (ESP32)
  10. Konfiguration (config.yaml)
  11. Kommunikationsprotokoll
  12. Regler-Tuning
  13. Bekannte Probleme & Workarounds
  14. Roadmap

1. Systemübersicht

┌─────────────────────────────────────────────────────────────┐
│  Overhead-Kamera (HuskyLens 2)                              │
│  RTSP → 192.168.0.30:8554/live                              │
└────────────────────┬────────────────────────────────────────┘
                     │ RTSP (UDP, 1280×720)
                     ▼
┌─────────────────────────────────────────────────────────────┐
│  Ubuntu-Server  192.168.0.250                               │
│  /opt/robot-tracker-v010/                                   │
│  ┌──────────┐  ┌───────────┐  ┌──────────────┐             │
│  │ tracker  │  │ collision │  │  navlogger   │             │
│  │ AprilTag │→ │ Potential │→ │  CSV-Logging │             │
│  │ Detektor │  │ -feld PID │  │              │             │
│  └──────────┘  └───────────┘  └──────────────┘             │
│       FastAPI / uvicorn  Port 80                            │
│       TCP-Server         Port 8765                          │
└────────────────┬────────────────────────────────────────────┘
                 │ TCP (JSON, newline-delimited)
                 ▼
┌─────────────────────────────────────────────────────────────┐
│  Roboter  (Freenove ESP32-S3 + MicroPython v1.23)           │
│  WiFi FrankeLab → 192.168.0.250:8765                        │
│  ┌──────────┐  ┌────────────┐  ┌────────────┐              │
│  │ PID-Loop │  │  Encoder   │  │  Motoren   │              │
│  │ 20 Hz    │← │  IRQ 50ms  │  │ L9110S     │              │
│  └──────────┘  └────────────┘  └────────────┘              │
└─────────────────────────────────────────────────────────────┘

2. Hardware-Komponenten

Server / Kamera

Komponente Details
Server Ubuntu 24.04, statisch 192.168.0.250
Kamera HuskyLens 2 (RTSP-Modus)
Kamera-Mount Overhead-Befestigung zentral über der Spielfläche

Roboter (ein Exemplar, erweiterbar)

Komponente Bezeichnung / Bestellnummer
Chassis DollaTek 2WD Roboter-Chassis-Kit
Mikrocontroller Freenove ESP32-S3 WROOM Board Lite
Breakout-Board Freenove ESP32-S3 Breakout Board (Schraubklemmen)
Motortreiber TB6612FNG Dual DC Motor Stepper
Encoder-Sensoren Optokoppler-Geschwindigkeitssensor (74HC14D-basiert, 3,35V)
Powerbank Intenso XS5000 (USB-A + USB-C Ausgang)
AprilTag (Roboter) Tag36h11, ID 91, auf Aufkleber am Roboter

Hinweis Motortereiber-Geschichte: Zunächst wurde ein handgelöteter TB6612FNG auf Lochraster verwendet. Dieser wurde durch das deutlich handhabbarere L9110S-Schraubklemmen-Modul ersetzt.

WARNUNG: Ein früheres AZDelivery-ESP32-Board wurde wahrscheinlich durch gleichzeitigen USB+Akku-Betrieb beschädigt. Niemals gleichzeitig USB und Powerbank anlegen wenn GPIO-Pins des Motortreibers auf HIGH liegen!


3. Verdrahtung & Schaltplan

Stromversorgung — ZWEI GETRENNTE RAILS (kritisch!)

Das System verwendet zwei physisch getrennte Stromversorgungskreise, weil WiFi-Peak (200400 mA) + zwei Motoren den 5V-Rail eines einzigen USB-Anschlusses überlasten:

┌─────────────────────┐     ┌──────────────────────────────────┐
│  Intenso XS5000     │     │  Intenso XS5000                  │
│  USB-C Ausgang      │     │  USB-A Ausgang                   │
│  5V → ESP32 (VIN)   │     │  Kabel aufschneiden:             │
│                     │     │  Roter Draht → VM (Motortreiber) │
└─────────────────────┘     └──────────────────────────────────┘
        │                                │
        │ USB-C Kabel                    │ Modifiziertes USB-A-Kabel
        ▼                                ▼
 ESP32-S3 (USB-C)                  VM-Pin L9110S
 Logikversorgung                   Motorversorgung

GND verbunden: ESP32 GND ←——→ L9110S GND  (gemeinsame Masse!)

Wichtig: Das schwarze (GND) Kabel des USB-A-Kabels wird ebenfalls mit dem ESP32-GND verbunden. Die Datenleitungen (grün/weiß) bleiben unbenutzt / abgeklebt.


GPIO-Belegung ESP32-S3

GPIO Funktion Verbindung
GPIO 17 AIN1 (Motor A Richtung 1) L9110S Pin IA1
GPIO 18 AIN2 (Motor A Richtung 2) L9110S Pin IA2
GPIO 8 PWMA (Motor A Geschwindigkeit) L9110S Pin — via Jumper oder direkt
GPIO 9 BIN1 (Motor B Richtung 1) L9110S Pin IB1
GPIO 10 BIN2 (Motor B Richtung 2) L9110S Pin IB2
GPIO 11 PWMB (Motor B Geschwindigkeit) L9110S Pin —
GPIO 6 Encoder Links (Interrupt) Optokoppler OUT
GPIO 7 Encoder Rechts (Interrupt) Optokoppler OUT
3V3 Encoder VCC Optokoppler VCC
GND Gemeinsame Masse L9110S GND, Encoder GND

Achtung GPIO 12: GPIO 12 ist am ESP32(-S3 nicht betroffen, aber klassischer ESP32) ein Strapping-Pin und verursacht Boot-Probleme. Wird hier nicht verwendet.


L9110S Motor-Treiber Anschlussschema

        ESP32-S3                    L9110S Modul
        ─────────                   ────────────
        GPIO 17 ──────────────────→ IA1   OA1 ──→ Motor A (links)  Klemme 1
        GPIO 18 ──────────────────→ IA2   OA2 ──→ Motor A (links)  Klemme 2
        GPIO 8  ──────── (PWM) ──→ IA1 oder Jumper (je nach Modul)
        GPIO 9  ──────────────────→ IB1   OB1 ──→ Motor B (rechts) Klemme 1
        GPIO 10 ──────────────────→ IB2   OB2 ──→ Motor B (rechts) Klemme 2
        GPIO 11 ──────── (PWM) ──→ IB1 oder Jumper
        3V3     ──────────────────→ VCC (Logik)
        GND     ──────────────────→ GND
        USB-A rot ────────────────→ VM  (Motorversorgung 5V)

Hinweis: Das L9110S-Modul vereint Richtungssteuerung und PWM auf zwei Eingangspins pro Kanal (IA1/IA2). Die Drehrichtung ergibt sich aus dem Muster HIGH/LOW bzw. PWM/GND. Konkrete Zuordnung in main.py → set_motor().


Encoder-Sensor Anschluss

        Optokoppler-Modul (74HC14D)     ESP32-S3
        ───────────────────────────     ─────────
        VCC  ──────────────────────→  3V3
        GND  ──────────────────────→  GND
        OUT (linker Sensor)  ──────→  GPIO 6
        OUT (rechter Sensor) ──────→  GPIO 7

        Lochscheibe: 20 Löcher/Umdrehung → 20 Pulse/Rev

Befestigung

  • Alle Elektronikmodule mit Heißkleber auf dem Chassis fixiert (sicher, rückbaubar)
  • Encoder-Sensoren justiert auf Lochscheibe der Motorachsen
  • AprilTag (ID 91) flach auf der Oberseite des Roboters aufgeklebt, muss von oben sichtbar sein

4. Netzwerk & Infrastruktur

Komponente IP-Adresse Port
Ubuntu-Server 192.168.0.250 (statisch)
FastAPI/Webinterface 192.168.0.250 80
TCP Robot-Server 192.168.0.250 8765
HuskyLens 2 (RTSP) 192.168.0.30 8554
Roboter (ESP32) DHCP

WLAN: FrankeLab (Passwort in main.py und WIFI_PSW Variable)

Python-Umgebung Server:

/opt/vision-venv/          # Virtuelle Umgebung
/opt/robot-tracker-v010/   # Aktueller Produktionsstand

5. AprilTag-Layout

Alle Tags aus der Familie tag36h11.

Kalibrierungs-Tags (Referenzpunkte)

Auf einem A4-Blatt, fixiert im Sichtfeld der Kamera:

Tag-ID Position (mm)
0 (0.0, 0.0) — Ursprung
1 (100.5, 0.0)
2 (0.0, 181.0)
3 (100.5, 181.0)

Roboter-Tags

Tag-ID Bedeutung
91 Roboter 1

Hindernis-Tags

Tag-IDs
11, 12, 21, 22, 31, 32, 41, 42, 51, 52, 61, 62, 71, 72, 81, 82, 92

Hindernisse werden als AprilTags auf dem Boden oder an Objekten platziert. Der Server berechnet automatisch Abstoßungskräfte (Potentialfeld).


6. Software-Architektur

Server-Stack

app.py          FastAPI-App, 20-Hz-Steuerungsloop, TCP-Server, WebSocket-Broadcast
tracker.py      AprilTag-Detektion via pupil-apriltags, EMA-Filter, Dead Reckoning
collision.py    Potentialfeld-Navigation: Zielanziehung + Hindernisabstoßung
navlogger.py    CSV-Logging aller Navigationsdaten → logs/nav_YYYYMMDD_HHMMSS.csv
calibration.py  Homographie-Kalibrierung (Kamerabild → Weltkoordinaten mm)
config.yaml     Alle Parameter externalisiert
static/         Browser-Frontend (Live-Kamerabild + AprilTag-Overlay)

ESP32-Firmware

main.py   Komplette Roboter-Firmware (MicroPython)
          ├── WiFi-Verbindung mit Auto-Reconnect
          ├── TCP-Client → Server 192.168.0.250:8765
          ├── Encoder-Interrupts (GPIO 6, 7)
          ├── PID-Loop 20 Hz (asyncio)
          ├── Telemetrie-Loop 10 Hz (asyncio)
          └── Feedforward-Architektur (v011, aktuell deaktiviert = Option B)

7. Entwicklungsgeschichte v005v011

v005 — Stabile Tracking-Basis (Server only)

  • Kamera-I/O auf dedizierten Background-Thread via asyncio.Queue(maxsize=1)
  • ffmpeg-Watchdog mit Auto-Reconnect bei Kameraausfall
  • config.yaml — alle Parameter externalisiert, kein Hardcoding mehr
  • Velocity-Vektor-Visualisierung im Browser-Frontend
  • Bestätigt: Merklich flüssiger als v004

v006 — Grundlegende Navigation

  • Erste TCP-Kommunikation mit ESP32
  • Einfache Richtungs-Befehle (links/rechts/vorwärts)
  • Kein Encoder-Feedback

v007 — Homographie-Kalibrierung

  • calibration.py eingeführt: 4 Referenz-Tags → Kamerabild-zu-Weltkoordinaten-Mapping
  • Kamerakoordinaten erstmals in echte mm umgerechnet
  • A4-Kalibrierblatt mit Tags 03 als Referenz

v008 — Erste Hindernisvermeidung

  • collision.py eingeführt: Potentialfeld-Ansatz
  • Zielanziehung + Hindernisabstoßung
  • Noch instabil: Spin-Probleme, zu aggressiv

v008_log — Navigation-Logging

  • navlogger.py hinzugefügt
  • CSV-Logging aller Navigationsdaten für spätere Analyse
  • Log-Dateien in logs/nav_DATUM_UHRZEIT.csv

v009 — Encoder-PID + Erste stabile Navigation

Meilenstein: Erster Roboter fährt autonom zum Ziel

  • Encoder-Interrupts auf GPIO 6/7
  • PID-Regler für Radgeschwindigkeit: KP=1.2, KI=0.3, KD=0.05
  • Kommunikationsprotokoll auf mm/s umgestellt (server→robot: {"speed_l": N, "speed_r": N})
  • TCP statt WebSocket für Robot-Kommunikation (simpler, stabiler)
  • Dead Reckoning: Bei Tag-Verlust schätzt ESP32 Position aus Encoder-Daten
  • Hardware-Parameter in config.yaml: mm_per_pulse=10.525, axle_width_mm=121.0
  • PWM-Minimum 150 damit Motoren sicher anlaufen

v009_fix → v009_spin_fix → v009_goal_fix

Iterative Fixes basierend auf Log-Analyse:

  • Spin-Schwelle angepasst
  • Ziel-Erreicht-Erkennung verbessert
  • Repulsionskraft von 5000 → 1500 (war zu aggressiv)

v010 — Stabilisierte Navigation (aktueller Produktionsstand)

Serverseitige Verbesserungen:

Parameter Alt Neu Grund
FORCE_ALPHA 0.15 0.35 Zu träge → Endlos-Spin
SPIN_THRESHOLD 120° 90° 120° zu hoch, Roboter überschoss
SPIN_STOP 35° Sauberes Spin-Ende
SPIN_SLOWDOWN 60° Sanftes Abbremsen
HEADING_DEADBAND Unter 6° kein Kurskorrektur
repulsion_strength 5000 1500 Sanftere Abstoßung
Controller-Frequenz 10 Hz 20 Hz Doppelte Reaktionsgeschwindigkeit
reset_robot() Roboter stoppt bei Verbindungsabbruch

Bug behoben: FORCE_ALPHA=0.15 war zu träge — der geglättete Kraftvektor folgte der echten Richtung so langsam, dass der Roboter beim Drehen immer wieder die Stopp-Schwelle überschoss und neu anfing zu drehen → Endlos-Spin.

v011 — Feedforward-Architektur (aktuell auf ESP32)

Firmware-Verbesserungen:

Neue Feedforward-Architektur: u = kS·sign(v) + kV·|v| + PID(error)

  • kS (Haftreibungskompensation): Fester PWM-Wert um Haftreibung zu überwinden
  • kV (Kennlinien-Linearisierung): PWM proportional zur Sollgeschwindigkeit
  • PID regelt nur noch den kleinen Restfehler statt alles alleine
  • Gleitendes Messfenster SPEED_WINDOW=3 (150ms) für Istgeschwindigkeit — bei 20 Pulsen/Rev ist 50ms zu grob (quantisiertes Rauschen)
  • Anti-Windup: Integral einfrieren bei PWM-Sättigung
  • Option B (aktuell aktiv): KS=0, KV=0 → reiner PID wie v009, aber mit Messfenster und Anti-Windup

Bugs gefunden und behoben in v011:

Bug 1: Motoren drehten nicht

  • Ursache: In v011 wird current_pwm = ff + pid_out · sign direkt gesetzt statt aufaddiert
  • Mit KS=KV=0 gibt feedforward() 0 zurück → PWM bleibt auf der ersten Stufe, zu klein für Motoranlauf
  • Fix: Wenn KS=KV=0 → aufaddieren wie v009 (current_pwm += pid_out · sign) + PWM_MIN erzwingen
if KS_L == 0.0 and KV_L == 0.0:
    current_pwm_l += pid_out_l * sign_l          # aufaddieren
    if sign_l > 0 and current_pwm_l < PWM_MIN:
        current_pwm_l = float(PWM_MIN)            # Mindest-PWM
else:
    current_pwm_l = ff_l + pid_out_l * sign_l    # Feedforward-Modus

8. Deployment (Server)

Voraussetzungen

# Python-Umgebung
python3 -m venv /opt/vision-venv
source /opt/vision-venv/bin/activate
pip install fastapi uvicorn pupil-apriltags opencv-python-headless numpy pyyaml

Aktuellen Stand deployen

# Dateien kopieren
scp -r v010/* root@192.168.0.250:/opt/robot-tracker-v010/

# Server starten
ssh root@192.168.0.250
cd /opt/robot-tracker-v010
/opt/vision-venv/bin/python -m uvicorn app:app --host 0.0.0.0 --port 80

Schnell neu starten

ssh root@192.168.0.250 "pkill -f uvicorn; sleep 1; \
  cd /opt/robot-tracker-v010 && \
  /opt/vision-venv/bin/python -m uvicorn app:app --host 0.0.0.0 --port 80"

Nur collision.py neu laden (kein Neustart nötig bei Code-Änderungen)

scp collision.py root@192.168.0.250:/opt/robot-tracker-v010/
# dann Server neu starten

Logs einsehen

ssh root@192.168.0.250
ls -lt /opt/robot-tracker-v010/logs/
cat /opt/robot-tracker-v010/logs/nav_20260401_192534.csv | head -5

Log-Format

timestamp, elapsed_s, robot_x_mm, robot_y_mm, robot_theta,
goal_x_mm, goal_y_mm, dist_to_goal_mm, angle_error_deg,
cmd_speed_l, cmd_speed_r, cmd_status,
num_obstacles, nearest_obstacle_id, nearest_obstacle_dist_mm,
all_obstacle_dists

cmd_status kann sein: straight, curving, spinning


9. Firmware flashen (ESP32)

Voraussetzungen

# esptool aus Freenove-Toolchain
~/Downloads/Freenove_ESP32_S3_WROOM_Board_Lite-main/Python/Python_Firmware/

# mpremote für REPL-Zugriff
pip install mpremote

Gerät erkennen

Der ESP32-S3 erscheint unter Ubuntu als /dev/ttyACM0 (native USB, nicht /dev/ttyUSB*).

ls /dev/ttyACM*

MicroPython flashen (nur wenn nötig)

# Flash löschen
python esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash

# MicroPython flashen (v1.23.0)
python esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash \
  -z 0x0 ESP32_GENERIC_S3-SPIRAM_OCT-20240602-v1.23.0.bin

Firmware-Datei übertragen

# main.py auf ESP32 kopieren
mpremote connect /dev/ttyACM0 fs cp main.py :main.py

# Neustart
mpremote connect /dev/ttyACM0 reset

REPL für Debugging

mpremote connect /dev/ttyACM0
# Dann Ctrl+C → interaktive Python-Konsole

10. Konfiguration (config.yaml)

camera:
  rtsp_url: "rtsp://192.168.0.30:8554/live"
  width: 1280
  height: 720
  rtsp_transport: "udp"
  reconnect_delay: 3.0

server:
  host: "0.0.0.0"
  port: 80
  broadcast_hz: 30

tracker:
  tag_family: "tag36h11"
  detector_threads: 2
  quad_decimate: 1.0
  ema_alpha_pos: 0.35       # Positions-Glättung (0=träge, 1=roh)
  ema_alpha_ang: 0.35       # Winkel-Glättung
  ema_alpha_vel: 0.25       # Geschwindigkeits-Glättung
  hold_seconds: 0.80        # Tag-Verlust Toleranz
  predict_seconds: 0.25     # Prediction-Horizont

robots:
  tcp_port: 8765
  robot_tag_ids: [91]
  goal_radius: 80           # mm — Ziel gilt als erreicht
  speed_base: 160           # mm/s — Fahrgeschwindigkeit
  speed_turn: 90            # mm/s — Spin-Geschwindigkeit
  speed_min: 50
  speed_max: 350
  angle_threshold: 120      # Grad — nur noch für Spin-Trigger

calibration:
  reference_tags:
    0: [0.0,   0.0  ]
    1: [100.5, 0.0  ]
    2: [0.0,   181.0]
    3: [100.5, 181.0]

obstacles:
  safe_distance: 150        # mm — Abstoßungsbereich
  repulsion_strength: 1500  # Stärke der Abstoßung
  obstacle_tag_ids: [11,12,21,22,31,32,41,42,51,52,61,62,71,72,81,82,92]

robot_hardware:
  wheel_diameter_mm: 67.0
  wheel_circumference_mm: 210.5
  encoder_pulses_per_rev: 20
  mm_per_pulse: 10.525       # 210.5 / 20
  axle_width_mm: 121.0       # Radmitte links → Radmitte rechts
  pulses_per_360deg: 36.1    # π × axle_width / mm_per_pulse

11. Kommunikationsprotokoll

Server → Roboter (TCP, newline-delimited JSON)

{"speed_l": 160.0, "speed_r": 160.0}

Beide Werte in mm/s. Negativ = rückwärts. 0.0 = Stop.

Roboter → Server (alle 100 ms)

{
  "robot_id": 91,
  "enc_l": 1234,
  "enc_r": 1235,
  "speed_l": 158.3,
  "speed_r": 161.1,
  "pwm_l": 420,
  "pwm_r": 435
}
Feld Bedeutung
enc_l/r Absoluter Encoder-Zähler (monoton steigend)
speed_l/r Istgeschwindigkeit in mm/s (aus gleitendem Fenster)
pwm_l/r Aktueller PWM-Wert (0950)

12. Regler-Tuning

Serverseitig (collision.py)

Parameter Aktuell Wirkung
SPIN_THRESHOLD 90° Ab diesem Winkel dreht Roboter auf der Stelle
SPIN_STOP 35° Unterhalb: Spin beendet, Kurvenfahrt beginnt
SPIN_SLOWDOWN 60° Spin wird sanfter
FORCE_ALPHA 0.35 EMA-Glättung des Kraftvektors (0=träge, 1=roh)
HEADING_DEADBAND Winkeltoleranz für Geradeausfahrt
repulsion_strength 1500 Abstoßungsstärke von Hindernissen
goal_radius 80 mm Ziel gilt als erreicht

ESP32-seitig (main.py) — Option A: Feedforward aktivieren

Schritt 1 — kS bestimmen (Haftreibung):
  → speed_base sehr klein setzen (z.B. 20 mm/s)
  → KS_L/R erhöhen bis Räder gerade anfangen zu drehen
  → Typisch: 130170

Schritt 2 — kV bestimmen (Kennlinie):
  → speed_base = 160 mm/s, KS bereits gesetzt
  → Log beobachten: pwm_l/pwm_r bei Geradeausfahrt ablesen
  → kV = (PWM_gemessen - kS) / 160
  → Typisch: 2.03.5

Schritt 3 — PID reduzieren:
  → KP von 1.2 auf ~0.4 reduzieren
  → KI von 0.3 auf ~0.08 reduzieren
  → KD bleibt ~0.02

Schritt 4 — Links/Rechts-Asymmetrie:
  → kS_L ≠ kS_R fein abgleichen bis Geradeausfahrt ohne Drift

ESP32-seitig — Option B (aktuell): Reiner PID

KS_L = KS_R = 0.0   # Feedforward aus
KV_L = KV_R = 0.0
KP = 1.2
KI = 0.3
KD = 0.05
PWM_MIN = 150        # Erzwungenes Mindest-PWM

13. Bekannte Probleme & Workarounds

Problem Ursache Workaround / Fix
Endlos-Spin FORCE_ALPHA zu klein, Kraftvektor folgt zu langsam FORCE_ALPHA=0.35, SPIN_THRESHOLD=90°
Motoren laufen nicht an PWM_MIN fehlt im direkten Feedforward-Pfad PWM_MIN=150 erzwingen wenn KS=0
ESP32 bootet nicht GPIO 12 belegt (Strapping-Pin) GPIO 12 nicht verwenden
ESP32 beschädigt Gleichzeitig USB + Powerbank + Motoren Stets separate Stromkreise!
Kamera-Freeze ffmpeg verliert RTSP-Stream Watchdog mit Auto-Reconnect in v005+
Tag nicht erkannt Schlechte Lichtverhältnisse Gleichmäßige Beleuchtung, möglichst kein direktes Sonnenlicht
Ziel nie erreicht goal_radius zu klein, Kamera-Jitter goal_radius auf 100120 mm erhöhen
Ziel wird weggedrückt Hindernis-Repulsion nahe am Ziel repulsion_strength bei kleiner dist_to_goal abschwächen (TODO)

14. Roadmap

Kurzfristig (Tuning)

  • Feedforward-Koeffizienten kS, kV experimentell bestimmen und aktivieren
  • goal_radius auf 100 mm erhöhen
  • Repulsion nahe am Ziel abschwächen
  • axle_width_mm und pulses_per_360deg exakt nachmessen

Mittelfristig (Architektur)

  • v/ω-Regler statt direktem Innen-/Außenrad-Schema (collision.py)
  • Weiches Dead-Reckoning/Vision-Fusion (kein harter Sprung bei Tag-Wiedersehen)
  • Mehrere Roboter gleichzeitig (IDs 91, 92, ...)
  • Seeed Grove I2C TB6612 für einfachere Multi-Roboter-Verdrahtung

Langfristig (Unterrichts-Interface)

  • Blockly-Interface für Schülerinnen und Schüler
  • Ziel per Klick im Browser setzen (bereits teilweise vorhanden)
  • Mehrere Ziele als Route definieren
  • Begegnungs-/Kooperationsszenarien

Lizenz

Entwickelt für Unterrichtszwecke an der Pädagogischen Hochschule Weingarten.
Internes Projekt — nicht zur öffentlichen Weiterverbreitung bestimmt.


Zuletzt aktualisiert: April 2026 — v011 Firmware, v010 Server