ENPLDEPT
Flash
Cała dokumentacja
Brama LoRaWAN

Dodaj swoją bramę LoRaWAN do Sensmos — pasywny nasłuch dla packet forwarderów Semtech

Brama Helium, TTN albo prywatna widzi każdą ramkę LoRa wokół siebie. Jeden pasywny skrypt raportuje metadane radiowe do Sensmos — nadania awaryjne i ramki czujników pobliskich nodów są dostarczane, a Twoja brama nigdy nie zmienia tego, co robi dla własnej sieci.

radio · 868 MHz · emergency frame
Heard by a neighbour · 2.1 kmgarage · temp 3.4°C · door OPEN · 21:44

Dla kogo to jest

Dowolna maszyna z Linuksem, na której działa zwykły packet forwarder Semtech (UDP, port w stylu 1700) skierowany na zdalny serwer: hotspoty Helium i ich stacki multi-mining, RAK, Dragino, Mikrotik, Raspberry Pi z HAT-em koncentratora. Jeśli Twoja brama ma zamiast tego ChirpStack Gateway Bridge, jest tam lokalny broker MQTT i stosuje się inny agent.

Potrzebujesz też jednego noda Sensmos w tym samym LAN-ie co brama (dowolne ESP32 z firmware, radio niewymagane) i jego PIN-u. Node ręczy za bramę; bez niego backend nie wyda tokena.

Dlaczego pasywnie, a nie przekaźnik

Oczywisty sposób na podpięcie się pod forwarder to wstawić coś pośrodku — skierować forwarder na nas i przekazywać dalej. Celowo tego nie robimy. Na sprzęcie, który zarabia, nasza awaria stałaby się Twoją awarią. Forwarder i tak wysyła każdą ramkę do każdego skonfigurowanego serwera osobno przez loopback; skrypt po prostu czyta razem z nim na lo. Nie jest niczyim celem, niczego nie przekazuje i niczego nie opóźnia.

Szybki start

Wszystko to uruchom na bramie, nie na laptopie. Tylko Python 3 — bez pakietów pip, bez budowania. Root jest potrzebny, bo czytanie surowych pakietów wymaga CAP_NET_RAW.

# 1 — install the script
sudo curl -fsSL -o /usr/local/bin/sensmos-gw-udp.py https://raw.githubusercontent.com/Galusz/sensmos-gateway/main/sensmos-gw-udp.py
sudo chmod +x /usr/local/bin/sensmos-gw-udp.py

# 2 — see what would leave your box (sends nothing, needs no pairing)
sudo sensmos-gw-udp.py --dry-run

# 3 — find a Sensmos node on this network (prints the exact pair command)
sudo sensmos-gw-udp.py --find

# 4 — pair with it (address from step 3, PIN = the node's PIN)
sudo sensmos-gw-udp.py --pair NODE_IP --pin YOUR_PIN --name "rooftop RAK"

# 5 — install & start it as a boot-persistent service
sudo sensmos-gw-udp.py --install-service

# 6 — confirm
systemctl status sensmos-gw-udp --no-pager
journalctl -u sensmos-gw-udp -f

Krok 6 powinien pokazać active (running), a log wypisuje connection works — first batch accepted, gdy popłyną ramki, potem linię statusu co minutę. W apce brama pojawia się w Przystawkach noda; na mapie na żywo node zyskuje warstwę RF.

Już sparowane, brak usługi? Token przetrwał; powtórz kroki 1 i 5. Tylko jeśli --install-service mówi pair first, powtórz krok 4.

Zajrzyj, zanim nam zaufasz

--dry-run wypisuje jako JSON dokładnie te ramki, które by przekazał, i na tym poprzestaje. Dodaj --log-tx, żeby zobaczyć też, kiedy Twoja brama nadaje. Skrypt to jeden czytelny plik; źródło jest na GitHubie.

Parowanie — jak działa token

