Para quem é
Qualquer máquina Linux a correr um packet forwarder Semtech simples (UDP, ao estilo da porta 1700) apontado para um servidor remoto: hotspots Helium e as suas stacks de multi-mining, RAK, Dragino, Mikrotik, um Raspberry Pi com um HAT concentrador. Se o seu gateway corre o ChirpStack Gateway Bridge, existe um broker MQTT local e aplica-se um agente diferente.
Também precisa de um node Sensmos na mesma LAN que o gateway (qualquer ESP32 com o firmware, sem rádio necessário) e do seu PIN. O node responde pelo gateway; sem ele o backend não emite um token.
Porquê passivo, e não um relay
A forma óbvia de ler um forwarder é pôr algo no meio — apontar o forwarder para nós e reencaminhar para a frente. Deliberadamente não o fazemos. Em hardware que ganha dinheiro, a nossa falha tornar-se-ia a sua falha. O forwarder já envia cada frame para cada servidor configurado separadamente pelo loopback; o script limita-se a ler em lo. Não é o destino de ninguém, não retransmite nada e não atrasa nada.
Início rápido
Corra tudo isto no gateway, não no seu laptop. Apenas Python 3 — sem pacotes pip, nada para compilar. É preciso root porque ler pacotes em bruto exige 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
O passo 6 deve mostrar active (running), e o log imprime connection works — first batch accepted assim que os frames fluem, e depois uma linha de estado a cada minuto. Na app, o gateway aparece em Attachments do node; no mapa ao vivo o node ganha a camada RF.
Já emparelhado, mas sem serviço? O token sobrevive; repita os passos 1 e 5. Só se --install-service disser pair first repita o passo 4.
Veja antes de confiar em nós
--dry-run imprime, em JSON, exatamente os frames que reencaminharia e fica por aí. Adicione --log-tx para ver também quando o seu gateway transmite. O script é um único ficheiro legível; o código está no GitHub.
Emparelhamento — como funciona o token
O script não tem credenciais próprias. Pede ao node Sensmos na sua LAN (com o PIN do node) que responda por ele; o node retransmite o pedido ao backend pela sua própria ligação, e o backend emite um token limitado ao scope radio.frames — reportar frames recebidos, nada mais. O token fica guardado em /etc/sensmos-gw-udp.json (modo 0600). A partir daí o script fala diretamente com o backend; o node fica fora do circuito.
- Um node pode responder por até quatro attachments.
- O dono pode revogar qualquer um deles a partir da app (node → Attachments); o script fica inerte de imediato.
- Um token vive 30 dias a partir do primeiro uso e o script renova-o sozinho.
O contrato completo (endpoints, scopes, formatos) está em Attachments e a API EXT.
O que ele lê
Semtech GWMP por UDP, reconhecido pelo cabeçalho do pacote, não pela porta, por isso funciona sejam quais forem as portas que a sua stack usa:
| byte | |
|---|---|
| 0 | versão do protocolo (1 ou 2) |
| 1–2 | token |
| 3 | tipo — 0 PUSH_DATA (frames recebidos), 3 PULL_RESP (gateway instruído a transmitir) |
| 4–11 | EUI do gateway |
| resto | JSON |
De PUSH_DATA extrai as entradas rxpk: frequência, spreading factor, RSSI, SNR, tamanho, resultado do CRC e o payload PHY em bruto. Os payloads de aplicação estão encriptados com chaves que não temos; o que a rede usa são os metadados de rádio e o reconhecimento de frames Sensmos no meio do tráfego.
PULL_RESP — uma transmissão que o seu gateway está prestes a fazer — é reportado apenas quando é audível por outros (ipol = false, p. ex. beacons de cobertura que algumas redes transmitem). Um downlink LoRaWAN normal usa IQ invertido que mais ninguém consegue ouvir, não prova nada e é ignorado. Os reportados são prova de que o gateway é real: o seu payload é imprevisível, por isso um node Sensmos que ouça os mesmos bytes de forma independente confirma que o gateway realmente transmitiu.
Um forwarder multi-servidor envia o frame idêntico para cada servidor configurado, pelo que a mesma transmissão aparece várias vezes no loopback (RF packets received: 1 / PUSH_DATA datagrams sent: 2 no log do seu forwarder). O script desduplica; uma transmissão é reportada uma vez. Os frames são agrupados (uma vez por segundo ou a cada 50 frames) e enviados para https://sensmos.com/v1/ext/radio/frames.
Flags
| flag | significado |
|---|---|
--iface | interface a observar, por padrão lo |
--dry-run | imprimir o que seria enviado, não enviar nada |
--log-tx | registar também o que o seu gateway transmite |
--find | procurar nodes Sensmos na LAN e imprimir o comando de emparelhamento |
--pair IP --pin PIN --name "…" | emparelhamento único através de um node |
--install-service | copiar para /usr/local/bin, escrever e ativar a unit systemd (mostrada antes) |
--be URL | backend alternativo, para testes |
A unit que ele escreve, caso prefira fazê-lo à mão:
# /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 e containers
O script escuta AF_PACKET no loopback do host. O loopback é privado por namespace de rede, por isso um container isolado vê um lo vazio e não ouve nada. Corrê-lo num container exige --network host e --cap-add NET_RAW. A forma suportada é o serviço systemd no host; uma build em container é sua para manter a funcionar.
Atualizações
O script nunca se atualiza sozinho — por design. Corre como root no seu gateway, na sua LAN; puxar código automaticamente de um servidor faria desse servidor uma porta de entrada para a sua rede. Para atualizar, repita o passo 1 e reinicie o serviço:
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
Resolução de problemas
root required: run with sudo— a captura em bruto precisa deCAP_NET_RAW. Usesudo(ousetcap cap_net_raw+epno binário do Python, o que o concede a todos os programas Python —sudoé mais limpo).- Ligado mas sem frames — confirme que o tráfego está mesmo no loopback:
sudo tcpdump -i lo -n -c 10 udp. Se o seu forwarder fala com um servidor remoto pela rede em vez de com uma bridge local, aponte o script para essa interface:--iface eth0. AF_PACKET is Linux only— é mesmo. Isto corre no gateway, não num Mac.--findnão encontra nada — o node tem de estar no mesmo segmento da LAN e online (o seu/infotem de responder comws_connected: true). Em alternativa, dê-lhe o IP do node a partir da app.pair firstem--install-service— o emparelhamento não foi concluído; verifique o PIN e se o node está online.
Desinstalar
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
Revogue também o attachment na app. Nada mais foi alguma vez alterado, por isso não há mais nada para desfazer.
Na rede hoje
O primeiro gateway de terceiros juntou-se em agosto de 2026 (Alemanha), com um node sem rádio próprio a responder por ele. Os frames que ouve — transmissões de emergência e frames de sensores dos nodes ao alcance — são entregues como os frames ouvidos por qualquer node. Detalhes do que o LoRa faz pelo dono de um node: LoRa.