Ein Observer ist ein Node, der mithört und jedes gehörte Paket an den Analyzer meldet. Er leitet nichts weiter – das machen nur Repeater – und an den Analyzer gehen ausschließlich echte Empfangsereignisse.
Die Rolle beschreibt den Stack, nicht das Gerät: Der Node läuft mit Companion-Firmware und könnte senden. Zum Observer macht ihn die Bridge daneben. Wer denselben Node auch als Sende-Endpunkt nutzen will, findet den Weg unter meshinfra – der Observer-Aufbau bleibt dabei unverändert.
Aus den Meldungen vieler Observer entsteht die Netzkarte: Wer hat welches Paket mit welchem SNR gehört, über wie viele Hops, aus welcher Richtung. Ein einzelner Observer sieht einen Ausschnitt, zehn sehen das Netz. Deshalb lohnt es sich, einen beizusteuern – besonders aus Gegenden, aus denen bisher niemand meldet.
Beide sind erprobt. Der Unterschied liegt nur darin, wie die Bridge läuft und wie der Node angebunden ist.
| Weg 1: Docker | Weg 2: Systemdienst | |
|---|---|---|
| Nimm den, wenn | du schon einen Server mit Docker betreibst | du ein Gerät übrig hast, auf dem sonst nichts läuft |
| Node hängt | im WLAN, per TCP erreichbar | per USB am selben Rechner |
| Aufwand | .env ausfüllen, zwei Container starten |
ein Installer, Assistent durchklicken |
| Lokaler MQTT-Spiegel | ja | nein |
| Erprobt mit | Heltec V3, Linux-Host | Heltec V4, Raspberry Pi |
| Anleitung | Teil A → Teil B → Teil C | Teil A (ohne A2/A3) → Variante ohne Docker |
Teil A ist für beide gleich – der Node muss so oder so geflasht und eingestellt sein.
Wenn du dich nicht entscheiden kannst: Docker, wenn ohnehin ein Server läuft. Sonst der Systemdienst, das ist der kürzere Weg.
Von oben nach unten abarbeiten, dann läuft es.
| Node | Heltec WiFi LoRa 32 V3, 868-MHz-Variante |
| Host für die Bridge | Irgendetwas mit Docker, ~150 MB RAM. Ein vorhandener Server genügt |
| WLAN | 2,4 GHz am Standort des Nodes |
| Zugangsdaten | Benutzer und Passwort für den Broker – beim Betreiber anfragen |
Zu den Zugangsdaten: Es gibt pro Node einen eigenen Zugang, keinen gemeinsamen Account. Nur so lässt sich später zuordnen, wer was meldet, und im Bedarfsfall einzeln sperren.
Ein eigenes Gerät nur für den Observer brauchst du nicht. Der Node hängt im WLAN, die Bridge läuft auf etwas, das ohnehin schon durchläuft.
Getestet ist genau eine Kombination: Heltec V3 als Node, Linux-Host mit Docker für die Bridge. Diese Anleitung beschreibt deshalb nur die.
Beides ist der Sache nach nicht zwingend. Als Node sollte jedes Gerät taugen, auf dem eine Companion-Firmware mit WLAN und TCP-Server läuft. Und die Bridge ist ein Docker-Image – sie sollte überall laufen, wo Docker läuft: NAS, Mac, Windows, Mini-PC, VPS. Ausprobiert haben wir das nicht.
Bereits gemeldet: Heltec V4 per USB an einem Raspberry Pi, ohne Docker – siehe unten.
Wenn du es mit anderer Hardware oder auf einem anderen Host zum Laufen bringst: bitte hier ergänzen. Dann steht beim nächsten nicht mehr „sollte gehen", sondern was tatsächlich funktioniert.
Antenne vor dem ersten Einschalten anschrauben. Senden ohne Antenne zerstört den Sendeverstärker.
Wir nutzen meshcomod mit der Build-Umgebung Heltec_v3_companion_radio_usb_tcp.
Die Rolle ist Companion, und das ist eine bewusste Wahl: Nur der Companion hat den TCP-Client-Port, über den die Bridge strukturierte Paketereignisse bekommt. Ein Repeater hat den nicht.
0x0. Sie heißt nach dem Muster:Heltec_v3_companion_radio_usb_tcp-v<version>-<commit>-merged.bin.bin hochladen
flasher.meshcomod.comist derzeit nicht erreichbar (Stand 8. August 2026 – die Domain löst auf, aber der Server antwortet weder auf Port 443 noch 80). Deshalb der Weg über den offiziellen MeshCore-Flasher, der Custom Firmware unterstützt:.binfür ESP32-Geräte,.zipfür nRF52.
Board wird nicht erkannt: BOOT gedrückt halten, RESET kurz drücken, BOOT loslassen.
Display bleibt nach dem Flashen schwarz: Es war die app-only-Datei statt der merged. Nochmal mit der richtigen Datei flashen.
Es gibt keine serielle Textkonsole. Companion-Builds legen auf USB das binäre Companion-Protokoll, kein Textprotokoll. PuTTY,
config.meshcore.iound die Konsole im Flasher scheitern deshalb alle – das ist erwartetes Verhalten und kein Defekt. Nicht dort suchen.
Konfiguriert wird über den Meshcomod-Chat-Kontakt in der MeshCore-App. Der ist ab Werk favorisiert und steht unten in der Kontaktliste. Befehle dort laufen lokal am Gerät und gehen nicht über LoRa.
Node per Bluetooth koppeln, Meshcomod-Kontakt öffnen, dann der Reihe nach:
help
wifi scan
wifi use <n>
wifi set pwd DeinPasswort
wifi apply
wifi status
<n> ist die Nummer aus der Liste, die wifi scan ausgibt.
Erwartet bei wifi status: connected=1 und eine IP-Adresse.
Die Falle, die hier wirklich zuschlägt: das WLAN-Passwort ohne Anführungszeichen setzen.
richtig: wifi set pwd MeinPasswort123
falsch: wifi set pwd "MeinPasswort123"
Mit Quotes werden diese wörtlich Teil des Passworts, und wifi status meldet dauerhaft connected=0. Anführungszeichen nur, wenn das Passwort selbst Leerzeichen enthält.
Zwei weitere Kleinigkeiten:
help-Aufruf in einem frischen Chat antwortet manchmal nicht – einfach nochmal sendenIP am Netzwerk-Tab des Displays ablesen oder aus wifi status.
Dann im Router eine DHCP-Reservierung auf diese IP setzen. Das ist nicht optional – ohne sie zeigt die Bridge-Konfiguration nach dem nächsten Lease-Wechsel ins Leere.
Der TCP-Port ist 5000.
Name nach AT-Schema:
AT-<Bezirkskennzeichen>-<Name>
Bezirkskennzeichen sind die Kfz-Kürzel: K, KL, VI, VL, FE, HE, SV, SP, VK, WO.
Position in Dezimalgrad eintragen, sonst erscheint der Observer nicht sinnvoll auf der Karte.
Radio-Preset – Preset EU/UK Narrow:
| Parameter | Wert |
|---|---|
| Frequenz | 869.618 MHz |
| Bandbreite | 62.5 kHz |
| Spreading Factor | SF8 |
| Coding Rate | 8 |
In der CLI-Kurzform:
set radio 869.618,62.5,8,8
Das Preset muss exakt stimmen. Weicht ein einziger Wert ab, ist der Node taub: Die Bridge verbindet sich sauber, meldet keinen Fehler – und liefert nie ein Paket. Das ist die mit Abstand häufigste Ursache für einen Observer, der „läuft", aber nichts sieht.
Quelle: meshcore.at. Siehe auch Funkeinstellungen.
Duty Cycle auf den europäischen Wert – ab Werk steht er auf 50 %:
set dutycycle 10
Owner Info setzen, damit man den Observer zuordnen kann:
set owner.info 📌 Standort|📻 Gerät|📡 Antenne|🌐 Regions aktiv|✉ deine Erreichbarkeit
Regionen freischalten – in der App unter Manage Regions, alle auf „Flood Allowed":
* · at · at-ktn · at-ktn-<bezirk> · at-sued
Beides ausführlich erklärt auf Repeater Setup Guide.
Läuft auf dem Docker-Host. Zwei Container: die Bridge und ein lokaler Mosquitto als Spiegel. Die Befehle unten sind für Linux geschrieben.
git clone https://github.com/achildrenmile/carinthiameshobserver.git
cd carinthiameshobserver/stack
cp .env.example .env
chmod 600 .env
.env öffnen. Diese Werte sind für carinthiamesh bei allen gleich:
MESHCORE_TCP_HOST=<IP-deines-Nodes>
MESHCORE_TCP_PORT=5000
CARINTHIA_ENABLED=true
CARINTHIA_HOST=mqtt.carinthiamesh.com
CARINTHIA_PORT=443
CARINTHIA_TRANSPORT=websockets
CARINTHIA_TLS=true
CARINTHIA_TLS_VERIFY=true
CARINTHIA_IATA=KLU
CARINTHIA_USER=<vom Betreiber>
CARINTHIA_PASS=<vom Betreiber>
Daraus ergibt sich das Topic meshcore/KLU/<PUBLIC_KEY>/packets. Den Public Key liest die Bridge selbst aus dem Node aus – nicht von Hand setzen.
Quoting-Regel, an der die meisten scheitern: Werte ohne Anführungszeichen und ohne Escaping schreiben. Docker Compose nimmt den Rest der Zeile wörtlich.
richtig: CARINTHIA_PASS=BeispielPasswort]mit"Sonderzeichen? falsch: CARINTHIA_PASS="BeispielPasswort]mit\"Sonderzeichen?"
],",?,!,&und Leerzeichen sind unproblematisch. Zwei Zeichen nicht:#leitet einen Kommentar ein, der Rest der Zeile verschwindet.$löst Compose-Interpolation aus. Enthält dein Passwort eines davon, lass es dir vom Betreiber ändern.
for p in 1883 9001; do
ss -ltn "sport = :$p" | grep -q LISTEN && echo "$p BELEGT" || echo "$p frei"
done
Belegt? In .env ausweichen:
MOSQUITTO_HOST_PORT=1884
MOSQUITTO_WS_HOST_PORT=9002
Das betrifft nur die Ports nach außen. Container-intern bleibt alles auf 1883/9001.
Nur alphanumerisch, siehe Quoting-Regel:
tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32; echo
Ergebnis in .env als LOCAL_MQTT_PASS eintragen.
cd config/mosquitto
docker run --rm --user $(id -u):$(id -g) -v "$PWD":/work eclipse-mosquitto:2 \
mosquitto_passwd -c -b /work/passwd meshbridge '<DEIN_PASSWORT>'
docker run --rm -v "$PWD":/work alpine:3 \
sh -c 'chown 1883:1883 /work/passwd && chmod 600 /work/passwd'
cd ../..
Der zweite Befehl setzt Eigentümer 1883 – die UID, unter der Mosquitto im Container läuft. Ohne das kann er die Datei nicht lesen und startet nicht.
Zuerst allein, damit ein Fehler eindeutig zuzuordnen ist:
docker compose up -d mosquitto
docker compose ps
Erwartet: healthy.
docker compose up -d bridge
docker compose logs -f bridge
Erwartet:
Connecting via TCP to 192.168.1.50:5000
Connected to: {'public_key': '…', 'name': 'AT-KL-Dein-Node', 'radio_freq': 869.618, …}
Connected to local-mosquitto at mosquitto:1883 (transport=tcp, tls=False)
Connected to carinthiamesh at mqtt.carinthiamesh.com:443 (transport=websockets, tls=True)
Connected to 2 MQTT broker(s)
Sieh dir die Zeile Connected to: genau an. Sie zeigt die tatsächlichen Radio-Einstellungen des Nodes. Steht dort nicht 869.618, stimmt das Preset nicht – zurück zu A4, bevor du weitermachst.
Der am häufigsten übersprungene Schritt:
docker compose exec bridge printenv PACKETCAPTURE_MQTT2_PASSWORD | od -c
od -c zeigt jedes Byte einzeln. Zeichenweise mit dem Sollwert vergleichen. Tauchen am Anfang oder Ende " auf, die nicht hingehören, ist die Quoting-Falle zugeschnappt.
docker compose up -d
docker compose ps
Zwei Services, beide healthy.
Zweiter erprobter Weg, beigetragen von Dominik Leitner (07.08.2026). Er läuft auf einem Raspberry Pi, auf dem sonst nichts läuft, mit einem Heltec V4 per USB. Wenn du ein Gerät übrig hast, ist das der kürzere Weg – der Installer des Projekts erledigt fast alles.
Statt Teil B. Teil A (Node vorbereiten) gilt weiter – mit einem Unterschied: Hängt der Node per USB am Rechner, brauchst du kein WLAN am Node. Die Schritte A2 und A3 entfallen dann, A1 und A4 bleiben.
| Aufbau | Verbindungsart |
|---|---|
| Node im WLAN, Bridge auf einem anderen Host | tcp, Port 5000 |
| Node per USB am selben Rechner | serial |
Das widerspricht nicht dem Hinweis aus A2, dass es keine serielle Textkonsole gibt. Über USB läuft das binäre Companion-Protokoll – genau das, was die Bridge spricht. Ein Terminalprogramm zeigt deshalb Zeichensalat, die Bridge nicht.
Den Installer des Projekts herunterladen und ausführen. Diese Form empfiehlt das Skript selbst – curl | sudo bash lehnt es ab, weil dann die Rückfragen des Assistenten keine Eingaben mehr bekommen:
tmp=$(mktemp) \
&& curl -fsSL https://raw.githubusercontent.com/agessaman/meshcore-packet-capture/main/install.sh -o "$tmp" \
&& sudo bash "$tmp"; rm -f "$tmp"
Der Installer legt ein Python-venv an, installiert das Paket und richtet einen systemd-Dienst ein.
Im Assistenten:
mqtt.carinthiamesh.com, Port 443, WebSockets, TLS an, IATA KLU, dazu deine Zugangsdaten vom BetreiberDanach einmal neu starten:
sudo reboot
systemctl status meshcore-packet-capture
journalctl -u meshcore-packet-capture -f
Erwartet werden dieselben Zeilen wie in Teil C – Connected to: mit 'radio_freq': 869.618, danach Connected to MQTT broker.
Die Prüfschritte aus Teil C gelten sinngemäß, nur mit anderen Befehlen:
| Teil C sagt | Ohne Docker |
|---|---|
docker compose logs bridge |
journalctl -u meshcore-packet-capture -f |
docker compose ps |
systemctl status meshcore-packet-capture |
docker compose up -d |
sudo systemctl restart meshcore-packet-capture |
C1 (Node erreichbar) entfällt bei USB-Anschluss. C3 (lokale Pakete) ebenfalls – bei dieser Variante gibt es keinen lokalen Mosquitto. Wer den Spiegel und den Anschlusspunkt für Grafana oder Node-RED will, nimmt den Docker-Weg aus Teil B.
nvm bzw. Node.js wird nicht gebraucht. Der Installer ist reines Python – venv, pip, systemd. Im Skript kommt weder node noch npm vor. Der Schritt schadet nicht, ist aber überflüssig--tagDer Reihe nach. Bei einem Fehlschlag dort stehen bleiben und die Ursache beheben, nicht zum nächsten Schritt weitergehen – alles danach wäre Folgefehler.
timeout 2 bash -c 'echo > /dev/tcp/<IP-deines-Nodes>/5000' && echo "erreichbar" || echo "NICHT erreichbar"
IP unbekannt? Subnetz absuchen:
for i in $(seq 1 254); do
(timeout 1 bash -c "echo > /dev/tcp/192.168.1.$i/5000" 2>/dev/null \
&& echo "Port 5000 offen: 192.168.1.$i") &
done; wait
Melden sich mehrere Hosts: Companions machen zusätzlich Port 8765 auf. Wer beides offen hat, ist der Node. Gebraucht wird 8765 nicht, es ist nur ein gutes Erkennungsmerkmal.
docker compose logs bridge | grep "Connected to:"
Erwartet: eine Zeile mit Public Key, Node-Namen und 'radio_freq': 869.618.
docker compose exec mosquitto sh -c \
'mosquitto_sub -h 127.0.0.1 -t "#" -v -u "$MOSQ_USER" -P "$MOSQ_PASS"'
Erwartet: laufend Zeilen wie
meshcore/KLU/A1B2…/packets {"origin":"AT-KL-Dein-Node","type":"PACKET","SNR":"12.5","RSSI":"-48",…}
Kommt nichts? Erst in die Bridge-Logs sehen. Steht dort Captured packet, liegt es am Broker. Steht dort nichts, hört der Node nichts – dann ist das Radio-Preset dran, nicht die Software.
Bei ruhigem Netz kann es dauern. Geduld ist hier billiger als Fehlersuche.
docker compose logs bridge | grep -i carinthia
Erwartet – beide Zeilen:
Connected to carinthiamesh at mqtt.carinthiamesh.com:443 (transport=websockets, tls=True)
Connected to MQTT broker: carinthiamesh
Die erste heißt nur „Socket steht". Die zweite kommt aus dem Connect-Callback und bedeutet: Der Broker hat CONNACK ohne Fehler geschickt, die Authentifizierung ist akzeptiert.
Connection Refused: not authorised → Passwort. Zurück zu B8.
docker compose logs bridge | grep "Captured packet"
📦 Captured packet #1: F type 4, 126 bytes, SNR: 13.25, RSSI: -48, hash: 2837… (MQTT: 2/2)
(MQTT: 2/2) heißt: an beide Broker publiziert. Steht dort 1/2, hat einer abgelehnt.
Auf map.carinthiamesh.com nach deinem Node-Namen suchen.
last_seensteigt nur, wenn Funkverkehr hereinkommt. Bei ruhigem Netz bleibt der Wert stehen, ohne dass etwas kaputt ist. Immer gegen die Bridge-Logs gegenprüfen.
docker compose down
docker compose up -d
sleep 30
docker compose ps
docker compose logs bridge | grep "Connected to MQTT broker"
Beide Broker müssen wieder auftauchen. Erst dann ist der Observer fertig.
| Symptom | Ursache | Behebung |
|---|---|---|
wifi status meldet dauerhaft connected=0 |
Passwort mit Anführungszeichen gesetzt | wifi set pwd ohne Quotes wiederholen, dann wifi apply |
WLAN wird bei wifi scan nicht gefunden |
5-GHz-only-SSID | 2,4 GHz aktivieren |
| Display bleibt nach dem Flashen schwarz | app-only-Datei statt merged geflasht | Mit der merged-Datei neu flashen |
| Board wird vom Flasher nicht erkannt | Bootloader-Modus fehlt | BOOT halten, RESET kurz, BOOT loslassen |
flasher.meshcomod.com lädt nicht |
Server offline | Über flasher.meshcore.io → Custom Firmware flashen, Datei von den GitHub-Releases |
| Serielle Konsole antwortet nicht | Companion-Builds haben keine Textkonsole | Kein Defekt. Über den Meshcomod-Chat-Kontakt konfigurieren |
Connection refused zum Node |
Node nicht im WLAN, IP gewandert | DHCP-Reservierung prüfen, dann C1 |
| Bridge verbindet, aber nie ein Paket | Radio-Preset am Node falsch | Werte in der Connected to:-Zeile gegen A4 halten. Am Node korrigieren |
| Pakete nur sehr selten | HF-Lage | Antenne, Standort, Höhe. Keine Softwarefrage |
Failed to connect to any MQTT broker |
kein Broker erreichbar | Reihenfolge: erst mosquitto healthy, dann bridge |
| TLS-Handshake scheitert | Systemzeit falsch | Zuerst timedatectl – eine falsche Uhr erzeugt Zertifikatsfehler, die keine sind. Danach openssl s_client -connect mqtt.carinthiamesh.com:443 |
Connection Refused: not authorised (Upstream) |
Passwort abgeschnitten oder mit Quotes | docker compose exec bridge printenv PACKETCAPTURE_MQTT2_PASSWORD \| od -c |
Connection Refused: not authorised (lokal) |
.env passt nicht zu passwd |
Benutzer neu setzen, dann docker compose up -d – nicht restart, das liest die .env nicht neu |
mosquitto startet nicht, Error opening password file |
passwd gehört nicht UID 1883 |
chown-Befehl aus B5 wiederholen |
passwd ist plötzlich ein Verzeichnis |
Bind-Mount zeigte auf eine nicht existierende Datei | Verzeichnis löschen, Datei nach B5 neu erzeugen |
| Nur MQTT1 verbindet | Lücke in der Broker-Nummerierung | Lückenlos ab 1 nummerieren, jeder Slot braucht _ENABLED |
| Node nicht auf der Karte | falscher IATA-Code | Topic beim Mitlesen gegen meshcore/KLU/<PUBKEY>/packets halten |
Failed to enable TCP keepalive |
Socket lässt die Option nicht setzen | Kosmetisch. Auto-Reconnect greift trotzdem |
Wenn nichts offensichtlich ist: von unten nach oben durch die Kette aus Teil C. Der erste Fehler zählt, alles danach ist Folge. Und Logs lesen statt raten: docker compose logs bridge | head -50 zeigt die komplette Startsequenz.
docker compose ps # Status
docker compose logs -f bridge # mitlesen
docker compose pull && docker compose up -d # aktualisieren
Der lokale Mosquitto ist dein Spiegel: Dieselben Pakete liegen unabhängig vom Upstream bei dir, als Anschlusspunkt für Grafana, Node-RED oder eigene Skripte. Er läuft ohne TLS – nicht ins Internet exponieren.
Keine synthetischen Pakete oder Adverts an den Upstream schicken. Der Analyzer-Betreiber behält sich vor, Zugänge zu sperren, wenn Daten oder APIs zum Überbrücken getrennter Funknetze benutzt werden.
Das betrifft den Weg zum Analyzer. Echte Funknachrichten über denselben Node zu senden ist etwas anderes und ausdrücklich in Ordnung – sie gehen über die Luftschnittstelle und werden ganz normal von allen gehört, auch vom eigenen Observer.
Ein Companion-Node nimmt zwei gleichzeitige TCP-Clients auf Port 5000. Neben der Observer-Bridge kann ein zweiter Stack laufen, der aus der eigenen IT heraus Nachrichten ins Netz schickt – Monitoring, Alarme, Ankündigungen. Am Observer-Aufbau ändert sich dabei nichts: anderer Host, anderer Broker, anderer Topic-Prefix, kein Reflash.
Teil A dieser Anleitung gilt unverändert weiter, der Rest steht auf einer eigenen Seite → meshinfra
Start · Schnellstart · Funkeinstellungen · Kanäle · Geräte · Rollen · Repeaterliste · Repeater Setup Guide · Observer · meshinfra · Karte · FAQ · Glossar · Mitmachen · Alle Themen