Zurück zur Startseite

WSPR-Protokoll: Python-Codierung von Rufzeichen

WSPR ist ein Protokoll zur Analyse der Ausbreitung schwacher HF-Signale mit einem verteilten Netzwerk von Baken. Die Struktur wird beschrieben: 4-FSK, 50-Bit-Nachrichten, Python-Implementierung von Codecs für Rufzeichen und Locatoren. Beispiele für Klassen MsgItem und WSPRCallsign.

WSPR auf Python: Codierung ionosphärischer Baken
Advertisement 728x90

WSPR-Protokoll in Python: Praxisbeispiele zur Datenkodierung

Das WSPR-Protokoll nutzt 4-FSK-Modulation, um Signale unterhalb der Rauschschwelle zu übertragen. Jeder Sendezyklus dauert 110,59 Sekunden, belegt eine Bandbreite von nur 5,85 Hz und kodiert die gesamte Nachricht in 50 Bit mittels Faltungscodierung (Code-Rate ½). Sender aktivieren sich jeweils zu Beginn jeder geraden Minute – mit einer Toleranz von ±1 Sekunde – und wählen zufällig eine Frequenz innerhalb eines 200-Hz-Bandes.

Zum Übertragungsinhalt gehören der Rufname der Station, ein vierstelliger QTH-Locator sowie die Sendeleistung in dBm. Empfänger decodieren die Signale, generieren Berichte und laden diese während der verbleibenden 10 Sekunden des Sendezeitfensters auf wsprnet.org hoch.

Physik der Kurzwellenausbreitung

Der KW-Bereich (3–30 MHz) nutzt die Ionosphäre als natürlichen Wellenleiter. Signale brechen in Höhen zwischen 140 und 1000 km: Ein Teil entweicht ins All, ein anderer wird zur Erdoberfläche reflektiert. Mehrfache „Hops“ ermöglichen weltweite Reichweite.

Google AdInline article slot

Die ionosphärischen Bedingungen schwanken je nach Sonnenaktivität, Tageszeit und Jahreszeit:

  • 80 m: optimal für Nachtbetrieb;
  • 40 m: langstreckenfähig nachts, kurze Reichweite tagsüber;
  • 20 m: zuverlässig für Langstreckenverbindungen bei Tageslicht;
  • 10 m: nur in Phasen maximaler Sonnenaktivität nutzbar.

Wichtig: Direkte Nahbereichsverbindungen sind oft nicht praktikabel, da die Reflexionswinkel zu steil sind.

Schichtenarchitektur des Protokolls

WSPR strukturiert die Verarbeitung in drei logische Ebenen:

Google AdInline article slot
  • Anwendungsebene: Rufname, Locator, Sendeleistung;
  • Darstellungsebene: Komprimierung in 50 Bit mittels eigener Zeichentabellen;
  • Verbindungsebene: 4-FSK-Modulation mit Synchronisation und Fehlerkorrektur.

Alle Übertragungen sind auf UTC synchronisiert; Beacons arbeiten pseudozufällig – bis zu sieben Slots pro Stunde sind möglich.

Darstellungsebene: Zeichentabellen und Codecs

Daten werden als Indexwerte in vordefinierten Tabellen kodiert:

import string

CHAR_TABLE_NUMERIC = string.digits
CHAR_TABLE_LETTERS = string.ascii_uppercase
WSPR_CHAR_TABLE_ALPHANUM = f'{CHAR_TABLE_NUMERIC}{CHAR_TABLE_LETTERS}'
WSPR_CHAR_TABLE_ALPHANUM_SPACE = f'{CHAR_TABLE_NUMERIC}{CHAR_TABLE_LETTERS} '

Hilfsfunktionen für die Konvertierung:

Google AdInline article slot
def nchar(c: str, table: str) -> int:
    return table.find(c)

def charn(c: int, table: str) -> str:
    return table[c]

