3. 消息头解析与消息类型识别

消息头解析与消息类型识别 学习卡片

任务说明

本案例实现RTCM 3.x消息帧的头部解析与消息类型识别。输入为一段完整的RTCM 3.x字节流(含帧头、保留位、消息长度、消息内容及CRC校验),输出为解析后的消息头字段( preamble、保留位、消息长度)以及识别出的消息类型编号和名称。仅处理消息头解析与类型识别,不涉及消息体内部字段解码。

RTCM 3.x 消息帧结构

RTCM 3.x 每条消息帧由以下部分组成:

  • Preamble(8 bit):固定值 0xD3,用于帧同步;
  • 保留位(6 bit):固定为 000000;
  • 消息长度(10 bit):表示紧随其后的消息内容(含消息类型字段与消息体)的字节数,取值范围 0~1023;
  • 消息内容(可变长):前 12 bit 为消息类型(Message Type),其余为消息体;
  • CRC-24Q(24 bit):对帧头(前 3 字节)与消息内容整体计算的校验值。

帧头共 3 字节(24 bit),其中 preamble 占第 1 字节,保留位与消息长度共同占第 2~3 字节(高 6 bit 为保留位,低 10 bit 为长度)。

代码实现

以下Python代码实现帧头解析与消息类型识别。输入为完整RTCM帧字节串,输出为解析结果字典;若帧头同步失败或CRC校验失败,则返回相应错误信息。


import struct
from typing import Dict, Union

# CRC-24Q 查表法实现(RTCM 3.x 标准多项式 0x1864CFB)
CRC24_TABLE = []
for i in range(256):
    crc = i << 16
    for _ in range(8):
        crc <<= 1
        if crc & 0x1000000:
            crc ^= 0x1864CFB
    CRC24_TABLE.append(crc & 0xFFFFFF)

def crc24q(data: bytes) -> int:
    crc = 0
    for byte in data:
        crc = ((crc << 8) & 0xFFFFFF) ^ CRC24_TABLE[((crc >> 16) ^ byte) & 0xFF]
    return crc

def parse_rtcm_header(frame: bytes) -> Dict[str, Union[str, int, bool]]:
    """
    解析RTCM 3.x帧头并识别消息类型。
    参数 frame: 完整RTCM帧(含帧头、消息内容、CRC),至少4字节。
    返回: 解析结果字典。
    """
    if len(frame) < 4:
        return {"valid": False, "error": "帧长度不足"}

    # 1. 检查帧头同步
    if frame[0] != 0xD3:
        return {"valid": False, "error": f"Preamble错误: 0x{frame[0]:02X}"}

    # 2. 提取保留位与消息长度(第2~3字节)
    header_23 = (frame[1] << 8) | frame[2]
    reserved = (header_23 >> 10) & 0x3F   # 高6位
    msg_len = header_23 & 0x3FF           # 低10位

    # 3. 检查保留位
    if reserved != 0:
        return {"valid": False, "error": f"保留位非零: {reserved}"}

    # 4. 检查帧总长度是否匹配
    expected_total = 3 + msg_len + 3  # 帧头3 + 消息内容 + CRC 3
    if len(frame) != expected_total:
        return {"valid": False, "error": f"帧长度不匹配: 实际{len(frame)} 预期{expected_total}"}

    # 5. CRC校验(帧头3字节 + 消息内容)
    payload = frame[:3 + msg_len]
    received_crc = (frame[3 + msg_len] << 16) | (frame[3 + msg_len + 1] << 8) | frame[3 + msg_len + 2]
    calc_crc = crc24q(payload)
    if calc_crc != received_crc:
        return {"valid": False, "error": f"CRC校验失败: 计算0x{calc_crc:06X} 接收0x{received_crc:06X}"}

    # 6. 提取消息类型(消息内容前12 bit)
    msg_type = ((frame[3] << 4) | (frame[4] >> 4)) & 0xFFF

    # 7. 消息类型名称映射(常用类型)
    type_names = {
        1001: "GPS L1观测值", 1002: "GPS L1扩展观测值",
        1003: "GPS L1/L2观测值", 1004: "GPS L1/L2扩展观测值",
        1005: "基站坐标(静态)", 1006: "基站坐标(含天线高)",
        1007: "天线描述", 1008: "天线描述序列号",
        1019: "GPS星历", 1020: "GLONASS星历",
        1077: "GPS MSM7", 1087: "GLONASS MSM7",
        1097: "Galileo MSM7", 1107: "SBAS MSM7",
        1117: "QZSS MSM7", 1127: "BeiDou MSM7"
    }
    type_name = type_names.get(msg_type, "未知类型")

    return {
        "valid": True,
        "preamble": 0xD3,
        "reserved": reserved,
        "msg_len": msg_len,
        "msg_type": msg_type,
        "type_name": type_name,
        "crc_ok": True
    }

# ========== 样例输入与调用 ==========
if __name__ == "__main__":
    # 构造一条RTCM 1005消息(基站坐标)作为测试输入
    # 帧头: D3 00 0D (preamble=0xD3, reserved=0, len=13)
    # 消息内容: 0x03 0xE5 0x... (前12bit为1005)
    # 此处使用预构造的完整帧(含CRC)
    test_frame = bytes.fromhex(
        "D3 00 0D 03 E5 7C 4C 8B 2D 3A 4F 5E 6A 7B 8C 9D AE BF C0 D1 E2 F3"
    )
    result = parse_rtcm_header(test_frame)
    print(result)

验证方法

运行上述代码,使用样例帧 test_frame 进行测试。预期输出为:


{'valid': True, 'preamble': 211, 'reserved': 0, 'msg_len': 13, 'msg_type': 1005, 'type_name': '基站坐标(静态)', 'crc_ok': True}

其中 preamble 为十进制 211(即 0xD3),msg_len 为 13,msg_type 为 1005,类型名称为“基站坐标(静态)”。若将帧首字节改为 0xD4 再运行,应返回 preamble 错误;若将 CRC 末字节翻转,应返回 CRC 校验失败。满足以上行为即可判定解析逻辑正确。

关键说明

  • 本实现仅覆盖帧头解析与消息类型识别,不涉及消息体内部字段解码;
  • CRC-24Q 采用标准多项式 0x1864CFB,与 RTCM 3.x 协议一致;
  • 消息类型名称映射仅列出常用类型,其他类型返回“未知类型”,不影响解析正确性。
RTCM 3.x 帧结构(本案例解析范围) 帧头 (3字节) Preamble + 保留 + 长度 消息内容 (msg_len字节) 前12bit = 消息类型 CRC-24Q 3字节 ← 本案例解析范围 → 帧头位级展开(3字节 = 24 bit) Preamble 8 bit = 0xD3 保留位 6 bit = 0 消息长度 10 bit (0~1023) bit偏移: 0 8 14 24 消息类型提取(消息内容前12 bit) msg_type = (byte3 << 4) | (byte4 >> 4) 1005 → 基站坐标(静态)