标签: u-blox

  • u-blox 初始化「看似成功」:ACK 被丢弃、False 也继续接收

    结论:u-blox 初始化链路里有几处会导致「看起来配置成功、实际没有生效」的缺口:send_with_ack() 调用 wait_for_ack() 却丢弃了返回的布尔值,因此收到 NACK 时调用者仍会继续下一条配置;init_pigeon() 十轮全部超时后返回 False,而 init() 不检查这个返回值,仍进入接收循环;wait_for_ack() 的默认前缀只比对 UBX 头的前 6 字节,不核对目标 CFG class/id 或校验和。串口以 9600 波特率打开,切换 460800 的那步不等待 ACK。以下为固定快照 c8fb906 的静态研究,未访问串口、未驱动 GPIO、未下载辅助数据、未执行清备份的复位。

    启动与串口所有权

    process_config.py 把 pigeond 配置为 u-blox 路径的受管 Python 进程,它提供原始串口流,ubloxd 再解析定位。TTYPigeon.__init__() 在设备上的 GNSS 串口以 9600 波特率打开共享的 Serial 封装,读超时为 0;由于未使用该封装的 exclusive=True 选项,串口独占并不由此保证。

    receive() 以 64 字节块累计到本次最多约 4096 字节,遇到空读就返回,输出的是任意字节块、不是完整 UBX 帧,分帧由 ubloxd 完成。init() 注册 SIGINT 清理、关闭再打开 GNSS 电源并等待,然后 init_baudrate() 在 9600 发送 $PUBX,41 并把主机端切到 460800——这一步不等待协议 ACK,若模组未完成切速,后续 init_pigeon() 的失败会表现为 ACK 超时。

    阶段 源码可见行为 边界
    串口打开 9600 波特、读超时 0、共享 Serial 未用 exclusive=True,独占无保证。
    原始读取 64 字节块累计到约 4096 字节 输出任意字节块,不是完整 UBX 帧。
    电源控制 set_power() 驱动 SAFEBOOT_N / GNSS_PWR_EN / RST_N gpio_set() 写异常被捕获并打印,调用方不立即收到异常。
    切速 9600 发 $PUBX,41,主机切 460800 不等 ACK;未完成切速表现为后续超时。
    配置 init_pigeon() 最多十轮 CFG 端口/速率/导航/里程计与六种 UBX 输出 定长配置包是当前模组固件契约,不可移植到另一模组。

    ACK 状态与失败传播

    wait_for_ack() 在累计的串口字节里先查 ACK 前缀再查 NACK 前缀:ACK 返回 True,NACK 返回 False,0.5 s 超时抛 TimeoutError。它的默认前缀只含 UBX 头的前 6 字节,没有核对目标 CFG class/id、长度后续字段或校验和——多个应答或异步导航消息交错时,不能把「发现某个 ACK 前缀」当成指定配置已生效的证明。

    函数 可见行为 插件边界
    wait_for_ack() 先查 ACK 前缀,再查 NACK 前缀;ACK 返回 True、NACK 返回 False、0.5 s 超时抛错 默认前缀只含头 6 字节,未核对 class/id 与校验和。
    send_with_ack() 发命令后调用 wait_for_ack(),但丢弃返回的布尔值 NACK 不抛异常,调用者继续下一条配置。
    init_pigeon() 十轮 try 仅捕获 TimeoutError 部分 NACK 不触发本轮重试;全超时返回 False。
    init() 不检查 init_pigeon() 的 False 仍进入接收循环,需要显式设备就绪状态与错误码。
    save_almanac() 直接检查特定 SOS ACK/NACK 的布尔值,超时吞掉 与 CFG 的 send_with_ack() 失败语义不同。

    停止路径也有边界:常规停止由 manager 默认发 SIGINT,deinitialize_and_exit() 先发受控停机命令再关闭 GPIO 电源;若五秒内未退出,manager 可转 SIGKILL,清理处理器未必完成。现有 test_pigeond.py 覆盖原始消息频率、启动后首消息与停止后供电引脚,但本轮未运行,也不能证明每条 CFG 都被正确 ACK 或辅助数据全量装载。

    原始数据以 ubloxRaw 发布,供 UbxFramer.add_data() 处理。这一层只表明串口有字节,不能单独证明配置成功、定位已 fix 或时间可信;UBX 分帧与星历缓存属于另一条链,系统校时门又是第三条链。

    辅助定位与备份

    get_assistnow_messages() 优先用 AssistNowToken 直连 u-blox,退而用 DongleId 请求代理;HTTP 设置 5 秒超时并检查状态码。随后按 UBX 的两字节长度切分响应,但只断言当前片段以 B5 62 开始,不校验每片长度是否足够、也不校验 CK_A/CK_B;短或损坏的响应可能在索引或发送时失败,因此 HTTP 成功不等于辅助数据有效。

    run_receiving() 的后台线程等待系统时间有效且网络类型非 none,每 10 秒重试下载直到 assist_messages is not None——注意空列表也会结束下载重试。主线程看到列表后先置 assist_attempted=True,再逐条 send_with_ack();空列表会直接走到「sent」日志。若发送中抛异常,只记录「failed」且不在同一次运行中重试;若收到 NACK,由于 send_with_ack() 忽略 False,仍可能记录「sent」。只有收到首字节为 0x00 的串口块时才会重新 init() 并把 assist_attempted 清零,才有再次发送机会。

    自制硬件与插件验收

    平台适配器应独占 GNSS 的供电/复位/UART、配置版本、ACK class/id 关联、重试预算与断电顺序,并提供分层可观测状态:power_on、uart_ready、cfg_acked、raw_stream_alive、has_fix、time_trusted。辅助定位插件只产出经 UBX 长度与校验验证的消息,不直接操作供电或修改系统时间。台架需逐项注入 NACK、错命令 ACK、ACK 与数据交错、9600→460800 切速失败、下载损坏、发送中断、无串口字节与 SIGKILL,再验证真实天线、TTFF、UTC/PPS 与电源波形;本轮这些均未完成。

    常见问题

    收到 NACK 会阻止后续配置下发吗?

    不会。send_with_ack() 在发出命令后调用 wait_for_ack(),但丢弃了返回的布尔值,因此 NACK 不抛异常,调用者会继续下一条配置;这是源码可直接判定的控制流,不是模组实测。

    init_pigeon() 全部超时返回 False 后会怎样?

    init() 不检查这个 False,仍会进入接收循环。也就是说初始化失败不会挡住接收路径,插件需要显式的设备就绪状态与错误码。

    wait_for_ack() 会校验是目标配置的应答吗?

    不会。它的默认前缀只比对 UBX 头的前 6 字节,没有核对目标 CFG class/id、长度后续字段或校验和,因此在多个应答交错时不能把「发现某个 ACK 前缀」当成指定配置已生效。

    AssistNow 下载返回 HTTP 成功,辅助数据就一定有效吗?

    不一定。切分响应时只断言片段以 B5 62 开始,不校验长度是否足够、也不校验 CK_A/CK_B;短或损坏响应可能在索引或发送时失败。再加上空列表也会结束下载重试,HTTP 成功不等于辅助数据有效。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/GNSS_UBLOX_DEVICE_LIFECYCLE_DEEP_DIVE.md(取用了启动与串口所有权、ACK 状态与失败传播表、AssistNow 与备份边界、以及插件验收分层状态要求)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • GNSS 双路径与系统时间:u-blox 与 Quectel 的选择与边界

    结论:GNSS 在这套系统里是二选一的双路径:存在指定串口设备节点且未设置强制 Quectel 开关时选 u-blox,由 pigeond 与 ubloxd 同时运行,其余情况选 qcomgpsd;两条路径输出的定位主题名不同——u-blox 走 gpsLocationExternal,Quectel 走 gpsLocation,系统时间进程按参数选其中一条,不能只改名字就视为等价。时间校正本身条件严格:所选主题刚更新、logMonoTime 距当前不超过两秒、hasFix=True 且 GPS 时间在允许范围内才调用设置时间,且差值小于十秒不改。本文为固定源码快照 c8fb906 的静态阅读,未连接 u-blox、Quectel、天线或实板。

    平台门与消息契约

    平台选择由设备节点与持久化参数共同决定:存在指定串口设备节点且未设置强制 Quectel 开关时选 u-blox,此时 pigeond 和 ubloxd 同时运行,ublox() 还更新 UbloxAvailable 参数;其他情况下选 qcomgpsd。三者均受硬件与行车条件限制,设备节点判断不是芯片健康检测。需要注意的是,管理器会按当前设备节点重评 GNSS 生产者,但系统时间进程常驻且只在启动时选订阅主题,因此驻车与行车切换、运行中设备切换并不等价。

    边界 已核对行为
    平台门 串口设备节点存在且未设强制 Quectel 开关时选 u-blox,否则选 qcomgpsd
    u-blox 定位主题 输出 gpsLocationExternal
    Quectel 定位主题 输出 gpsLocation
    原始/卫星主题 ubloxRaw 是串口字节包;ubloxGnss 与 qcomGnss 承载测量、星历、卫星状态与诊断
    订阅选择 系统时间进程按 UbloxAvailable 选一条,且只在初始化时决定

    ubloxRaw 是串口字节包,ubloxGnss 与 qcomGnss 承载测量、星历、卫星状态与诊断等结构,它们都不是 GpsLocationData 的替代品。两条服务的频率与字段来源不同,接入时不能假定可互换。

    对比项 u-blox 路径 Quectel 路径
    运行进程 pigeond 与 ubloxd 同时运行 qcomgpsd
    物理链路 电源、复位、安全启动引脚与串口设备节点 AT 端口与 DIAG 串口,共享互斥锁文件
    帧格式与校验 UBX 前导 B5 62、长度与 Fletcher 校验 HDLC 解转义与 CRC16-X25
    定位主题 gpsLocationExternal gpsLocation
    时间来源 NAV-PVT 的 UTC,异常时置零 固定 18 秒 GPS-UTC 差,留有闰秒待办

    u-blox:供电、串口、UBX 与辅助定位

    set_power() 与串口封装类管理电源使能、复位、安全启动引脚与串口设备节点。初始化先做电源循环,再由 init_baudrate() 把默认 9600 波特率切到 460800;init_pigeon() 用带 ACK 的配置报文设置输出频率、消息种类与导航参数,最多尝试十轮。run_receiving() 把串口块发布为 ubloxRaw,首字节为 0x00 时尝试重新初始化;后台线程等待有效系统时间与网络后,从 u-blox 服务或代理下载辅助数据并送设备,接收过程中每五分钟尝试保存星历备份。

    UbxFramer.add_data() 从跨块字节流寻找 UBX 前导 B5 62、长度与 Fletcher 校验,坏帧丢首字节后重找;解析器parse_frame() 只处理 NAV-PVT、RXM-SFRBX、RXM-RAWX、NAV-SAT、MON-HW/HW2 等已列类型,其余返回空。NAV-PVT 映射把经纬度、海拔、速度、NED 速度和精度换算到 gpsLocationExternal,根据 flags 置 hasFix,UTC 日期异常时 Unix 时间置零。

    有两个容易误判的静态边界:其一,init() 调用 init_pigeon() 却不检查其布尔返回,十轮失败后仍进入接收循环,因此应显式上报电源、UART、ACK、首定位与持续断流状态;其二,主循环记录上游包时间,却并未把它写回发布消息的 logMonoTime——后者由消息构造在解析时生成,所以日志时间不能代表卫星测量时刻,硬件替换时必须单独保留采样时刻与处理延迟。

    Quectel:AT 与 DIAG 路径

    wait_for_modem() 等 AT 端口及 GNSS 子系统应答;at_cmd() 在一个共享内存互斥锁文件上加锁,与蜂窝管理进程共享 AT 端口——这个锁是接口所有权边界,新板卡驱动不能绕过它并发发 AT 指令。setup_quectel() 通过 DIAG/NV 开启 OEMDRE 日志、关闭 GNSS 省电模式、设置 NMEA 输出与时间并启动 GNSS,cleanup() 在信号退出时关闭 GNSS 与日志并拉低电源引脚。

    ModemDiag.hdlc_decapsulate() 对 DIAG 串口数据解转义并校验 CRC16-X25;主循环再按内外长度、日志类型和版本解析 OEMDRE、GPS 与 GLONASS 测量及位置,位置报告要求位置来源字段等于 2 且 GPS 周不是无效哨兵,海拔精度等于 500 时置 hasFix=False。位置映射把 ENU 速度转换到 NED 输出 gpsLocation,源码按固定 18 秒 GPS-UTC 差计算 Unix 时间,并留有闰秒更新待办;若把该路径作为设备时间源,必须验证当前闰秒、模组固件与真实 UTC 的一致性。

    系统时间与硬件契约

    系统时间进程发布 clocks,仅在所选 GPS 主题刚更新、logMonoTime 距当前不超过两秒、hasFix=True 且 GPS 时间在允许范围内时调用设置时间,差值小于十秒不改系统时间;该流程只处理系统 UTC,不保证相机帧起始、IMU 中断、DIAG 日志时间与卫星测量已经共时钟。面向硬件与插件化的最低契约是:每条 GNSS 提供方须声明物理链路、供电与复位、安全关机、协议及校验、观测时钟域、UTC 与闰秒来源、定位有效位与精度、首次定位及断流超时;gpsLocationExternal 与 gpsLocation 只能通过受控适配器切换,不能让任意插件覆盖系统时间或伪造定位有效位。

    常见问题

    系统怎么决定用 u-blox 还是 Quectel?

    由设备节点与持久化参数共同决定:存在指定串口设备节点且未设置强制 Quectel 开关时选 u-blox,由 pigeond 与 ubloxd 同时运行并更新 UbloxAvailable;其他情况选 qcomgpsd。设备节点判断只是平台门,不是芯片健康检测。

    gpsLocationExternal 和 gpsLocation 可以互换吗?

    不能只改名互换。前者由 u-blox 的 NAV-PVT 映射产出,后者由 Quectel 的 DIAG 位置映射产出,两条服务频率与字段来源不同;系统时间进程按 UbloxAvailable 选其中一条,且只在初始化时决定,平台切换后要检查服务重启与参数同步。

    日志里的时间等于卫星测量时刻吗?

    不一定。u-blox 主循环并未把上游包时间写回发布消息的 logMonoTime,该字段由消息构造在解析时生成,因此日志时间不能代表卫星测量时刻,硬件替换时必须单独保留采样时刻与处理延迟。

    为什么 hasFix=False 会出现?

    两条路径都有置假条件:u-blox 根据 NAV-PVT 的 flags 置 hasFix 并在 UTC 日期异常时把 Unix 时间置零;Quectel 要求位置来源字段等于 2 且 GPS 周有效,海拔精度等于 500 时置 hasFix=False。因此有效位要连同来源与时间一起解读。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/GNSS_TIME_DEEP_DIVE.md(取用了平台门、两条定位主题、u-blox 与 Quectel 解析链、系统时间条件与硬件契约)。
    • 项目 02 —— openpilot-research/GNSS_BACKEND_SELECTION_SUBSCRIPTION_LIFECYCLE_DEEP_DIVE.md(仅用于确认订阅选择生命周期与运行中切换的边界)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11