def ct_decode(ct: str, val: int, l: int) -> str:
    s = ''
    ct_l = len(ct)
    for i in range(l):
        s = charn(val % ct_l, ct) + s
        val //= ct_l
    return s

Abstrakte Schnittstelle: MsgItem

Basis-Klasse für Nachrichtenkomponenten:

from abc import ABCMeta, abstractmethod
import typing

class MsgItem(metaclass=ABCMeta):
    __slots__ = ('val_str', 'val_int')

    def __init__(self, val: typing.Union[str, int]):
        if not self.validate(val):
            raise ValueError('Validierungsfehler')
        if isinstance(val, str):
            self.val_str = val.strip()
            self.val_int = self.to_int()
        elif isinstance(val, int):
            self.val_int = val
            self.val_str = self.to_str()
        else:
            raise TypeError(f'Nicht unterstützter Datentyp {type(val)}')

    @classmethod
    @abstractmethod
    def _validate_str(cls, val: str) -> bool:
        ...

    @classmethod
    @abstractmethod
    def _validate_int(cls, val: int) -> bool:
        ...

    @classmethod
    def validate(cls, val: typing.Union[str, int]) -> bool:
        if isinstance(val, str):
            return cls._validate_str(val)
        elif isinstance(val, int):
            return cls._validate_int(val)
        return False

    @abstractmethod
    def to_int(self) -> int:
        ...

    @abstractmethod
    def to_str(self) -> str:
        ...

    @property
    def as_str(self):
        return self.val_str

    @property
    def as_int(self):
        return self.val_int

Rufnamenkodierung

Struktur: sechs Positionen, jede mit einer eigenen Zeichentabelle verknüpft:

WSPR_BASECALL_CHAR_MAP = [
    WSPR_CHAR_TABLE_ALPHANUM_SPACE,
    WSPR_CHAR_TABLE_ALPHANUM,
    WSPR_CHAR_TABLE_NUMERIC,
    WSPR_CHAR_TABLE_LETTERS_SPACE,
    WSPR_CHAR_TABLE_LETTERS_SPACE,
    WSPR_CHAR_TABLE_LETTERS_SPACE
]

Klasse WSPRCallsign:

class WSPRCallsign(MsgItem):
    @classmethod
    def _validate_str(cls, val: str) -> bool:
        return len(val) <= 6

    @classmethod
    def _validate_int(cls, val: int) -> bool:
        return val < 262177560

    def to_int(self) -> int:
        return hash(self)

    def to_str(self) -> str:
        return ct_map_decode(WSPR_BASECALL_CHAR_MAP, self.val_int).strip()

    @staticmethod
    def _normalize_cs(cs: str) -> str:
        return ' ' * (6 - len(cs)) + cs

    def __hash__(self):
        cs_norm = self._normalize_cs(self.val_str)
        return ct_map_encode(WSPR_BASECALL_CHAR_MAP, cs_norm)

Beispiel: R9FEU260587010 (Binär: 0b1111100010000011111000000010).

QTH-Locator-Kodierung

Der Locator ist ein vierstelliger String (zwei Buchstaben + zwei Ziffern), der ein Gitterfeld von 180 × 180 km repräsentiert. Die Kodierung bildet diesen Wert auf ein globales geodätisches Raster ab.

Wesentliche Erkenntnisse:

  • WSPR erfasst Signale unterhalb der Rauschschwelle dank 4-FSK und robuster Fehlerkorrektur;
  • UTC-synchronisierte Zweiminuten-Slots vermeiden Kollisionen;
  • Aufteilung des 50-Bit-Nutzdatenpakets: Rufname (22 Bit), Locator (15 Bit), Leistung (7 Bit), Nachrichtentyp (1 Bit), CRC (4 Bit);
  • Gemeinsam bauen Beacons und Reporter eine Echtzeit-Karte der globalen Ausbreitungsbedingungen auf;
  • Eine Python-Implementierung ermöglicht lokales, offline-fähiges Testen und Validieren der Kodierlogik.

— Editorial Team

Advertisement 728x90

Weiterlesen