4. 消息1001 解析

任务说明

本案例实现RTCM 3.2协议中消息1001(L1-only GPS RTK观测)的二进制报文解析。输入为符合RTCM 3.2标准的1001消息字节流,输出为结构化字段集合,包含消息头、卫星数量、以及每颗卫星的L1伪距、载波相位、多普勒和信噪比观测值。解析结果可直接用于后续RTK定位解算。

消息1001格式定义

消息1001承载GPS L1频段的单频观测数据,不包含载波相位平滑伪距。其报文结构如下:

消息1001 位布局(共 0–N 位) 消息号 12位 参考站ID 12位 GPS历元 30位 同步 1位 卫星数 5位 平滑 1位 循环 每颗卫星观测数据(卫星数×64位) 见下方逐字段展开 单颗卫星字段(64位) PRN 6位 L1伪距 24位 L1相位 20位 锁定 7位 多普勒 8位 CNR 6位 半周期 1位 合计: 6+24+20+7+8+6+1 = 72位 比例尺:伪距 = 伪距×0.02 + 299792.458 km;相位 = 相位×0.0005 / (2π) 周 多普勒 = 多普勒×0.0001 Hz(有符号);CNR = CNR×0.25 dB-Hz 锁定时间 = 锁定×2^0.5 ms(近似);半周期标志:1=需翻转

代码实现


import struct

class RTCM1001Parser:
    """RTCM 3.2 消息1001解析器(L1-only GPS RTK观测)"""
    
    def __init__(self):
        self.msg_type = None
        self.station_id = None
        self.gps_epoch = None
        self.sync_flag = None
        self.sat_count = None
        self.smoothing = None
        self.satellites = []  # 每颗卫星的观测数据
    
    def _get_bits(self, data, start, length):
        """从字节数组提取位字段(大端序)"""
        if length == 0:
            return 0
        byte_start = start // 8
        bit_offset = start % 8
        # 读取足够字节
        needed = (bit_offset + length + 7) // 8
        chunk = data[byte_start:byte_start + needed]
        val = int.from_bytes(chunk, 'big')
        # 右移并掩码
        val >>= (8 * needed - bit_offset - length)
        return val & ((1 << length) - 1)
    
    def _sign_extend(self, value, bits):
        """有符号数扩展"""
        if value & (1 << (bits - 1)):
            return value - (1 << bits)
        return value
    
    def parse(self, payload: bytes) -> 'RTCM1001Parser':
        """
        解析1001消息负载(不含3字节前导和长度字段)
        payload: 消息体字节流(从消息号开始)
        """
        if len(payload) < 8:
            raise ValueError(f"负载过短: {len(payload)} 字节")
        
        # 消息头(前64位)
        self.msg_type = self._get_bits(payload, 0, 12)
        if self.msg_type != 1001:
            raise ValueError(f"消息类型错误: {self.msg_type}")
        
        self.station_id = self._get_bits(payload, 12, 12)
        self.gps_epoch = self._get_bits(payload, 24, 30)
        self.sync_flag = self._get_bits(payload, 54, 1)
        self.sat_count = self._get_bits(payload, 55, 5)
        self.smoothing = self._get_bits(payload, 60, 1)
        
        # 逐卫星解析(每颗卫星72位)
        self.satellites = []
        pos = 61  # 当前位偏移
        
        for _ in range(self.sat_count):
            if pos + 72 > len(payload) * 8:
                raise ValueError(f"数据不足: 需要{pos+72}位, 仅有{len(payload)*8}位")
            
            sat = {}
            sat['prn'] = self._get_bits(payload, pos, 6)
            pos += 6
            
            # L1伪距(24位无符号,单位0.02m,偏移299792.458m)
            raw_pseudo = self._get_bits(payload, pos, 24)
            sat['pseudo_range'] = raw_pseudo * 0.02 + 299792.458
            pos += 24
            
            # L1载波相位(20位有符号,单位0.0005/2π周)
            raw_phase = self._sign_extend(self._get_bits(payload, pos, 20), 20)
            sat['carrier_phase'] = raw_phase * 0.0005 / (2 * 3.141592653589793)
            pos += 20
            
            # 锁定时间(7位)
            sat['lock_time'] = self._get_bits(payload, pos, 7)
            pos += 7
            
            # 多普勒(8位有符号,单位0.0001Hz)
            raw_doppler = self._sign_extend(self._get_bits(payload, pos, 8), 8)
            sat['doppler'] = raw_doppler * 0.0001
            pos += 8
            
            # CNR(6位,单位0.25dB-Hz)
            sat['cnr'] = self._get_bits(payload, pos, 6) * 0.25
            pos += 6
            
            # 半周期标志(1位)
            sat['half_cycle'] = self._get_bits(payload, pos, 1)
            pos += 1
            
            self.satellites.append(sat)
        
        return self
    
    def summary(self) -> str:
        """生成解析摘要"""
        lines = [
            f"消息类型: {self.msg_type}",
            f"参考站ID: {self.station_id}",
            f"GPS历元: {self.gps_epoch}",
            f"同步标志: {self.sync_flag}",
            f"卫星数量: {self.sat_count}",
            f"平滑标志: {self.smoothing}",
            "--- 卫星观测 ---"
        ]
        for i, sat in enumerate(self.satellites):
            lines.append(
                f"卫星{i+1}: PRN={sat['prn']:2d} | "
                f"伪距={sat['pseudo_range']:.3f} m | "
                f"相位={sat['carrier_phase']:.6f} 周 | "
                f"多普勒={sat['doppler']:.4f} Hz | "
                f"CNR={sat['cnr']:.2f} dB-Hz | "
                f"锁定={sat['lock_time']} | "
                f"半周期={sat['half_cycle']}"
            )
        return "\n".join(lines)

