Der MQTT-Server von The Things Network liefert jeder Anwendung einen Live-Feed ihrer LoRaWAN-Uplinks und macht es damit leicht, Sensordaten mit großer Reichweite in Ihre eigenen Systeme zu holen. In diesem Tutorial schreiben Sie einen Payload Formatter, verbinden sich mit einem API-Schlüssel mit dem TTN-MQTT-Server und betreiben eine kleine Python-Bridge, die dekodierte Messwerte als sauberes JSON in den Topic-Baum Ihres eigenen Brokers veröffentlicht. Dort können Dashboards, Node-RED und Automatisierungen sie zusammen mit Ihren anderen Geräten nutzen.
Warum TTN an Ihren eigenen Broker anbinden
The Things Network (TTN) übernimmt die Funkseite von LoRaWAN: Gateways, Geräteaktivierung, Deduplizierung und Entschlüsselung. Sein MQTT-Server veröffentlicht jeden Uplink als großes JSON-Dokument mit Metadaten von jedem Gateway, das das Paket empfangen hat. Für die Fehlersuche ist das hervorragend, als langfristiges Datenmodell aber unhandlich:
- die Topics gehören TTN (
v3/<app>@ttn/devices/<device>/up), nicht Ihrer Standorthierarchie; - die Payloads sind groß und verschachtelt, während die meisten Verbraucher drei oder vier Werte wollen;
- Ihre WLAN-Geräte, SPSen und Steuerungen leben bereits auf einem anderen Broker.
Eine Bridge löst alle drei Probleme. Sie abonniert TTN, extrahiert das Wesentliche und veröffentlicht kompakte Nachrichten in denselben Topic-Baum wie alles andere. Verbraucher brauchen nie TTN-Zugangsdaten, und wenn Sie später auf einen privaten Netzwerkserver wechseln, ändert sich nur die Bridge. Der Leitfaden zu Smart Farming IoT erklärt, wo das in ein betriebsweites Design passt.
Schritt 1: Die Payload in TTN dekodieren
LoRaWAN-Geräte senden kompakte Bytes. Fügen Sie in der TTN Console unter Applications > your app > Payload formatters > Uplink einen Payload Formatter vom Typ Custom Javascript formatter hinzu. Dieses Beispiel dekodiert einen Bodenknoten, der Feuchte (uint16, Zehntelprozent), Bodentemperatur (int16, Zehntelgrad) und Batterie (uint16, Millivolt) sendet:
function decodeUplink(input) {
var b = input.bytes;
if (input.fPort !== 1 || b.length < 6) {
return { errors: ["unexpected port or length"] };
}
var moisture = ((b[0] << 8) | b[1]) / 10;
var tRaw = (b[2] << 8) | b[3];
if (tRaw & 0x8000) tRaw -= 0x10000;
var battery = ((b[4] << 8) | b[5]) / 1000;
return {
data: {
moisture_pct: moisture,
soil_temp_c: tRaw / 10,
battery_v: battery
},
warnings: [],
errors: []
};
}
Nutzen Sie das Panel Test mit einer Hex-Payload wie 0136 008C 0E74, um die Ausgabe zu prüfen, bevor ein Gerät ausgebracht wird. Das dekodierte Objekt erscheint in jedem Uplink als uplink_message.decoded_payload.
Schritt 2: MQTT-Zugangsdaten von TTN erhalten
Öffnen Sie in der Console Integrations > MQTT. Dort sehen Sie:
| Einstellung | Wert |
|---|---|
| Server | Ihr Cluster, zum Beispiel eu1.cloud.thethings.network |
| Ports | 1883 (unverschlüsselt) und 8883 (TLS) |
| Benutzername | <application-id>@ttn |
| Passwort | Ein API-Schlüssel; klicken Sie auf Generate new API key |
Kopieren Sie den API-Schlüssel sofort; TTN zeigt ihn nur einmal an. Verwenden Sie zu TTN immer Port 8883, da der Schlüssel Zugriff auf Ihre Anwendungsdaten gewährt. Der Abschnitt zum MQTT-Server in der offiziellen Dokumentation von The Things Stack listet alle Topics auf, einschließlich Join-, Downlink- und Service-Ereignissen.
Das Uplink-Topic für eine Anwendung lautet:
v3/<application-id>@ttn/devices/<device-id>/up
Sie können den Feed prüfen, bevor Sie Code schreiben:
mosquitto_sub -h eu1.cloud.thethings.network -p 8883 --capath /etc/ssl/certs \
-u 'my-farm-app@ttn' -P 'NNSXS.YOUR_TTN_API_KEY' \
-t 'v3/my-farm-app@ttn/devices/+/up' -v
Schritt 3: Die The-Things-Network-MQTT-Bridge in Python
Die Bridge nutzt zwei paho-mqtt-2.x-Clients: einen, der TTN abonniert, und einen, der an Ihren Broker veröffentlicht. Betreiben Sie sie zunächst mit dem öffentlichen MQTT-Broker und ersetzen Sie t6n1b4 durch Ihre eigene Zufallszeichenfolge, denn der öffentliche MQTT-Broker ist für jeden lesbar und nur für Tests und Prototypen gedacht, nicht für Produktivdaten.
import json
import os
import ssl
import paho.mqtt.client as mqtt
TTN_HOST = "eu1.cloud.thethings.network"
TTN_APP = os.environ.get("TTN_APP", "my-farm-app")
TTN_KEY = os.environ["TTN_API_KEY"]
OUT_HOST = os.environ.get("OUT_HOST", "broker.connectmq.com")
OUT_PORT = int(os.environ.get("OUT_PORT", "1883"))
OUT_USER = os.environ.get("OUT_USER") # im Produktivbetrieb setzen
OUT_PASS = os.environ.get("OUT_PASS")
OUT_CLIENT_ID = os.environ.get("OUT_CLIENT_ID", "ttn-bridge-out-t6n1b4")
OUT_PREFIX = os.environ.get("OUT_PREFIX", "connectmq/demo/t6n1b4/farm")
# --- ausgehender Broker -------------------------------------------------------
out = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=OUT_CLIENT_ID)
if OUT_USER:
out.username_pw_set(OUT_USER, OUT_PASS)
if OUT_PORT == 8883:
out.tls_set(cert_reqs=ssl.CERT_REQUIRED)
out.will_set(f"{OUT_PREFIX}/bridge/status", "offline", qos=1, retain=True)
def out_on_connect(client, userdata, flags, reason_code, properties):
print("out broker:", reason_code)
client.publish(f"{OUT_PREFIX}/bridge/status", "online", qos=1, retain=True)
out.on_connect = out_on_connect
out.connect(OUT_HOST, OUT_PORT, keepalive=60)
out.loop_start()
# --- TTN ---------------------------------------------------------------------
def ttn_on_connect(client, userdata, flags, reason_code, properties):
print("ttn:", reason_code)
client.subscribe(f"v3/{TTN_APP}@ttn/devices/+/up", qos=1)
def ttn_on_message(client, userdata, msg):
try:
up = json.loads(msg.payload)
device = up["end_device_ids"]["device_id"]
um = up["uplink_message"]
decoded = um.get("decoded_payload")
except (ValueError, KeyError) as exc:
print("skipping malformed uplink:", exc)
return
if not decoded:
print(f"{device}: no decoded payload (check the formatter)")
return
rx = um.get("rx_metadata", [])
best = max(rx, key=lambda g: g.get("rssi", -999)) if rx else {}
record = {
"ts": up.get("received_at"),
**decoded,
"rssi": best.get("rssi"),
"snr": best.get("snr"),
"gateways": len(rx),
"f_cnt": um.get("f_cnt"),
}
topic = f"{OUT_PREFIX}/{device}/telemetry"
out.publish(topic, json.dumps(record), qos=1, retain=True)
print(topic, record)
ttn = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"ttn-bridge-in-{TTN_APP}")
ttn.username_pw_set(f"{TTN_APP}@ttn", TTN_KEY)
ttn.tls_set(cert_reqs=ssl.CERT_REQUIRED)
ttn.on_connect = ttn_on_connect
ttn.on_message = ttn_on_message
ttn.connect(TTN_HOST, 8883, keepalive=60)
ttn.loop_forever()
Starten Sie sie:
pip install paho-mqtt
export TTN_API_KEY='NNSXS.YOUR_TTN_API_KEY'
python ttn_bridge.py
Und beobachten Sie die saubere Ausgabe in einem zweiten Terminal:
mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-t6n1b4 \
-t 'connectmq/demo/t6n1b4/farm/#' -v
Jeder Uplink wird zu einer retained Nachricht wie {"ts":"2026-10-16T06:15:02Z","moisture_pct":31.0,"soil_temp_c":14.0,"battery_v":3.7,"rssi":-108,"snr":4.5,"gateways":2,"f_cnt":412}. Da sie retained ist, sieht auch ein später gestartetes Dashboard sofort den letzten Messwert jedes Sensors; das ist wichtig bei Geräten, die nur alle 15 Minuten melden.
Hinweise zum Design
Warum RSSI, SNR und Gateway-Anzahl mitsenden? Sie sind die früheste Warnung vor einer defekten Antenne, einem versetzten Gateway oder einem Sensor, der im Schlamm versinkt. Sie zu speichern kostet fast nichts.
Warum retained Messages? LoRaWAN-Geräte sind die meiste Zeit still. Ohne Retain sieht jeder Verbraucher, der sich zwischen zwei Uplinks verbindet, bis zum nächsten nichts.
Warum QoS 1 auf beiden Seiten? Die Bridge könnte neu starten, während TTN zustellt; QoS 1 mit einem sich neu verbindenden Client verringert Lücken. Die Abwägungen erläutert MQTT-QoS-Stufen erklärt.
Topic-Tiefe. connectmq/demo/t6n1b4/farm/<device>/telemetry hat sechs Ebenen und liegt damit bequem innerhalb des Limits von acht Ebenen des öffentlichen MQTT-Brokers. Im Produktivbetrieb verwenden Sie Ihre eigene Hierarchie, etwa farm1/north/<device>/telemetry. In Industrieumgebungen ordnen Sie LoRaWAN-Sensoren auf dieselbe Weise in einen Unified Namespace (UNS) nach ISA-95 ein, zum Beispiel acme/werk-muenchen/aussenlager/tank-2/telemetry, sodass Funk- und SPS-Daten im selben Topic-Baum nebeneinanderliegen.
Nachrichtengröße. Ein roher TTN-Uplink mit Metadaten mehrerer Gateways kann einige Kilobyte groß sein. Der neu veröffentlichte Datensatz umfasst ein paar hundert Byte, weit unter dem Limit von 64 KB des öffentlichen MQTT-Brokers und dem Limit von 256 KB des kostenlosen ConnectMQ-Plans, und ist für Dashboards und mobile Clients mit Akkubetrieb viel günstiger zu verarbeiten.
Umgang mit Lücken und Duplikaten
LoRaWAN ist konstruktionsbedingt eine verlustbehaftete Funkverbindung: Geräte senden ohne Bestätigung, und ein Paket, das kein Gateway empfängt, ist einfach verloren. Der in jedem Datensatz enthaltene Frame Counter (f_cnt) ermöglicht es Verbrauchern, Lücken zu erkennen. Springt der Zähler von 410 auf 413, gingen zwei Uplinks auf der Funkstrecke verloren, nicht in MQTT. Diese Unterscheidung spart Zeit, wenn jemand fehlende Daten meldet.
Die Bridge kann denselben Uplink auch zweimal sehen, wenn sie sich mit TTN neu verbindet, während eine QoS-1-Nachricht unterwegs ist. Verbraucher, die Daten speichern, sollten die Kombination aus Geräte-ID und Frame Counter als eindeutigen Schlüssel behandeln und Wiederholungen ignorieren. Beachten Sie, dass der Zähler zurückgesetzt wird, wenn ein Gerät dem Netz erneut beitritt; nutzen Sie ihn daher zur Lückenerkennung über kurze Zeiträume und nicht als dauerhafte Kennung.
Downlinks senden
Um einen Befehl an ein Gerät zu senden (zum Beispiel ein neues Meldeintervall), kann die Bridge ein Befehls-Topic auf Ihrem Broker abonnieren und es an das Downlink-Topic von TTN weiterleiten:
import base64
def forward_downlink(device_id, port, raw_bytes):
body = {"downlinks": [{
"f_port": port,
"frm_payload": base64.b64encode(raw_bytes).decode(),
"priority": "NORMAL",
}]}
ttn.publish(f"v3/{TTN_APP}@ttn/devices/{device_id}/down/push",
json.dumps(body), qos=1)
# Beispiel: Intervall auf 30 Minuten setzen (ein Byte) auf Port 2
# forward_downlink("soil-node-07", 2, bytes([30]))
Geräte der Klasse A empfangen Downlinks erst nach ihrem nächsten Uplink, sodass Befehle mit einer Verzögerung in Höhe des Meldeintervalls ankommen. Halten Sie Downlinks selten; Sendezeit- und Duty-Cycle-Regeln machen sie bei LoRaWAN teuer.
Zuverlässiger Betrieb
- Betreiben Sie die Bridge als systemd-Dienst oder Container mit automatischem Neustart.
- Bewahren Sie den TTN-API-Schlüssel in einer Umgebungsvariable oder einem Secret Store auf, niemals im Skript.
- Geben Sie dem API-Schlüssel nur die Rechte, die er braucht (Lesen des Anwendungsverkehrs und, falls genutzt, Schreiben von Downlinks).
- Überwachen Sie
bridge/status: Der Last Will setzt es aufoffline, wenn der Prozess abstürzt. - Denken Sie an die Fair-Use-Richtlinie von TTN für das Community-Netz; es ist für Sensoren mit niedriger Sendefrequenz gedacht, nicht für gesprächige Geräte.
Produktivbetrieb auf einem privaten Broker
Setzen Sie OUT_HOST=broker.connectmq.com, OUT_PORT=8883, OUT_USER, OUT_PASS und OUT_CLIENT_ID auf die Zugangsdaten Ihres privaten Brokers und ändern Sie OUT_PREFIX auf Ihre eigene Hierarchie. Der Rest des Skripts bleibt gleich. Ein verwalteter MQTT-Broker gibt Ihren LoRaWAN-Daten, WLAN-Sensoren und Steuerungen ein gemeinsames, verschlüsseltes Zuhause, das Sie nicht selbst warten müssen. Wenn Sie statt TTN einen eigenen Netzwerkserver betreiben, zeigt das Tutorial ChirpStack MQTT die entsprechende Bridge, und der Bau eines Bodenfeuchtesensors mit MQTT ist eine WLAN-Alternative für Felder in Gebäudenähe. Weitere Muster finden Sie im Leitfaden zur IoT-Automatisierung, und die Grundlagen dazu, was ein MQTT-Broker macht, helfen, wenn MQTT neu für Sie ist.
Bringen Sie Ihre LoRaWAN-Daten zu ConnectMQ
ConnectMQ gibt Ihren über die Bridge übertragenen TTN-Daten einen privaten MQTT-Broker mit TLS, einen Live-Topic-Explorer, ein Dashboard und Schwellwert-Alarmregeln für Werte wie niedrigen Batteriestand oder trockenen Boden. Erstellen Sie unter app.connectmq.com einen kostenlosen Serverless-Broker, ohne Kreditkarte, und richten Sie die Ausgabe der Bridge darauf aus.
Häufig gestellte Fragen
Wie lauten Benutzername und Passwort für TTN MQTT?
Der Benutzername ist Ihre Application ID gefolgt von @ttn (für die Community Edition von The Things Network), und das Passwort ist ein API-Schlüssel, den Sie unter Integrations > MQTT erzeugen.
Warum fehlt decoded_payload in meinen Uplinks?
Es ist kein Uplink Payload Formatter konfiguriert, oder er hat einen Fehler ausgelöst. Testen Sie ihn im Formatter-Panel der Console mit einer echten Hex-Payload.
Kann ich TTN ohne Code direkt mit einem anderen Broker verbinden?
TTN bietet Webhooks und andere Integrationen, aber um in Ihren eigenen MQTT-Broker mit Ihrer eigenen Topic-Struktur zu veröffentlichen, ist eine kleine Bridge wie diese die einfachste und transparenteste Lösung.
Muss die Bridge in der Nähe des Gateways laufen?
Nein. Beide Verbindungen laufen über das Internet, sodass sie auf jedem Server, einem Raspberry Pi oder in einem Container laufen kann.



