ESP32 und Arduino

ESP32 GPS Tracker MQTT: NEO-6M-Positionen auf einer Live-Webkarte

Lesen Sie ein NEO-6M-GPS-Modul mit dem ESP32 aus, veröffentlichen Sie Position, Geschwindigkeit und Fix-Qualität per MQTT nur dann, wenn sich das Gerät bewegt, und zeigen Sie die Route live auf einer Leaflet-Karte über Secure WebSocket an.

ESP32 mit NEO-6M-GPS-Modul, das in einem ESP32-GPS-Tracker-MQTT-Projekt Positionen an eine Live-Webkarte sendet
Probieren Sie es auf Ihrem eigenen MQTT-Broker ausHolen Sie sich im Free-Tarif einen privaten, verwalteten MQTT-Broker und starten Sie mit fertigen Codebeispielen.
Kostenlos testen Codebeispiele Keine Kreditkarte erforderlich

Ein ESP32-GPS-Tracker-MQTT-Projekt zeigt ein sich bewegendes Gerät mit sehr wenig Code nahezu in Echtzeit auf einer Karte. In diesem Tutorial lesen Sie ein NEO-6M-Modul mit TinyGPSPlus aus, veröffentlichen Position, Geschwindigkeit und Fix-Qualität an broker.connectmq.com nur dann, wenn sich das Gerät tatsächlich bewegt, und zeichnen die Live-Route auf einer Leaflet-Webkarte, die über Secure WebSocket abonniert.

Wie die Teile zusammenspielen

Der Datenfluss besteht aus drei Teilen:

  1. NEO-6M zum ESP32. Das GPS-Modul sendet NMEA-Sätze über einen UART mit 9600 Baud. TinyGPSPlus parst sie in Breitengrad, Längengrad, Geschwindigkeit und mehr.
  2. ESP32 zum Broker. Der ESP32 veröffentlicht eine kompakte JSON-Position per MQTT über TCP-Port 1883 (oder in der Produktion TLS auf 8883).
  3. Broker zum Browser. Eine statische Webseite verbindet sich mit MQTT.js über Secure WebSocket auf Port 8084, Pfad /mqtt, und bewegt eine Markierung auf der Karte.

Weil MQTT Publisher und Subscriber entkoppelt, können Sie die Karte auf mehreren Geräten gleichzeitig öffnen, und dank einer Retained Message mit der letzten Position zeigt eine frisch geöffnete Karte den Tracker sofort an, statt auf den nächsten Fix zu warten. Wenn MQTT im Browser neu für Sie ist, erklärt MQTT über WebSocket den Transport.

Bauteile und Verdrahtung

  • Ein ESP32-Entwicklungsboard.
  • Ein NEO-6M-GPS-Modul mit Keramik-Patchantenne (das verbreitete Board GY-NEO6MV2).
  • Bibliotheken: TinyGPSPlus von Mikal Hart und PubSubClient.
NEO-6M-Pin ESP32-Pin Hinweise
VCC 3V3 (oder 5V, wenn das Board einen Spannungsregler hat) Prüfen Sie Ihr Modul; die meisten GY-NEO6MV2-Boards vertragen beides
GND GND
TX GPIO 16 (UART2 RX) GPS-Daten in den ESP32
RX GPIO 17 (UART2 TX) Nur zum Senden von Konfigurationsbefehlen nötig

Der ESP32 hat drei Hardware-UARTs, sodass das GPS einen eigenen Port erhält und Serial für das Debugging frei bleibt. Verwenden Sie kein Software-Serial.

Den ersten Fix bekommen

Ein NEO-6M braucht beim Kaltstart freie Sicht zum Himmel und kann mehrere Minuten bis zum ersten Fix benötigen. In Innenräumen bekommt er möglicherweise nie einen. Die blaue LED blinkt auf den meisten Modulen einmal pro Sekunde, wenn ein gültiger Fix vorliegt. Praktische Tipps:

  • Führen Sie den ersten Test im Freien oder direkt an einem Fenster durch, mit der Antenne nach oben.
  • Halten Sie den ESP32 und seine WLAN-Antenne einige Zentimeter von der GPS-Antenne entfernt.
  • Die kleine Pufferbatterie auf dem Modul speichert die Almanach-Daten, sodass der nächste Start viel schneller geht. Sind Neustarts immer langsam, ist die Batterie möglicherweise leer.

Sendestrategie: bewegungsbasiert statt zeitbasiert

Jede Sekunde zu veröffentlichen, verschwendet im Stand Daten und überflutet Subscriber. Der Sketch veröffentlicht, wenn:

  • sich das Gerät seit der letzten Veröffentlichung um mehr als eine Schwellendistanz bewegt hat (standardmäßig 25 m), oder
  • ein Heartbeat-Intervall abgelaufen ist (60 s), damit die Karte weiß, dass der Tracker im Stand noch lebt.
