No description
  • C++ 93.6%
  • Python 2.5%
  • C 2.3%
  • CMake 1%
  • Shell 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Lufti f2491dd9b7 Fix: Rot-Kanal in /api/led-switch wurde nie uebernommen
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>
2026-08-07 20:14:44 +02:00
.forgejo/workflows Pull-basiertes Firmware-Update: ESP holt sich Updates selbst von Forgejo 2026-07-31 11:25:58 +02:00
.vscode komentare entfernt 2026-07-31 12:10:46 +02:00
include Initial commit: ESP32 Panzer-Rover Firmware + MiniPC Web-Steuerung 2026-07-30 18:11:18 +02:00
lib BMS (Ruipu/OKAI) per SoftwareSerial anbinden + im Web-UI anzeigen 2026-08-04 21:02:15 +02:00
src Fix: Rot-Kanal in /api/led-switch wurde nie uebernommen 2026-08-07 20:14:44 +02:00
test Initial commit: ESP32 Panzer-Rover Firmware + MiniPC Web-Steuerung 2026-07-30 18:11:18 +02:00
web-server Initial commit: ESP32 Panzer-Rover Firmware + MiniPC Web-Steuerung 2026-07-30 18:11:18 +02:00
.gitignore Initial commit: ESP32 Panzer-Rover Firmware + MiniPC Web-Steuerung 2026-07-30 18:11:18 +02:00
flake.lock Initial commit: ESP32 Panzer-Rover Firmware + MiniPC Web-Steuerung 2026-07-30 18:11:18 +02:00
flake.nix Initial commit: ESP32 Panzer-Rover Firmware + MiniPC Web-Steuerung 2026-07-30 18:11:18 +02:00
KONFIGURATION.md Doku: BMS-Anbindung und neue Pin-Belegung dokumentiert 2026-08-04 21:32:04 +02:00
ota-via-minipc.sh Initial commit: ESP32 Panzer-Rover Firmware + MiniPC Web-Steuerung 2026-07-30 18:11:18 +02:00
platformio.ini BMS auf Hardware-UART0 (Serial) statt SoftwareSerial 2026-08-04 22:40:26 +02:00
README.md Doku: BMS-Anbindung und neue Pin-Belegung dokumentiert 2026-08-04 21:32:04 +02:00
version.py Pull-basiertes Firmware-Update: ESP holt sich Updates selbst von Forgejo 2026-07-31 11:25:58 +02:00

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.h das 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:

  1. Push auf den Branch deploy im Forgejo-Repo (codeberg.lufti.space/lufti/Projekt-Drone).
  2. 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 Branch firmware-latest.
  3. Der ESP32 prüft per Knopf „Nach Update suchen" im Web-Admin-Tab Update, oder automatisch alle 6 Stunden, ob firmware.json eine andere Version als die eigene (FIRMWARE_VERSION) enthält.
  4. Bei Unterschied lädt er firmware.bin per 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