- C++ 93.6%
- Python 2.5%
- C 2.3%
- CMake 1%
- Shell 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Der Query-Parser suchte den Schluessel mit indexOf("r="). In der Query
"master=1&fixed=1&r=255&g=0&b=0" trifft das aber schon das "r=" in
"master=" - Rot bekam damit immer den Wert von master, also 1.
Folge: Rot (255,0,0) landete als (1,0,0) am Streifen und blieb dunkel,
Weiss (255,255,255) wurde zu (1,255,255) und damit cyan statt weiss.
Blau und Gruen waren zufaellig korrekt, weil "g=" und "b=" in keinem
frueheren Schluessel vorkommen.
Der Parser akzeptiert den Schluessel jetzt nur noch am Query-Anfang oder
direkt hinter einem '&'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
| .forgejo/workflows | ||
| .vscode | ||
| include | ||
| lib | ||
| src | ||
| test | ||
| web-server | ||
| .gitignore | ||
| flake.lock | ||
| flake.nix | ||
| KONFIGURATION.md | ||
| ota-via-minipc.sh | ||
| platformio.ini | ||
| README.md | ||
| version.py | ||
Projekt-Drone — Panzer-Rover Controller 🐱⚡
ESP32-basierter Panzerlenk-Controller (WT32-ETH01) für zwei VESC-Motoren, gesteuert per Funkfernsteuerung (i-BUS oder ExpressLRS/CRSF) oder über eine Web-Oberfläche (Ethernet).
📋 Übersicht
Das Projekt realisiert eine 1-Stick-Mixing-Steuerung für einen Kettenantrieb mit zwei VESC-gesteuerten Motoren. Der ESP32 empfängt Funksignale (FlySky i-BUS oder ExpressLRS/CRSF) oder Web-Befehle und leitet Duty-Cycle-Werte an beide VESCs weiter.
Kernfunktionen
- 1-Stick-Mixing: Gas (vor/zurück) + Lenkung (links/rechts) gemischt auf beide Ketten
- Zwei VESC-Ausgaben: Master-VESC per UART, zweiter VESC per CAN-Forward
- Umschaltbares RC-Protokoll: FlySky i-BUS oder ExpressLRS/CRSF — eine
#define-Zeile - Ramping: einstellbare Beschleunigungs-/Bremskurve (S1-Poti), bidirektional
- Tempolimit: über den linken Stick (CH3) stufenlos begrenzbar
- WS2812B-Lichter: Positionslicht, Bremslicht, Rückfahrlicht, Blinker
- BMS-Telemetrie: Ruipu/OKAI-Batterie per SoftwareSerial (SoC, Spannung, Strom, Leistung, Zell- und Temperatur-Extremwerte) im Web-UI
- Web-Admin (Ethernet): Status/Telemetrie, WiFi/IP-Config, Gamepad + Tastatur + virtueller Joystick, Kamera-Vorschau
- MiniPC-Web-Steuerung: separater Python-Server mit PTZ-Kamera
- Automatisches Firmware-Update: ESP holt sich neue Firmware selbst per HTTPS von Forgejo (Knopf im Web-Admin oder alle 6 h automatisch) — CI baut bei jedem Push auf
deploy - Failsafe: Signal-Timeout & Arm-Schalter stoppen sofort
🔌 Hardware
Board
| Komponente | Modell |
|---|---|
| Mikrocontroller | WT32-ETH01 (ESP32 + LAN8720 Ethernet) |
| Antrieb | 2× VESC (Master per UART, 2. per CAN-Forward) |
| Funke | FlySky i-BUS oder ExpressLRS/CRSF (RadioMaster TX12 Mk II) |
| BMS | Ruipu/OKAI-Batterie per SoftwareSerial (9600 Baud) |
| Licht | WS2812B LED-Streifen (50 LEDs) |
Pinbelegung
| Funktion | Pin | Beschreibung |
|---|---|---|
| VESC UART TX | GPIO4 | → VESC RX |
| VESC UART RX | GPIO39 | ← VESC TX (Telemetrie; input-only) |
| i-BUS / CRSF RX | GPIO2 | Signal vom Empfänger |
| BMS RX | GPIO35 | ← BMS TX (Ruipu/OKAI, input-only) |
| BMS TX | GPIO32 | → BMS RX (Abfrage-Befehl) |
| WS2812B DATA | GPIO17 | LED-Streifen |
| Ethernet | LAN8720 | MDC=23, MDIO=18, POWER=16, CLK=GPIO0 (fest verdrahtet) |
ℹ️ i-BUS und CRSF nutzen denselben Pin (GPIO2) — zum Wechseln nur den Empfänger tauschen und in
config.hdas Protokoll umstellen, kein Umlöten nötig.🔋 BMS hängt an SoftwareSerial (GPIO35/32, 9600 Baud), da alle drei Hardware-UARTs belegt sind (Debug/VESC/Funke). Logikpegel 3,3 V — bei einem 5-V-BMS einen Pegelwandler auf die RX-Leitung (GPIO35) setzen.
VESC-Einstellungen (VESC Tool)
- APP to Use:
UART - Enable Permanent UART:
True - Baudrate:
115200
🛰️ RC-Protokoll & Kanäle
In src/config.h eine Zeile umstellen:
#define RC_PROTOCOL RC_PROTOCOL_IBUS // FlySky i-BUS
#define RC_PROTOCOL RC_PROTOCOL_CRSF // ExpressLRS / TBS Crossfire (aktuell)
Kanalbelegung (RadioMaster TX12 Mk II, Mode 2)
| Kanal | Quelle | Funktion |
|---|---|---|
| CH2 | Elevator (R-Stick vertikal) | Gas vor/zurück (hochziehen = fahren) |
| CH1 | Aileron (R-Stick horizontal) | Lenkung links/rechts |
| CH3 | Throttle (L-Stick vertikal) | Topspeed-Limit (unten = min, oben = max) |
| CH8 | SB-Schalter | Arm/Not-Aus (>1700 = scharf) |
| CH5 | S1-Poti | Rampe: links = schnell (1 s / 0,5 s), rechts = sanft (5 s / 5 s) |
CRSF-Werte (172–1811) werden intern auf denselben Bereich wie i-BUS (1000–2000) gemappt — die gesamte Fahrlogik bleibt identisch.
⚙️ Konfiguration
Alle Einstellungen zentral in src/config.h. Übersicht siehe KONFIGURATION.md.
Wichtige Konstanten
| Parameter | Wert | Beschreibung |
|---|---|---|
VESC_TX_PIN / VESC_RX_PIN |
4 / 35 | UART zum Master-VESC |
CAN_ID |
1 | CAN-ID des zweiten VESC |
CH_THROTTLE / CH_STEER |
2 / 1 | Gas / Lenkung |
CH_ARM / CH_SPEED |
8 / 3 | Arm-Schalter / Tempolimit |
MAX_DUTY |
0.90 | Duty-Obergrenze |
MIN_SCALE |
0.10 | unteres Tempolimit (10 %) |
DEADBAND |
0.05 | Totband um Stick-Mitte |
NET_MAX_DUTY |
0.10 | Schrittgeschwindigkeit Web/Tastatur |
INVERT_LEFT / INVERT_RIGHT |
false / true | Motor-Drehrichtung |
ARM_THRESHOLD |
1700 | Schalter-EIN-Schwelle |
SIGNAL_TIMEOUT_MS |
150 | Funkausfall → Stopp |
SENDE_MS |
20 | VESC-Ausgabefrequenz (50 Hz) |
LED_COUNT |
50 | Anzahl WS2812B-LEDs |
Netzwerk
| Parameter | Wert |
|---|---|
USE_DHCP |
0 (feste IP) |
STATIC_IP |
192.168.8.50 |
GATEWAY |
192.168.8.1 |
UDP_PORT |
8888 (Steuerbefehle) |
DEBUG_TCP_PORT |
23 (Live-Log übers Netz) |
🌐 Web-Steuerung
Es gibt zwei Steuer-Oberflächen — es sollte immer nur eine gleichzeitig aktiv sein:
1. ESP32 Web-Admin (direkt auf dem Controller, Port 80)
http://192.168.8.50/ → Tabs: Status · Ethernet · WiFi · Kanäle · Steuerung · Kamera · Update · Log
- Status: Ethernet/WiFi/Funke/UDP, VESC-Telemetrie und Batterie (BMS) — SoC, Spannung, Strom, Leistung, Zelle min/max, Temp min/max, online/offline
- Kanäle: RC-Kanalbelegung + Invert-Flags zur Laufzeit umstellen, kein Neu-Flashen nötig
- Steuerung: Gamepad, Tastatur (WASD + Leertaste = Not-Aus), virtueller Touch-/Maus-Joystick
- Kamera: Digest-Auth-Proxy (Hikvision ISAPI/MJPEG) — der ESP holt das Bild selbst, Zugangsdaten verlassen ihn nie
- Update: aktuelle Firmware-Version, Knopf "Nach Update suchen" (siehe Abschnitt "Automatisches Firmware-Update" weiter unten)
- Standardmäßig aus (Häkchen im „Steuerung"-Tab), damit sie nicht mit dem MiniPC-Server kollidiert
2. MiniPC Web-Server (web-server/web_control_server.py)
Python Flask + flask-sock, Port 8080. Sendet UDP-Steuerbefehle an den ESP und bindet die Hikvision-PTZ-Kamera ein.
cd web-server
python3 web_control_server.py # (Flask, flask-sock, requests)
Kamera-Substream (.../102) auf MJPEG stellen — bei H.264 liefert der Preview-Endpunkt 403.
Steuer-Schnittstellen des ESP32
UDP (Port 8888):
| Befehl | Wirkung |
|---|---|
E |
Not-Aus |
R |
Reset (Not-Aus aufheben) |
C <t> <st> |
Fahren (Gas & Lenkung, je −100…100) |
HTTP-API (Auszug):
| Endpoint | Zweck |
|---|---|
GET /api/status |
JSON: IP, RC-Link, Protokoll, UDP, VESC- und BMS-Werte (SoC, V, A, W, Zelle/Temp) |
GET /api/drive?t=&s=&e= |
Fahren (nur wenn Web-Steuerung aktiviert) |
GET /api/control/enable?on= |
Web-Steuerung an/aus |
GET /api/camera/proxy |
Einzelbild von der Kamera (JPEG) |
GET /api/update/status |
JSON: aktuelle Firmware-Version, Update-Status, ob gerade aktiv |
GET /api/update/run |
Prüft auf Update und flasht bei Unterschied (blockierend, nur wenn nicht armiert) |
💡 Lichter (WS2812B, 50 LEDs)
Ein durchgehender Strang: erste Hälfte vorne, zweite hinten.
| Funktion | Farbe | Bedingung |
|---|---|---|
| Positionslicht | Weiß | immer (vorne) |
| Bremslicht | Rot | starkes Abbremsen |
| Rückfahrlicht | Weiß | Rückwärtsfahrt (mittige Zone hinten) |
| Blinker | Amber | Lenkausschlag > Schwelle (mit Entprellung) |
🔒 Sicherheit
- Arm-Logik: Der Arm-Schalter (CH8) muss erst auf AUS stehen, bevor er scharf schalten kann (
seenDisarm). - Kill-Switch: Ohne Arm kein Antrieb (
RC_KILLSWITCH = 1). - Signal-Timeout: Bleiben RC-Frames > 150 ms aus → sofortiger Stopp.
- Not-Aus: Leertaste / UDP
E/netEstop→ alle VESCs auf 0. - Ramping-Reset: Bei Disarm/Not-Aus wird die Rampe auf 0 gesetzt (kein Ruck beim nächsten Arm).
- Update-Sperre: Ein Firmware-Update (Download + Flash blockiert die Hauptschleife) wird verweigert, solange das Fahrzeug armiert ist.
🛠️ Build & Flash (PlatformIO)
[env:wt32-eth01]
platform = pioarduino (platform-espressif32, stable)
board = wt32-eth01
framework = arduino
extra_scripts = pre:version.py ; bettet Git-Kurz-SHA als FIRMWARE_VERSION ein
lib_deps = ; IBusBM, ArduinoJson, RuipuBattery liegen lokal in lib/
FastLED@^3.9.2
plerup/EspSoftwareSerial@^8.2.0 ; SoftwareSerial fuers BMS
upload_protocol = espota ; manueller Fallback, siehe unten
upload_port = 192.168.8.192
pio run # bauen
pio run -t upload # bauen + manuell per espota flashen (siehe Fallback unten)
🔄 Automatisches Firmware-Update
Der ESP32 holt sich Updates selbst (Pull), statt dass von außen auf ihn geflasht wird — das umgeht das Netbird/MiniPC-Problem des alten OTA-Wegs, da der ESP nur eine ausgehende HTTPS-Verbindung braucht.
Ablauf:
- Push auf den Branch
deployim Forgejo-Repo (codeberg.lufti.space/lufti/Projekt-Drone). - Der selbstgehostete Forgejo-Actions-Runner baut die Firmware (.forgejo/workflows/build.yml) und veröffentlicht
firmware.bin+firmware.json(Versions-Manifest mit Git-Kurz-SHA) auf den Branchfirmware-latest. - Der ESP32 prüft per Knopf „Nach Update suchen" im Web-Admin-Tab Update, oder automatisch alle 6 Stunden, ob
firmware.jsoneine andere Version als die eigene (FIRMWARE_VERSION) enthält. - Bei Unterschied lädt er
firmware.binper HTTPS herunter und flasht sich selbst (HTTPUpdate), danach Neustart.
Sicherheit: Ein Update wird verweigert, solange das Fahrzeug armiert ist (Download/Flash blockiert die Hauptschleife). Die HTTPS-Verbindung nutzt aktuell setInsecure() (keine Zertifikatsprüfung) — bewusste Vereinfachung fürs eigene Heimnetz, kein Versehen.
⚠️ Damit der ESP diese Fähigkeit überhaupt hat, muss er einmal manuell eine Firmware mit dem Update-Mechanismus bekommen (siehe Fallback unten) — danach läuft alles Weitere über den
deploy-Branch.
Manueller Fallback: OTA über den MiniPC
./ota-via-minipc.sh
Das Skript baut lokal, verbindet sich per SSH zum MiniPC (lufti@192.168.8.214) und führt dort espota.py aus — nötig für den allerersten Flash (bevor der Update-Mechanismus existiert) oder falls Forgejo/der Runner mal nicht verfügbar ist. Grund für den Umweg: Das ESP-Netz (192.168.8.0/24) ist via Netbird nur vom MiniPC aus erreichbar, weil espota eine neue eingehende Verbindung zum ESP braucht, die Netbirds Firewall sonst blockiert.
Live-Debug übers Netzwerk (ohne USB)
pio device monitor --port socket://192.168.8.50:23
🏗️ Verzeichnisstruktur
Projekt-Drone/
├── platformio.ini # PlatformIO-Konfiguration (Board wt32-eth01, OTA)
├── version.py # Pre-Build-Skript: bettet Git-Kurz-SHA als FIRMWARE_VERSION ein
├── .forgejo/workflows/build.yml # CI: baut bei Push auf "deploy", veröffentlicht auf "firmware-latest"
├── flake.nix / flake.lock # Nix-Entwicklungsumgebung
├── ota-via-minipc.sh # manueller OTA-Fallback über den MiniPC
├── KONFIGURATION.md # vollständige Konfig-Referenz
├── src/
│ ├── config.h # zentrale Einstellungen (Pins, IPs, Kanäle, Fahrverhalten, Update-URLs)
│ ├── main.cpp # Firmware (RC, VESC, Ramping, LED, Web-Admin, Kamera-Proxy, Update)
│ ├── web_ui.h # Web-Admin-Oberfläche (HTML/CSS/JS)
│ └── old/ # alte Test-Stände
├── lib/
│ ├── IBusBM-master/ # i-BUS-Decoder (FlySky)
│ ├── ArduinoJson/ # JSON (Web-API)
│ └── OKAI-Battery-Lib-main/ # RuipuBattery (Ruipu/OKAI-BMS-Protokoll)
└── web-server/
└── web_control_server.py # MiniPC-Web-Steuerung (Flask + PTZ-Kamera)
⚠️ Erste Fahrt
WICHTIG: Vor dem ersten Test das Fahrzeug aufbocken — Räder/Ketten müssen frei drehen können!
- Antrieb erst nach dem Armen (CH8-Schalter) aktiv.
- Nur eine Web-Steuerung gleichzeitig nutzen (ESP-Admin oder MiniPC-Server).
📜 Lizenz
Eigenes Projekt — alle Rechte vorbehalten. Autor: Lufti