~/blog

Czym jest dekoder w HexSDR? Od surowych próbek radiowych do samolotów, statków i tytułów piosenek

18.09.2026 · 6 min

Gdy ludzie widzą radio definiowane programowo (SDR) po raz pierwszy, widzą zazwyczaj wodospad: wielobarwne pasma płynące z góry na dół ekranu. Wygląda to efektownie, ale sam wodospad nie mówi człowiekowi niemal nic. Prawdziwa zabawa zaczyna się w momencie, gdy komputer rozumie, co kryje się wewnątrz odebranego sygnału.

W projekcie HexSDR za to zadanie odpowiadają dekodery – obecnie w kodzie znajduje się ich ponad 120. W tym wpisie wyjaśniam, jak działają od środka i do czego służą, na konkretnych przykładach jawnych sygnałów, które każdy z nas może legalnie odebrać z powietrza.

Stack technologiczny

  • Dekodery w Pythonie: NumPy, SciPy, Numba (kompilacja krytycznych pętli w locie)
  • Mostki IPC: sprawdzone, natywne narzędzia open-source uruchamiane w odizolowanych procesach tam, gdzie pisanie kodu od zera nie miało sensu
  • Warstwa transportowa: WebSockety strumieniujące zdarzenia bezpośrednio do przeglądarki
  • Frontend: React, dedykowane okno dla każdego typu dekodera, mapa MapLibre GL dla emisji z koordynatami GPS

1. Jeden żelazny kontrakt dla każdego dekodera

Dekoder w HexSDR to wtyczka realizująca prostą obietnicę. Przyjmuje paczkę próbek IQ (surowe dane radiowe w postaci liczb zespolonych) i zwraca ustrukturyzowane metadane w czytelnym formacie, w pełni serializowalnym do JSON-a.

Demodulacja odbywa się bezpośrednio wewnątrz dekodera, dzięki czemu główny silnik aplikacji nie musi wiedzieć absolutnie nic o specyfice danego protokołu. Obowiązują przy tym twarde reguły: dekoder jest bezstanowy pomiędzy wywołaniami resetu, a przetworzenie pojedynczego bloku próbek musi zająć poniżej 500 ms, aby podgląd na żywo nie łapał żadnych opóźnień.

Dzięki temu rdzeń systemu traktuje dokładnie tak samo telemetryczny dekoder lotniczy, jak i prosty demodulator telegrafii (CW). Silnik wie tylko, jak dekoder zainicjalizować, nakarmić próbkami i odebrać wynik.

2. Każdy dekoder sam deklaruje, jak szerokiego pasma potrzebuje

Różne sygnały wymagają diametralnie różnej szerokości wycinka pasma radiowego. Każdy dekoder w HexSDR definiuje parametr preferred_bandwidth. Gdy podpinasz go pod dany wycinek (slice), serwis DSP samoczynnie konfiguruje odpowiednią szerokość filtru i próbkowania:

Dekoder

Co dekoduje

Preferowana szerokość pasma

CW

Kod Morse'a (telegrafia)

3 kHz

APRS

Pakiety krótkofalarskie

12,5 kHz

AIS

Pozycje i kursy statków

25 kHz

RDS

Dane tekstowe stacji radiowych FM

200 kHz

DAB

Cyfrowe radio rozsiewcze

1,536 MHz

ADS-B

Transpondery i pozycje samolotów

2 MHz

Rozpiętość jest gigantyczna: od 3 tysięcy do 2 milionów herców. Bez automatycznej deklaracji w kodzie musiałbym pamiętać właściwe parametry dla każdego ze 120 protokołów przy każdym kliknięciu – co w praktyce natychmiast prowadziłoby do pomyłek.

3. Kod natywny albo sprawdzone narzędzie – wybieram to, co lepsze

Nie piszę wszystkiego od zera, gdy nie ma to racjonalnego uzasadnienia inżynierskiego.