Skrypt nie ma własnych poświadczeń. Prosi noda Sensmos w Twoim LAN-ie (z PIN-em noda), żeby za niego poręczył; node przekazuje żądanie do backendu przez własne łącze, a backend wydaje token ograniczony do radio.frames — raportowanie odebranych ramek, nic więcej. Token jest przechowywany w /etc/sensmos-gw-udp.json (tryb 0600). Od tej chwili skrypt rozmawia z backendem bezpośrednio; node jest poza pętlą.

  • Jeden node może poręczyć za maksymalnie cztery przystawki.
  • Właściciel może odwołać każdą z nich z apki (node → Przystawki); skrypt natychmiast staje się bezczynny.
  • Token żyje 30 dni od pierwszego użycia, a skrypt odnawia go sam.

Pełny kontrakt (endpointy, zakresy, formaty) jest na Przystawki i EXT API.

Co czyta

Semtech GWMP przez UDP, rozpoznawany po nagłówku pakietu, nie po porcie, więc działa niezależnie od tego, jakich portów używa Twój stack:

bajt
0wersja protokołu (1 albo 2)
1–2token
3typ — 0 PUSH_DATA (odebrane ramki), 3 PULL_RESP (brama poproszona o nadanie)
4–11EUI bramy
resztaJSON

Z PUSH_DATA bierze wpisy rxpk: częstotliwość, spreading factor, RSSI, SNR, rozmiar, wynik CRC i surowy payload PHY. Payloady aplikacyjne są zaszyfrowane kluczami, których nie mamy; sieć używa metadanych radiowych i rozpoznawania ramek Sensmos w ruchu.

PULL_RESP — nadanie, które Twoja brama zaraz wykona — jest raportowane tylko wtedy, gdy jest słyszalne dla innych (ipol = false, np. beacony pokrycia, które nadają niektóre sieci). Zwykły downlink LoRaWAN używa odwróconego IQ, którego nikt inny nie słyszy, niczego nie dowodzi i jest pomijany. Raportowane nadania są dowodem, że brama jest prawdziwa: ich payload jest nieprzewidywalny, więc node Sensmos, który niezależnie usłyszy te same bajty, potwierdza, że brama faktycznie nadała.

Forwarder wieloserwerowy wysyła identyczną ramkę do każdego skonfigurowanego serwera, więc to samo nadanie pojawia się na loopbacku kilka razy (RF packets received: 1 / PUSH_DATA datagrams sent: 2 w logu Twojego forwardera). Skrypt deduplikuje; jedno nadanie jest raportowane raz. Ramki są grupowane (raz na sekundę albo co 50 ramek) i wysyłane na https://sensmos.com/v1/ext/radio/frames.

Flagi

flagaznaczenie
--ifaceinterfejs do obserwowania, domyślnie lo
--dry-runwypisz, co byłoby wysłane, nie wysyłaj nic
--log-txloguj też, co nadaje Twoja brama
--findprzeskanuj LAN w poszukiwaniu nodów Sensmos i wypisz komendę parowania
--pair IP --pin PIN --name "…"jednorazowe parowanie przez noda
--install-serviceskopiuj do /usr/local/bin, zapisz i włącz unit systemd (najpierw Ci go pokaże)
--be URLalternatywny backend, do testów

Unit, który zapisuje, gdybyś wolał zrobić to ręcznie:

# /etc/systemd/system/sensmos-gw-udp.service
[Unit]
Description=Sensmos passive gateway listener
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/bin/python3 /usr/local/bin/sensmos-gw-udp.py
Restart=always
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target

Docker i kontenery

Skrypt sniffuje AF_PACKET na loopbacku hosta. Loopback jest prywatny per przestrzeń nazw sieciowych, więc izolowany kontener widzi puste lo i nic nie słyszy. Uruchomienie w kontenerze wymaga --network host i --cap-add NET_RAW. Wspierany sposób to usługa systemd na hoście; build kontenerowy utrzymujesz na własną rękę.

Aktualizacje

Skrypt nigdy nie aktualizuje się sam — celowo. Działa jako root na Twojej bramie, w Twoim LAN-ie; automatyczne pobieranie kodu z serwera zrobiłoby z tego serwera drogę do Twojej sieci. Żeby zaktualizować, powtórz krok 1 i zrestartuj usługę:

