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 | |
|---|---|
| 0 | wersja protokołu (1 albo 2) |
| 1–2 | token |
| 3 | typ — 0 PUSH_DATA (odebrane ramki), 3 PULL_RESP (brama poproszona o nadanie) |
| 4–11 | EUI bramy |
| reszta | JSON |
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
| flaga | znaczenie |
|---|---|
--iface | interfejs do obserwowania, domyślnie lo |
--dry-run | wypisz, co byłoby wysłane, nie wysyłaj nic |
--log-tx | loguj też, co nadaje Twoja brama |
--find | przeskanuj LAN w poszukiwaniu nodów Sensmos i wypisz komendę parowania |
--pair IP --pin PIN --name "…" | jednorazowe parowanie przez noda |
--install-service | skopiuj do /usr/local/bin, zapisz i włącz unit systemd (najpierw Ci go pokaże) |
--be URL | alternatywny 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 wymagaCAP_NET_RAW. Użyjsudo(albosetcap cap_net_raw+epna binarce Pythona, co nadaje to każdemu programowi w Pythonie —sudojest 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.--findnic nie znajduje — node musi być w tym samym segmencie LAN i online (jego/infomusi odpowiadaćws_connected: true). Zamiast tego podaj IP noda z apki.pair firstprzy--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.