Dekoder transponderów ADS-B napisałem od podstaw w Pythonie. Z kolei dla niektórych standardów istnieją już wybitne, rozwijane latami narzędzia open-source – w takich sytuacjach zaprzęgam je do pracy za pomocą dedykowanego mostka (bridge). Przykładowo, dekoder cyfrowego radia DAB uruchamia w tle binarkę dablin i przesyła zdekodowany dźwięk wprost do przeglądarki w postaci strumienia PCM. Podobnie zoptymalizowane narzędzia obsługują APRS czy dane stacji FM. Mostek zarządza pełnym cyklem życia takiego procesu (start, monitoring, automatyczny restart po błędzie) i formatuje jego wyjście do wspólnego standardu JSON.

Słabe sygnały niosą jednak pułapkę. Gdy zmiennoprzecinkowe próbki IQ (float) są konwertowane do formatu 8- lub 16-bitowego na potrzeby zewnętrznego programu, bardzo cichy sygnał potrafi zająć zaledwie kilka najniższych bitów i całkowicie zniknąć w szumie kwantyzacji. Z tego powodu wyposażyłem mostki w adaptacyjną normalizację – wewnętrzny układ AGC, który podbija dynamikę sygnału jeszcze przed konwersją formatu.

4. Co realnie można odebrać i zdekodować?

Wszystkie poniższe przykłady to w 100% jawne, niekodowane transmisje radiowe, które z założenia są nadawane publicznie w eter:

  • Samoloty (ADS-B): Statki powietrzne na częstotliwości 1090 MHz nieustannie transmitują swoją pozycję GPS, wysokość, prędkość i kurs dla bezpieczeństwa ruchu lotniczego. Dekoder przekłada te pakiety na tabelę na żywo oraz znaczniki na trójwymiarowej mapie, rysując trajektorie lotu w czasie rzeczywistym.
  • Statki i żegluga (AIS): Jednostki morskie i śródlądowe rozgłaszają swoje dane identyfikacyjne, wymiary, zanurzenie i kurs. W efekcie otrzymujemy mapę ruchu na wodzie wokół stacji odbiorczej.
  • Informacje stacji FM (RDS): Obok analogowego audio każda współczesna stacja radiowa FM transmituje mały strumień danych: nazwę stacji, alternatywne częstotliwości oraz tytuł aktualnie granego utworu.
  • Cyfrowe radio (DAB/DAB+): Dekoder czyta cały multipleks cyfrowy, wyciąga listę stacji i odtwarza strumień audio wprost w głośnikach przeglądarki.
  • Krótkofalarstwo: Od klasycznego alfabetu Morse'a (CW), przez pakiety pozycji i telemetrii APRS, aż po zaawansowane modulacje słabosygnałowe z rodziny WSJT (takie jak FT8 czy WSPR), zaprojektowane tak, by wydobywać informację z sygnału ukrytego głęboko poniżej poziomu szumów tła.
  • Czujniki wokół nas: Bezprzewodowe domowe stacje pogodowe czy czujniki środowiskowe nadające krótkie paczki danych w nielicencjonowanym paśmie ISM (np. 433 MHz lub 868 MHz).

5. Jeden format danych, dedykowane okno dla każdego protokołu

Ponieważ każdy dekoder zwraca ustandaryzowaną strukturę zdarzeń, interfejs użytkownika może być generyczny tam, gdzie to wygodne, i precyzyjnie wyspecjalizowany tam, gdzie to konieczne.

Każdy dekoder ma własne okno w GUI skrojone pod swoje potrzeby:

  • dla ruchu lotniczego jest to interaktywna tabela z filtrowaniem i wektory na mapie 3D,
  • dla stacji radiowych – czytelny podgląd metadanych i tytułów,
  • dla radia cyfrowego – selektor stacji w danym multipleksie.

Dane z pozycją geograficzną natychmiast zasilają silnik mapy, a wszystkie zdarzenia opatrzone są precyzyjnymi znacznikami czasu, umożliwiając późniejsze odtworzenie historii na osi czasu (timeline replay).

6. Bez ściemy: realny stan wdrożenia