sudo curl -fsSL -o /usr/local/bin/sensmos-gw-udp.py https://raw.githubusercontent.com/Galusz/sensmos-gateway/main/sensmos-gw-udp.py
sudo systemctl restart sensmos-gw-udp

Rozwiązywanie problemów

  • root required: run with sudo — surowe przechwytywanie wymaga CAP_NET_RAW. Użyj sudo (albo setcap cap_net_raw+ep na binarce Pythona, co nadaje to każdemu programowi w Pythonie — sudo jest czystsze).
  • Połączony, ale brak ramek — sprawdź, czy ruch naprawdę jest na loopbacku: sudo tcpdump -i lo -n -c 10 udp. Jeśli Twój forwarder rozmawia ze zdalnym serwerem przez sieć, a nie z lokalnym mostkiem, skieruj skrypt na ten interfejs: --iface eth0.
  • AF_PACKET is Linux only — zgadza się. To działa na bramie, nie na Macu.
  • --find nic nie znajduje — node musi być w tym samym segmencie LAN i online (jego /info musi odpowiadać ws_connected: true). Zamiast tego podaj IP noda z apki.
  • pair first przy --install-service — parowanie się nie dokończyło; sprawdź PIN i czy node jest online.

Odinstalowanie

sudo systemctl disable --now sensmos-gw-udp
sudo rm /etc/systemd/system/sensmos-gw-udp.service /usr/local/bin/sensmos-gw-udp.py /etc/sensmos-gw-udp.json

Odwołaj też przystawkę w apce. Nic innego nigdy nie zostało zmienione, więc nie ma nic więcej do cofnięcia.

W sieci dziś

Pierwsza obca brama dołączyła w sierpniu 2026 (Niemcy), poręczona przez noda bez własnego radia. Ramki, które słyszy — nadania awaryjne i ramki czujników nodów w zasięgu — są dostarczane jak ramki usłyszane przez dowolnego noda. Szczegóły tego, co LoRa daje właścicielowi noda: LoRa.

Pytania i proste odpowiedzi

Czy to zmienia konfigurację mojej bramy?

Nie. Nic nie jest edytowane, żadna usługa nie jest restartowana, do forwardera nie jest dodawany żaden serwer. Skrypt tylko czyta pakiety, które i tak płyną przez loopback. Zabij go, a brama tego nie zauważy.

Czy może zmusić moją bramę do nadawania?

Nie — nie ma na to ścieżki w kodzie. Raportuje nadania, które brama robi sama z siebie (słyszalne beacony pokrycia), ale nigdy ich nie wywołuje.

Czy potrzebuję konta?

Nie. Node Sensmos w tym samym LAN-ie ręczy za bramę, a backend wydaje jej wąski token ograniczony do raportowania ramek. Właściciel noda może go w każdej chwili odwołać z apki.

Co dostaje sieć?

Każdą ramkę LoRa, którą brama słyszy, jako metadane radiowe plus surowe bajty PHY. Ramki Sensmos (nadania awaryjne, ramki czujników) są rozpoznawane i dostarczane właścicielom; obcy ruch LoRaWAN liczy się tylko jako statystyka widma — payloady są zaszyfrowane, a my nie mamy kluczy.

Czy brama zarabia GALU?

Sama brama nie ma portfela. Node, który za nią ręczy, dostaje kategorię danych RF, a to podnosi jego wagę nagród (+10% za każdą kategorię danych). Dodatkowo, gdy płatny odbiór ramki czujnika trafia do pierwszego obcego nasłuchu w danym dniu i jest nim Twoja brama, 70% opłaty idzie do właściciela ręczącego noda.

Moja brama ma ChirpStack Gateway Bridge, nie goły forwarder

Wtedy jest lokalny broker MQTT i lepiej pasuje inny agent (sensmos-gw-mqtt.py, w tym samym repozytorium) — ale jest nieprzetestowany: nie mamy takiej bramy, żeby go sprawdzić. Uruchom go z --dry-run i daj znać, co widzisz. Ta strona opisuje przypadek gołego packet forwardera, czyli to, co ma większość bram klasy Helium.

Aktualizacja: 2026-09-03