Skip to content
אצלי
Go back

Capturando ADS-B em 1090 MHz com a RTL-SDR v4

Torre de radar de aeroporto com avião cruzando o céu azul
Foto de Peter Xie na Pexels

Este é o quarto artigo da série, e é o hands-on prometido: o procedimento completo, do zero até a captura real de aeronaves, no servidor de homelab rodando EndeavourOS (Arch-based) com a RTL-SDR Blog V4.

Recapitulando o caminho até aqui: no primeiro artigo apresentei o projeto, a ideia de gerar meus próprios dados do mundo físico em vez de só olhar dashboards alheios, aproveitando que moro embaixo de uma das rotas aéreas mais movimentadas da América Latina, a de Guarulhos. No segundo mergulhei no protocolo: como o ADS-B funciona, a mensagem de 112 bits em 1090 MHz, a modulação PPM, e o detalhe incômodo de que tudo isso trafega sem autenticação nenhuma, aberto pra quem quiser ouvir (ou falsificar). No terceiro o hardware chegou: expliquei o que diferencia a v4 original dos clones (tuner R828D, TCXO de 1 PPM, bias tee, front-end filtrado) e documentei a instalação do driver nas duas máquinas, com o AUR resolvendo tudo em minutos no mini PC enquanto o Fedora do desktop virava uma saga de lib perdida, porta USB fraca e módulo de kernel zumbi. Agora falta o que interessa: transformar sinal de rádio em avião na tela.

1. Base já instalada (do artigo anterior)

Estes passos já haviam sido feitos e documentados no post anterior. Ficam registrados aqui pra dar o contexto completo.

1.1 Driver da RTL-SDR Blog V4

A v4 usa um chip R828D com mudanças de hardware que a librtlsdr padrão (osmocom) não trata direito, então é obrigatório usar o fork da própria RTL-SDR Blog.

yay -S rtl-sdr-blog-git

Instala a librtlsdr (fork) e os utilitários rtl_test, rtl_sdr, rtl_tcp, rtl_eeprom e rtl_biast.

1.2 Bloquear o driver de TV/DVB do kernel

O kernel Linux tenta reconhecer a RTL-SDR como uma placa de TV digital (dvb_usb_rtl28xxu) e assume o dispositivo antes que qualquer ferramenta SDR consiga abri-lo.

echo 'blacklist dvb_usb_rtl28xxu' | sudo tee /etc/modprobe.d/blacklist-rtlsdr.conf
sudo modprobe -r dvb_usb_rtl28xxu

Sem isso, rtl_test e rtl_sdr não conseguem reivindicar o dispositivo USB.

1.3 Validação inicial do hardware

rtl_test -t

Saída esperada (e obtida):

Found 1 device(s):
  0:  RTLSDRBlog, Blog V4, SN: 00000001
Using device 0: Generic RTL2832U OEM
Found Rafael Micro R828D tuner
RTL-SDR Blog V4 Detected

O aviso No E4000 tuner found, aborting. que aparece depois é normal: a flag -t do rtl_test é um teste de ganho específico do tuner E4000 (que não existe nesse dongle) e não indica falha real.

2. Confirmando a captura de RF em 1090 MHz

Antes de instalar qualquer decodificador, vale validar que o hardware realmente sintoniza e amostra em 1090 MHz:

rtl_sdr -f 1090000000 -s 2000000 -n 4000000 test1090.bin

Resultado: arquivo de 8.000.000 bytes (4M amostras I/Q vezes 2 bytes) gravado sem erros de overflow. Isso confirma que o pipeline RF, USB e disco está saudável antes de envolver software de decodificação.

3. Antena: qual usar para 1090 MHz

O kit veio com 4 antenas (2 grandes, 2 pequenas) e 2 suportes (um em “V” pra 2 antenas, outro de antena única com extensão).

Para 1090 MHz: uma antena pequena, no suporte único (vertical), não no suporte em V.

Motivos:

A estação montada, por enquanto em versão provisória de bancada: o tripé flexível do kit agarrado no mini PC do homelab (GK3 PRO N5105), com a antena na vertical:

Mini PC do homelab com o tripé flexível do kit montado sobre o gabinete e a antena estendida na vertical

4. Instalando o readsb (decodificador ADS-B)

