홈으로 돌아가기

WSPR 프로토콜: Python을 사용한 콜사인 인코딩

WSPR은 분산된 비컨 네트워크를 사용하여 약한 HF 신호의 전파를 분석하는 프로토콜입니다. 구조 설명: 4-FSK, 50비트 메시지, 콜사인 및 로케이터 코덱의 Python 구현. MsgItem 및 WSPRCallsign 클래스 예제.

Python에서의 WSPR: 전리층 비컨 인코딩
Advertisement 728x90

파이썬을 활용한 WSPR 프로토콜 구현: 인코딩 예제

WSPR 프로토콜은 잡음보다 낮은 수준에서 데이터를 전송하기 위해 4-FSK 변조 방식을 사용합니다. 각 전송 주기는 110.59초이며, 대역폭은 5.85Hz로 제한되며, 컨볼루션 코딩(비율 ½)을 통해 메시지를 50비트로 압축합니다. 송신기는 매 짝수 분의 시작 시점(±1초 이내)에 자동으로 활성화되며, 200Hz 폭의 주파수 대역 내에서 무작위로 주파수를 선택합니다.

전송 데이터에는 송신국의 콜사인, 4자리 QTH 로케이터, 그리고 dBm 단위의 송신 출력이 포함됩니다. 수신기는 신호를 복호화하고 보고서를 생성한 후, 전송 슬롯의 남은 10초 동안 wsprnet.org에 업로드합니다.

고주파(HF) 전파 전달 원리

HF 대역(3–30MHz)은 전리층을 파동 안내관(waveguide)처럼 활용합니다. 신호는 고도 140–1000km에서 굴절되며, 일부는 우주 공간으로 빠져나가고, 나머지는 지구로 반사됩니다. 여러 차례 ‘홉(hop)’을 거치면 전 세계적으로 커버할 수 있습니다.

Google AdInline article slot

전리층 상태는 태양 활동, 시간대, 계절에 따라 달라집니다:

  • 80m 대역: 야간 통신에 최적;
  • 40m 대역: 야간에는 장거리, 주간에는 단거리 통신 가능;
  • 20m 대역: 주간에 안정적인 장거리 통신 지원;
  • 10m 대역: 태양 활동 극대기 근처에서만 개방.

핵심 포인트: 반사 각도가 가파르기 때문에 직접적인 단거리 경로는 실용성이 낮습니다.

프로토콜 계층 구조

WSPR은 처리 과정을 세 가지 계층으로 구분합니다:

Google AdInline article slot
  • 애플리케이션 계층: 콜사인, 로케이터, 출력 전력;
  • 프레젠테이션 계층: 사전 정의된 문자 테이블을 기반으로 50비트로 패킹;
  • 링크 계층: 동기화 및 오류 정정 기능을 갖춘 4-FSK 변조.

모든 전송은 UTC 기준으로 정확히 동기화되며, 비콘은 시간당 최대 7개의 슬롯에서 의사무작위로 작동합니다.

프레젠테이션 계층: 문자 테이블과 코덱

데이터는 사전 정의된 테이블의 인덱스로 인코딩됩니다:

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} '

변환 보조 함수:

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

추상 인터페이스: MsgItem

메시지 구성 요소의 기본 클래스:

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('유효성 검사 오류')
        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'지원되지 않는 데이터 유형: {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

콜사인 인코딩

구조: 6자리 위치로 구성되며, 각 위치는 서로 다른 알파벳 집합에 매핑됩니다:

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
]

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)

예시: R9FEU260587010 (2진수: 0b1111100010000011111000000010).

QTH 로케이터 인코딩

로케이터는 4자리 문자열(2문자 + 2숫자)로, 180×180km 크기의 격자 사각형을 나타냅니다. 이 값은 전 지구적 지오데틱 격자에 매핑되어 인코딩됩니다.

핵심 요약:

  • WSPR은 4-FSK와 강력한 오류 정정 덕분에 잡음보다 낮은 신호도 탐지 가능;
  • UTC 기준 2분 간격의 전송 슬롯으로 충돌을 방지;
  • 50비트 페이로드 구성: 콜사인(22비트), 로케이터(15비트), 출력 전력(7비트), 메시지 유형(1비트), CRC(4비트);
  • 비콘과 리포터가 협업해 실시간 전 지구 전파 상태 지도를 구축;
  • 파이썬 구현을 통해 로컬 환경에서 오프라인 인코딩 로직 테스트 및 검증이 가능.

— Editorial Team

Advertisement 728x90

다음 읽기