Topic Retained Payload
connectmq/demo/gps6j1v/position Ja {"lat":41.008238,"lng":28.978359,"speed":32.4,"course":118.2,"alt":44.1,"sats":8,"hdop":1.1}
connectmq/demo/gps6j1v/status Ja online, offline, no-fix

Der ESP32-GPS-Tracker-MQTT-Sketch

#include <WiFi.h>
#include <PubSubClient.h>
#include <TinyGPSPlus.h>

const char* WIFI_SSID     = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST     = "broker.connectmq.com";
const uint16_t MQTT_PORT  = 1883;
const char* PREFIX        = "connectmq/demo/gps6j1v";   // eigene zufällige ID verwenden

const int GPS_RX = 16, GPS_TX = 17;
const double MIN_MOVE_M    = 25.0;
const uint32_t HEARTBEAT_MS = 60000;
const uint32_t MIN_GAP_MS   = 2000;     // nie schneller als dies

TinyGPSPlus gps;
HardwareSerial gpsSerial(2);
WiFiClient   net;
PubSubClient mqtt(net);
String clientId, tPosition, tStatus;

double lastLat = 0, lastLng = 0;
bool havePublished = false;
uint32_t lastPublishMs = 0;
bool fixReported = false;

void connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
    while (gpsSerial.available()) gps.encode(gpsSerial.read());   // weiter parsen
  }
  Serial.printf("Wi-Fi OK, IP %s\n", WiFi.localIP().toString().c_str());
}

void connectMqtt() {
  while (!mqtt.connected()) {
    if (mqtt.connect(clientId.c_str(), tStatus.c_str(), 1, true, "offline")) {
      mqtt.publish(tStatus.c_str(), "online", true);
      Serial.println("MQTT connected");
    } else {
      Serial.printf("MQTT failed, state %d\n", mqtt.state());
      delay(3000);
    }
  }
}

void publishPosition() {
  char payload[220];
  snprintf(payload, sizeof(payload),
           "{\"lat\":%.6f,\"lng\":%.6f,\"speed\":%.1f,\"course\":%.1f,"
           "\"alt\":%.1f,\"sats\":%lu,\"hdop\":%.1f}",
           gps.location.lat(), gps.location.lng(),
           gps.speed.isValid() ? gps.speed.kmph() : 0.0,
           gps.course.isValid() ? gps.course.deg() : 0.0,
           gps.altitude.isValid() ? gps.altitude.meters() : 0.0,
           (unsigned long)gps.satellites.value(),
           gps.hdop.isValid() ? gps.hdop.hdop() : 99.9);
  if (mqtt.publish(tPosition.c_str(), payload, true)) {
    lastLat = gps.location.lat();
    lastLng = gps.location.lng();
    lastPublishMs = millis();
    havePublished = true;
    Serial.println(payload);
  }
}

void setup() {
  Serial.begin(115200);
  gpsSerial.begin(9600, SERIAL_8N1, GPS_RX, GPS_TX);

  clientId  = "esp32-gps-" + String((uint32_t)ESP.getEfuseMac(), HEX);
  tPosition = String(PREFIX) + "/position";
  tStatus   = String(PREFIX) + "/status";

  connectWifi();
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setBufferSize(512);
  connectMqtt();
}

void loop() {
  while (gpsSerial.available()) gps.encode(gpsSerial.read());

  if (WiFi.status() != WL_CONNECTED) connectWifi();
  if (!mqtt.connected()) connectMqtt();
  mqtt.loop();

  bool fixValid = gps.location.isValid() && gps.location.age() < 3000;
  if (!fixValid) {
    if (fixReported || millis() - lastPublishMs > HEARTBEAT_MS) {
      mqtt.publish(tStatus.c_str(), "no-fix", true);
      fixReported = false;
      lastPublishMs = millis();
    }
    if (millis() > 10000 && gps.charsProcessed() < 10) {
      Serial.println("No NMEA data: check GPS TX -> GPIO 16 wiring");
    }
    return;
  }
  if (!fixReported) {
    mqtt.publish(tStatus.c_str(), "online", true);
    fixReported = true;
  }

  uint32_t sinceLast = millis() - lastPublishMs;
  if (sinceLast < MIN_GAP_MS) return;

  double moved = havePublished
      ? TinyGPSPlus::distanceBetween(lastLat, lastLng, gps.location.lat(), gps.location.lng())
      : 1e9;
  if (moved >= MIN_MOVE_M || sinceLast >= HEARTBEAT_MS) {
    publishPosition();
  }
}

Die Prüfung von gps.location.age() ist wichtig: TinyGPSPlus behält die letzte gültige Position auch nach dem Verlust des Fixes, ohne diese Prüfung würden Sie also weiter eine veraltete Position veröffentlichen, als wäre sie aktuell.