# ===== 使用示例 =====
if __name__ == "__main__":
    # 构造一条包含2颗卫星的1001消息负载(十六进制)
    # 消息头: 类型1001(0x3E9), 站ID=1, 历元=100, 同步=0, 卫星数=2, 平滑=0
    # 卫星1: PRN=5, 伪距原始值=10000, 相位原始值=2000, 锁定=10, 多普勒=-5, CNR=40, 半周期=0
    # 卫星2: PRN=12, 伪距原始值=10050, 相位原始值=-1500, 锁定=3, 多普勒=8, CNR=35, 半周期=1
    
    # 手动构造位流(简化:使用struct打包)
    # 消息头64位 = 8字节
    header = 0
    header |= 1001 << 52   # 12位消息号
    header |= 1 << 40       # 12位站ID
    header |= 100 << 10     # 30位历元
    header |= 0 << 9        # 1位同步
    header |= 2 << 4        # 5位卫星数
    header |= 0 << 3        # 1位平滑(剩余3位填充0)
    
    payload = bytearray(header.to_bytes(8, 'big'))
    
    # 卫星1数据(72位 = 9字节)
    sat1 = 0
    sat1 |= 5 << 66         # PRN 6位
    sat1 |= 10000 << 42     # 伪距 24位
    sat1 |= (2000 & 0xFFFFF) << 22  # 相位 20位(正数)
    sat1 |= 10 << 15        # 锁定 7位
    sat1 |= ((-5) & 0xFF) << 7  # 多普勒 8位(有符号)
    sat1 |= 40 << 1         # CNR 6位
    sat1 |= 0                # 半周期 1位
    
    payload.extend(sat1.to_bytes(9, 'big'))
    
    # 卫星2数据
    sat2 = 0
    sat2 |= 12 << 66        # PRN
    sat2 |= 10050 << 42     # 伪距
    sat2 |= ((-1500) & 0xFFFFF) << 22  # 相位(负数补码)
    sat2 |= 3 << 15         # 锁定
    sat2 |= (8 & 0xFF) << 7 # 多普勒
    sat2 |= 35 << 1         # CNR
    sat2 |= 1                # 半周期
    
    payload.extend(sat2.to_bytes(9, 'big'))
    
    # 解析
    parser = RTCM1001Parser()
    parser.parse(bytes(payload))
    print(parser.summary())

验证方法

运行上述代码,观察输出。预期输出应包含:消息类型1001、参考站ID=1、GPS历元=100、卫星数量=2。卫星1的伪距应为 10000×0.02 + 299792.458 = 299992.458 m,相位为 2000×0.0005/(2π) ≈ 0.159155 周,多普勒为 -5×0.0001 = -0.0005 Hz,CNR为 40×0.25 = 10.00 dB-Hz。卫星2的伪距应为 10050×0.02 + 299792.458 = 299993.458 m,相位为 -1500×0.0005/(2π) ≈ -0.119366 周,多普勒为 0.0008 Hz,CNR为 8.75 dB-Hz,半周期标志为1。若输出与上述计算一致,则解析逻辑正确。