Nie zamierzam udawać, że ponad 120 dekoderów zostało przetestowanych na żywym eterze. W kodzie utrzymuję jawną matrycę stanu – dekoder może mieć jeden z czterech statusów: potwierdzony (confirmed), operacyjny (operational), nieprzetestowany (untested) oraz uszkodzony (broken). Status na „potwierdzony” zmieniam wyłącznie wtedy, gdy własnoręcznie odbiorę i zdekoduję prawdziwy sygnał z podłączonej anteny. Transpondery lotnicze, APRS, RDS i DAB są w pełni potwierdzone w boju; AIS i CW są operacyjne; natomiast część amatorskich trybów cyfrowych ma gotowy kod, ale czeka na walidację w terenie i uczciwie nosi status untested.

Dochodzi do tego fizyka, o której wielu zapomina: tani tuner USB widzi w jednym momencie wycinek pasma o szerokości maksymalnie ok. 2,4 MHz. Samoloty nadają na 1090 MHz, a statki w okolicach 162 MHz. Żaden pojedynczy tuner nie odbierze obu tych emisji w tej samej milisekundzie. W HexSDR rozwiązałem to za pomocą harmonogramu (schedulera), który automatycznie przeskakuje pomiędzy zdefiniowanymi pasmami w pętli. A gdy zależy mi na ciągłym nasłuchu bez przerw – podłączam po prostu dwa tunery: jeden dedykowany dla nieba, drugi dla wody.

Uwaga prawna: Ten wpis i projekt skupiają się wyłącznie na odbiorze jawnych, niekodowanych transmisji o charakterze publicznym. Przepisy dotyczące nasłuchu radiowego różnią się w zależności od kraju – zawsze upewnij się, jakie regulacje obowiązują w Twojej jurysdykcji, zanim zaczniesz eksplorować inne wycinki widma.

👉 Sprawdź projekt na żywo: https://sdr.gut.red


Architektura techniczna w szczegółach

Backend

Wszystkie dekodery są klasami w Pythonie dziedziczącymi po wspólnej abstrakcyjnej klasie bazowej (ABC). Wymusza ona implementację trzech kluczowych metod: przetworzenia bloku próbek IQ, zresetowania stanu wewnętrznego oraz zwrócenia deskryptora możliwości. Wyjście dekodera to lekka klasa danych (dataclass) zawierająca unikalny identyfikator dekodera, słownik z danymi (payload), identyfikator żądania oraz precyzyjny timestamp – zawsze w notacji snake_case i bez problemu poddająca się serializacji do JSON-a. Centralny rejestr przy starcie skanuje moduły i izoluje je, więc ewentualny błąd w jednej wtyczce nie jest w stanie wyłożyć całego silnika.

Tam, gdzie operacje dotykają każdego pojedynczego sampla, pętle kompilowane są maszynowo przez Numbę (@njit). Dla strumieni o wysokiej przepływności (np. ADS-B przy 2 MSPS) zaimplementowałem dedykowaną ścieżkę fast path, która całkowicie odcina filtry pośredniej częstotliwości i tor audio. Najbardziej obciążające narzędzia zewnętrzne są izolowane w osobnych podprocesach zarządzanych przez mostki z automatycznym wznawianiem (respawn).

Frontend

Na poziomie aplikacji klienckiej każdy dekoder posiada swój wpis w rejestrze wtyczek, komponent prezentacyjny oraz domyślną konfigurację wycinka pasma (slice). Dane spływają WebSocketem prosto do wyspecjalizowanych magazynów stanu Zustand, skąd zasilają komponenty mapy (MapLibre GL 5) oraz oś czasu. Lista dekoderów w panelu administracyjnym nie jest statyczną tablicą – buduje się dynamicznie w oparciu o stan rzeczywisty raportowany przez backend w czasie wykonywania.

#ADS-B#AIS#APRS#CW#DAB#decoders#demodulation#DSP#FT8#HexSDR#IQ samples#Numba#plugin architecture#Python#RDS#rtl-sdr#sdr#WSPR

Komentarze

Jeszcze nikt nie skomentował. Bądź pierwszy.

bartosik.run terminal — type `help`.