Die Live-Webkarte

Speichern Sie Folgendes als map.html und öffnen Sie die Datei in einem Browser. Sie nutzt Leaflet mit OpenStreetMap-Kacheln und MQTT.js über Secure WebSocket. Passen Sie das Präfix an Ihren Sketch an.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>ESP32 GPS tracker</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
  <style>html, body, #map { height: 100%; margin: 0; }</style>
</head>
<body>
  <div id="map"></div>
  <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
  <script src="https://unpkg.com/mqtt@5.10.1/dist/mqtt.min.js"></script>
  <script>
    const PREFIX = "connectmq/demo/gps6j1v";
    const map = L.map("map").setView([0, 0], 2);
    L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
      maxZoom: 19,
      attribution: "&copy; OpenStreetMap contributors"
    }).addTo(map);

    const marker = L.marker([0, 0]).addTo(map);
    const track = L.polyline([], { weight: 4 }).addTo(map);
    let first = true;

    const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
      clientId: "gps-map-" + Math.random().toString(16).slice(2, 10),
      protocolVersion: 5,
      clean: true
    });

    client.on("connect", () => client.subscribe(PREFIX + "/#", { qos: 0 }));

    client.on("message", (topic, payload) => {
      if (topic === PREFIX + "/status") {
        marker.bindTooltip("Status: " + payload.toString()).openTooltip();
        return;
      }
      if (topic !== PREFIX + "/position") return;
      const p = JSON.parse(payload.toString());
      const pos = [p.lat, p.lng];
      marker.setLatLng(pos).bindPopup(
        `Speed ${p.speed} km/h<br>Satellites ${p.sats}<br>HDOP ${p.hdop}`);
      track.addLatLng(pos);
      if (first) { map.setView(pos, 16); first = false; } else { map.panTo(pos); }
    });
  </script>
</body>
</html>

Die Retained-Nachricht position kommt an, sobald die Seite abonniert, sodass die Markierung sofort zur letzten bekannten Position springt. Die Polylinie wächst dann mit jeder neuen Position. Die Optionen für Markierungen, Popups und Polylinien beschreibt die Leaflet-Dokumentation.

Den ESP32-GPS-Tracker-MQTT-Ablauf testen

Beobachten Sie die Rohnachrichten parallel zur Karte:

mosquitto_sub -h broker.connectmq.com -p 1883 -i gps-view-6j1v -t "connectmq/demo/gps6j1v/#" -v

Gehen Sie mit dem Tracker an einem Smartphone-Hotspot um den Block, und die Route zeichnet sich von selbst. Standortdaten sind personenbezogene Daten, und der öffentliche MQTT-Broker ist für jeden lesbar, der das Topic kennt oder errät – betrachten Sie dies also als kurzes Experiment und verfolgen Sie dort niemals Personen. Die Seite zum öffentlichen MQTT-Broker erklärt, wofür er gedacht ist: Tests und Prototypen, keine produktiven Daten.

Payloads klein und nützlich halten

Jedes Feld in der Positionsnachricht hat einen Zweck, und es lohnt sich, dem Drang nach weiteren Feldern zu widerstehen. Sechs Nachkommastellen bei Breiten- und Längengrad entsprechen etwa zehn Zentimetern, was bereits feiner ist, als ein NEO-6M liefern kann; mehr Stellen fügen also nur Bytes hinzu. Mit Geschwindigkeit und Kurs kann die Karte ein Symbol drehen oder eine Geschwindigkeitsanzeige darstellen, ohne sie aus aufeinanderfolgenden Punkten zu berechnen. Satellitenanzahl und HDOP beschreiben die Fix-Qualität: Ein Konsument kann die Markierung ausgrauen oder einen Punkt ganz ignorieren, wenn der HDOP hoch ist oder weniger als fünf Satelliten genutzt werden.

Wenn Sie später auf ein Mobilfunkmodem umsteigen, bei dem jedes Kilobyte Geld kostet, kürzen Sie die Schlüssel (la, lo, sp) oder senden Sie ein festes Binärformat, und behalten Sie das bewegungsbasierte Senden bei. Bei einem batteriebetriebenen Tracker ist oft das GPS-Modul selbst der größte Verbraucher; der NEO-6M unterstützt Stromsparmodi, die über UBX-Befehle an seinem RX-Pin konfiguriert werden – deshalb steht diese Leitung in der Tabelle, obwohl der einfache Sketch nie etwas sendet.

