返回首页

WSPR 协议:Python 对呼号的编码

WSPR 是一种使用分布式信标网络分析弱 HF 信号传播的协议。结构描述:4-FSK、50 位消息、Python 实现呼号和定位器编解码器。MsgItem 和 WSPRCallsign 类的示例。

Python 上的 WSPR:电离层信标的编码
Advertisement 728x90

Python 实现 WSPR 协议:编码示例详解

WSPR 协议采用 4-FSK 调制方式,在噪声基底以下传输数据。每次发射周期为 110.59 秒,仅占用 5.85 Hz 带宽,并通过卷积编码(码率 ½)将完整消息压缩为 50 比特。发射机在每个偶数分钟整点(±1 秒内)启动,并在指定的 200 Hz 频带内随机选择发射频率。

消息内容包括电台呼号、4 字符 QTH 定位网格(如 PM96)、以及以 dBm 为单位的发射功率。接收端完成解码后生成报告,并在每轮发射周期剩余的 10 秒内上传至 [wsprnet.org](https://wsprnet.org)。

短波传播的物理原理

短波(HF)频段(3–30 MHz)依赖电离层作为天然波导。信号在距地表 140–1000 公里高度发生折射:部分能量逸散至太空,其余则反射回地面。经多次“跳跃”(multi-hop),可实现全球覆盖。

Google AdInline article slot

电离层状态受太阳活动强度、昼夜及季节影响显著:

  • 80 米波段:夜间通信效果最佳;
  • 40 米波段:夜间远距离稳定,白天仅适合短距通信;
  • 20 米波段:日间远距离通信可靠性高;
  • 10 米波段:仅在太阳活动峰值期开通。

关键细节:因反射角过陡,极短距离的直射路径往往难以实现。

协议分层架构

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(二进制:0b1111100010000011111000000010)。

QTH 定位网格编码

定位网格为 4 字符字符串(2 字母 + 2 数字),对应一个 180×180 公里的地理网格单元。其编码直接映射至全球大地坐标系。

核心要点:

  • WSPR 凭借 4-FSK 调制与强纠错能力,可检测远低于噪声基底的微弱信号;
  • 基于 UTC 的 2 分钟同步时隙机制,彻底避免发射冲突;
  • 50 比特有效载荷分配:呼号(22 比特)、定位网格(15 比特)、发射功率(7 比特)、消息类型(1 比特)、CRC 校验(4 比特);
  • 信标台与接收报告系统协同构建实时全球传播图谱;
  • Python 实现支持本地离线测试与编码逻辑验证,大幅降低开发门槛。

— Editorial Team

Advertisement 728x90

继续阅读