O readsb é o decodificador, sucessor do dump1090: consome as amostras I/Q brutas em 1090 MHz, encontra os preâmbulos das mensagens do transponder, decodifica e valida CRC.

4.1 Primeira tentativa (via yay, no cache padrão do AUR)

yay -S readsb-git --answerclean All --answerdiff None --removemake --noconfirm

Falhou na compilação. O Makefile do projeto usa -Werror, e o GCC do sistema (mais novo) trata como erro dois avisos que o código (sem manutenção ativa desde ~2020) não previa:

interactive.c:102: error: initializer-string ... truncates NUL terminator [-Werror=unterminated-string-initialization]
interactive.c:195: error: '%5d' directive output may be truncated [-Werror=format-truncation=]

4.2 Tentativa de patch no PKGBUILD do cache do yay

Adicionei uma função prepare() ao PKGBUILD em ~/.cache/yay/readsb-git/ removendo o -Werror do Makefile via sed. Não persistiu: o yay reseta o PKGBUILD pro estado oficial do AUR a cada execução. É uma proteção contra adulteração local, comportamento correto e esperado do yay, mas incompatível com patches manuais.

4.3 Build manual fora do yay (solução definitiva)

Copiei o PKGBUILD e os arquivos de suporte pra um diretório próprio, fora do controle do yay:

mkdir -p ~/readsb-build
cp ~/.cache/yay/readsb-git/{PKGBUILD,readsb.default,readsb.sysusers,readsb.service,readsb.tmpfiles} ~/readsb-build/

E editei o PKGBUILD adicionando um prepare():

prepare() {
  cd "${srcdir}/${_gitname}"
  # Upstream Makefile hardcodes -Werror; GCC novo emite avisos novos
  # (unterminated-string-initialization, format-truncation) que esse
  # código sem manutenção não previa, virando erro fatal de build.
  sed -i 's/-Werror //' Makefile
  # GCC 14+ também tornou incompatible-pointer-types/implicit-function-declaration/
  # int-conversion erros por padrão em C, independente de -Werror.
  # readsbrrd.c passa char** onde rrd.h espera const char** - volta a ser aviso.
  sed -i '/^CFLAGS += \$(DIALECT)/ s/$/ -Wno-error=incompatible-pointer-types -Wno-error=implicit-function-declaration -Wno-error=int-conversion -Wno-error=implicit-int/' Makefile
}

Build e empacotamento:

cd ~/readsb-build
makepkg -si --noconfirm --cleanbuild

Compilou com sucesso, gerando os binários readsb e viewadsb. Se o pacman -U final pedir senha num contexto sem tty, basta rodar manualmente:

sudo pacman -U ~/readsb-build/readsb-git-4.0.4.r60.g845b65eb-1-x86_64.pkg.tar.zst

5. Permissão de acesso ao dispositivo USB

O pacote cria um usuário de sistema dedicado (readsb) pra rodar o serviço. A regra udev padrão do rtl-sdr libera o dispositivo pro grupo plugdev:

grep 2838 /usr/lib/udev/rules.d/10-rtl-sdr.rules
# SUBSYSTEMS=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", ..., GROUP="plugdev"

Só que o dispositivo real estava com dono root:root. O GROUP="plugdev" da regra não estava sendo aplicado: o systemd-logind dá ACL de acesso ao usuário com sessão ativa, o que mascarava o problema pro usuário interativo, mas não ajuda um usuário de serviço sem sessão como o readsb. A solução foi uma regra dedicada:

sudo tee /etc/udev/rules.d/99-readsb-rtlsdr.rules > /dev/null <<'EOF'
SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", GROUP="readsb", MODE="0660"
EOF
sudo udevadm control --reload-rules
sudo udevadm trigger

Resultado: /dev/bus/usb/001/007 passou a crw-rw----+ root readsb.

6. Configuração do readsb (/etc/default/readsb)

sudo tee /etc/default/readsb > /dev/null <<'EOF'
USER_OPTIONS="--device 0 --lat -23.58 --lon -46.55"
EOF

7. Corrigindo o readsb.service (flags incompatíveis com essa versão)

O readsb.service do pacote (/usr/lib/systemd/system/readsb.service) usa flags de uma versão mais nova do readsb do que a que foi compilada aqui. Em vez de editar o arquivo do pacote (seria sobrescrito em atualizações futuras), criei um override do systemd:

sudo mkdir -p /etc/systemd/system/readsb.service.d
sudo tee /etc/systemd/system/readsb.service.d/override.conf > /dev/null <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/bin/readsb --device-type rtlsdr --gain -10 \
                          --ppm 0 \
                          --max-range 360 \
                          --net \
                          --net-heartbeat 60 \
                          --net-ro-size 1200 \
                          --net-ro-interval 0.1 \
                          --net-ri-port 0 \
                          --net-ro-port 30002 \
                          --net-sbs-port 30003 \
                          --net-bi-port 30004,30104 \
                          --net-bo-port 30005 \
                          --rx-location-accuracy 1 \
                          --write-output /run/readsb \
                          $USER_OPTIONS --quiet
EOF
sudo systemctl daemon-reload
sudo systemctl restart readsb

Correções feitas (descobertas iterativamente lendo readsb --help a cada falha nova):

Flag original (não reconhecida)Flag correta nesta buildMotivo
--json-location-accuracy--rx-location-accuracynome da flag mudou entre versões
--write-json /run/readsb--write-output /run/readsbidem
(ausente)--device-type rtlsdrbinário compilado com suporte simultâneo a RTL-SDR, BladeRF e PlutoSDR; explicitar evita ambiguidade

O ExecStart= vazio antes do novo ExecStart=... é necessário: em overrides do systemd, isso limpa o ExecStart original do pacote antes de definir o novo, senão os dois se acumulam.

8. Habilitando e validando o serviço

sudo systemctl enable --now readsb
sudo systemctl status readsb --no-pager

Log de inicialização confirmando o hardware correto:

rtlsdr: using device #0: Generic RTL2832U OEM (RTLSDRBlog, Blog V4, SN 00000001)
Found Rafael Micro R828D tuner
RTL-SDR Blog V4 Detected
rtlsdr: enabling tuner AGC

Portas de rede abertas (confirmado via ss -tlnp): 30002 (raw out), 30003 (SBS/BaseStation out), 30004/30104 (Beast in), 30005 (Beast out).

Captura real de aeronaves

nc localhost 30003

Trecho de saída real capturada (formato SBS/BaseStation, uma linha por mensagem decodificada):

MSG,3,1,1,E491F0,1,2026/07/24,18:19:31.069,2026/07/24,18:19:31.077,,10750,,,-23.53653,-46.35493,,,0,,0,0
MSG,3,1,1,3C6706,1,2026/07/24,18:19:31.191,2026/07/24,18:19:31.240,,22125,,,-23.21251,-45.49174,,,0,,0,0
MSG,4,1,1,E491F0,1,2026/07/24,18:19:31.688,2026/07/24,18:19:31.732,,,305,251,,,960,,,,,0
MSG,3,1,1,E493D1,1,2026/07/24,18:19:33.515,2026/07/24,18:19:33.534,,,,,-23.46501,-46.07806,,,0,,0,0

Leitura rápida do formato SBS: MSG,<tipo>,...,<ICAO hex>,...,<data>,<hora>,...,<altitude>,...,<velocidade>,<proa>,<lat>,<lon>,<taxa vertical>,...

Três aeronaves distintas decodificadas nos primeiros 20 segundos:

ICAOAltitudePosição aproximada
E491F0~10.775 ft-23.53, -46.35
3C6706~22.100 ft-23.21, -45.49
E493D1~28.325 ft-23.46, -46.07

O pacote também traz o viewadsb, que conecta na porta Beast local e mostra a tabela de aeronaves ao vivo no terminal, com callsign, altitude, velocidade, posição e RSSI. Abaixo, a estação em operação: o viewadsb no topo, uma visão resumida ao vivo no meio e o stream SBS bruto do nc embaixo, com voos reais da TAM, Aerolíneas Argentinas e Lufthansa passando sobre a região:

Três terminais lado a lado: viewadsb listando aeronaves com callsign e RSSI, uma visão ao vivo resumida com quatro aeronaves detectadas, e o stream SBS bruto recebido via nc na porta 30003

Recepção de ADS-B confirmada de ponta a ponta: antena, RTL-SDR v4, readsb, mensagens decodificadas com posição, altitude e velocidade válidas.


Share this post on:

Next Post
linux-daw-ssl-lowlatency: exclusive ALSA for DAWs under PipeWire