Fehlerbehebung

  1. Überhaupt keine NMEA-Daten. TX und RX sind vertauscht oder die Baudrate stimmt nicht. Der NEO-6M arbeitet standardmäßig mit 9600.
  2. Daten, aber kein Fix. In Innenräumen oder die Antenne ist abgeschattet. Gehen Sie nach draußen und warten Sie beim Kaltstart einige Minuten.
  3. Position springt im Stand umher. Normales GPS-Rauschen. Erhöhen Sie MIN_MOVE_M oder verwerfen Sie Fixes mit einem HDOP über etwa 2,5.
  4. Karte bleibt bei 0,0. Der Browser erreicht Port 8084 nicht, oder das Präfix in map.html stimmt nicht überein. Prüfen Sie die Browserkonsole.
  5. Lücken während der Fahrt. Unterwegs ist kein WLAN verfügbar. Ein echter Fahrzeug-Tracker braucht ein Mobilfunkmodem; der MQTT-Code bleibt derselbe.

Einrichtung für die Produktion und Datenschutz

Für echtes Tracking erstellen Sie einen Broker unter app.connectmq.com, stellen den ESP32 auf WiFiClientSecure an Port 8883 mit dem Zertifikat ISRG Root X1 um und verbinden sich mit YOUR_CLIENT_ID, YOUR_USERNAME und YOUR_PASSWORD. Betten Sie keine Produktionszugangsdaten in eine öffentliche Webseite ein: Jeder kann sie im Seitenquelltext lesen. Stellen Sie die Karte von einem privaten Ort aus bereit oder schalten Sie ein kleines Backend zwischen Broker und öffentliche Betrachter. Wenn Sie Fahrzeuge oder Mitarbeitende tracken, gilt im DACH-Raum die DSGVO beziehungsweise das Schweizer Datenschutzgesetz; Ortung von Beschäftigten ist zudem meist mitbestimmungspflichtig. Für Anlagen wie Gabelstapler, Ladungsträger oder Container auf dem Werksgelände bietet sich ein Topic-Zweig je Standort an, etwa acme/werk-bremen/logistik/stapler-12/position. Im ConnectMQ-Portal zeigt der Live-Topic-Explorer jede Positionsaktualisierung beim Eintreffen an. Um jedem Fix einen Zeitstempel hinzuzufügen, verwenden Sie entweder die GPS-Zeit aus gps.date und gps.time oder folgen der Anleitung zum ESP32-NTP-Zeitstempel. Für Tracker, die BLE-Tags in der Nähe empfangen, ist das ESP32-BLE-MQTT-Gateway eine naheliegende Erweiterung.

Verfolgen Sie Ihre Flotte mit ConnectMQ

Ein Tracker braucht einen Broker, den Geräte und Browser gleichermaßen sicher erreichen. ConnectMQ ist ein Cloud-MQTT-Broker mit MQTT über TLS auf Port 8883 und Secure WebSocket auf Port 8084, Retained Messages für letzte bekannte Positionen und einem kostenlosen Serverless-Plan ohne Kreditkarte. Verbinden Sie Tracker und Karte und stöbern Sie dann in der Reihe zum ESP32-IoT-Tutorial nach weiteren Geräteprojekten.

Häufig gestellte Fragen

Wie oft sollte ein ESP32-GPS-Tracker per MQTT veröffentlichen?

Veröffentlichen Sie bei Bewegung plus Heartbeat statt in einem festen, schnellen Takt. Eine Schwelle von 25 m mit einem Heartbeat von einer Minute passt für Fußgänger und langsame Fahrzeuge.

Warum eine Retained Message für die Position verwenden?

Ein neuer Subscriber, etwa eine gerade geöffnete Karte, erhält sofort die letzte bekannte Position, statt darauf zu warten, dass sich das Gerät bewegt.

Kann sich die Webkarte direkt mit dem Broker verbinden?

Ja. Browser nutzen MQTT über Secure WebSocket, hier wss://broker.connectmq.com:8084/mqtt. Bei privaten Daten sollten Sie keine Zugangsdaten in einer öffentlichen Seite preisgeben.

Funktioniert der NEO-6M in Innenräumen?

Meist nicht zuverlässig. GPS-Signale sind schwach und brauchen Sicht zum Himmel. Testen Sie im Freien oder an einem Fenster.

Verwandte Artikel

ESP32 NTP MQTT Zeitstempel: genaue Uhrzeit in jeder Nachricht

Synchronisieren Sie die ESP32-Uhr per NTP, formatieren Sie Zeitstempel in ISO 8601 und Epoch-Millisekunden und hängen Sie sie an MQTT-Payloads, damit Messwerte ihren echten Erfassungszeitpunkt behalten, auch wenn sie verspätet veröffentlicht werden.

8 Min. Lesezeit

Alle Artikel →

Ihr privater MQTT-Broker ist nur wenige Klicks entfernt

Konto anlegen, Broker-Zugangsdaten erhalten und das erste Gerät verbinden — ohne Server, die installiert, gepatcht oder skaliert werden müssen.