标签: 系统时间

  • 系统时间自举:日期范围门、GNSS 校时与信任边界

    结论:openpilot 的系统时间自举建立在几个纯日期范围检查上:Python 侧的 system_time_valid() 用本地墙钟在固定下界 2025-02-21 与固定上界 2035-01-01 之间做严格开区间比较,若找到 systemd 可执行文件,还会把它的修改时间加一天并与下界取较大值。「有效」只表示当前日期落在区间内,不等于可信 UTC、证书有效或各传感器时钟已对齐:一台时钟在区间内但快慢数天的设备同样能通过。本文为固定源码快照 c8fb906 的静态阅读与有限隔离探针,未修改系统时钟、未连接 GNSS 或访问网络。

    公共日期门:范围检查而非可信时间

    MIN_DATE 与 MAX_DATE 分别为 2025-02-21 和 2035-01-01。min_date() 若发现 systemd 可执行文件,会把该文件的修改时间按本地时间加一天,再与固定下界取较大值;system_time_valid() 用本地 datetime.now() 做严格开区间比较。它没有读 NTP 同步状态、GNSS 精度、硬件 RTC 来源或 TLS 证书有效期。

    一个结构性边界是:若该文件的修改时间异常地进入未来,动态下界可能高于上界,导致整个有效区间为空。四项假路径探针确认了这一族行为:没有该文件或修改时间早于固定下界时仍用 2025-02-21;较新的修改时间会把下界抬到其本地日期加一天;恰好等于动态下界或 2035-01-01 上界均判无效;修改时间晚于上界可使整个有效区间为空。

    原生 C++ 侧还有一条不同的判断:它的固定下界是 2024-08-26,也考虑该文件修改时间加一天,但没有 2035 上界,安装器依赖这一路径。两端不能被当作同一个时间状态。

    项目 Python 侧 C++ 侧
    固定下界 2025-02-21 2024-08-26
    固定上界 2035-01-01 无
    动态下界 systemd 文件修改时间的本地时间加一天,与固定下界取大 同样考虑该文件修改时间加一天
    加一天的算术 先转本地无时区时间,再加一个日历日 直接对 epoch 秒加 86400,即固定 24 小时

    两端的「加一天」也不完全等价:在使用夏令时的本地时区且这一天跨越切换点时,两个下界可差一小时。离线以 America/New_York 的 2026-03-07 12:00 为修改时间,Python 得到次日 12:00,固定秒数得到次日 13:00;2026-10-31 12:00 则分别得到次日 12:00 与 11:00。这只验证时区算术差异,目标设备是否配置夏令时尚未验证。

    timed 的 GNSS 校时链

    定位服务按启动时的一个可用性参数在外部与内部定位主题之间选一个,timed.main() 创建一次订阅,不在主循环中动态切换。每轮先把当前墙钟写入时钟消息,并用上述范围判断设置外层有效位。只有所选主题本轮更新、消息单调时间距当前不超过两秒、定位有效且 GNSS 时间落在下界到上界之间,才调用校时函数;此路径没有检查所选定位消息的顶层有效位。偏差不足十秒直接跳过,更大的偏差通过系统命令设置,失败只记录异常。

    六项假消息与假命令探针补充了执行顺序证据:时钟发布发生在 GNSS 门控之前,即使本地墙钟无效也可用新鲜定位尝试校时;所选定位消息的外层无效不阻断调用。定位时间戳在检查「本轮更新、新鲜度、定位有效」之前即被转成日期时间,因此假消息即使标为未更新,超出可表示范围的时间戳仍会先抛异常,使本轮不进入其余门控。此外主循环在校时函数正常返回后无条件睡十秒,未根据实际校时成功与否区分退避。

    环节 实现事实 边界
    订阅选择 按启动时可用性参数在两条定位主题中选一条 主循环不动态切换
    新鲜度门 消息距当前单调时间不超过两秒 需定位有效且时间在范围内
    校时调用 偏差超过十秒才设置系统时间 不检查定位消息顶层有效位
    异常处理 设置失败只记录异常 主循环仍无条件睡十秒

    启动自举与下游行为

    时间门被多个网络与硬件路径复用:GNSS 相关服务仅在时间「有效」时给模组发送辅助时间,并在网络可用时才取辅助数据。另一方面 timed 又依赖 GNSS 首次有效定位来校准系统时间,所以自制硬件必须设计 RTC、NTP 或独立定位的冷启动路径,不能假设辅助时间与 GNSS 校时会自动互相解锁。

    下游同样依赖这个门:eSIM 的配置下载函数先检查系统时间范围,通知上传也有同一门;更新服务在时间无效时等待再重试;UI 配对与更新路径、以及取令牌的辅助函数也依赖它。部分初始设置在证书错误且本地时间无效时会尝试重启时间同步服务。这些调用说明「日期在范围内」是业务前置条件,却不能替代 HTTPS 自身的证书验证和服务端认证。

    还有两个容易混淆的语义:GNSS 时间戳被转为 UTC 后移除时区信息,再与本地时间下界比较,设备为 UTC 时一致、非 UTC 时边界会偏移;而外层有效位为真只表示当前墙钟通过了范围门,不是刚从 GNSS 成功校时。该进程也不会证明相机帧起始、IMU 中断与诊断日志时间戳已与墙钟完成共时钟标定。

    硬件与插件化要求

    时间服务应分别声明墙钟来源与可信级别、UTC 与闰秒、单调时钟域、观测时间戳、最后同步时间、估计误差、时钟跳变和上游服务状态。只有受信任的平台层可设置系统时间;GNSS 与网络插件应提交带来源与精度的候选时间,不能直接把范围检查当成「可信时间」,也不能自行运行系统校时命令。UI、更新与 eSIM 的网络门应区分「尚无可信时间、可信时间可用、时间失步」三种状态,避免把范围检查误认为完整安全检查。

    待验证边界

    尚未验证的场景包括:无 RTC 冷启动、真实镜像中该文件修改时间异常、时区非 UTC、GNSS 定位延迟或失效、NTP 与 GNSS 冲突,以及系统时间前后跳变对证书、令牌、日志与相机时序的影响。本轮未执行这些动态或实板测试。

    常见问题

    system_time_valid() 为真就代表时间可信吗?

    不代表。它只是一个纯日期范围检查,不读 NTP 同步状态、GNSS 精度、硬件 RTC 来源或 TLS 证书有效期。一台时钟在区间内但快慢数天的设备同样能通过,因此「日期合理」不等于可信 UTC。

    Python 与 C++ 的时间有效判断一致吗?

    不一致。Python 侧下界为 2025-02-21、上界为 2035-01-01;C++ 侧下界为 2024-08-26 且没有上界。两端的「加一天」算术也不同:Python 先转本地无时区时间再加一个日历日,C++ 直接对 epoch 秒加 86400,跨夏令时时可差一小时。

    只要 GNSS 有定位就会校准系统时间吗?

    不一定。只有所选主题本轮更新、消息距当前单调时间不超过两秒、定位有效且时间落在范围内才调用校时函数;偏差不足十秒会直接跳过。而且该路径不检查定位消息的顶层有效位。

    本地墙钟无效会不会阻止 GNSS 校时?

    不会。探针显示时钟发布发生在 GNSS 门控之前,即使本地墙钟范围检查不通过,也可用新鲜定位尝试校时;所选定位消息的外层无效同样不阻断调用。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/SYSTEM_TIME_BOOTSTRAP_TRUST_CONTRACT_DEEP_DIVE.md(取用了公共日期门、GNSS 校时链、下游消费者与硬件插件要求)。
    • 项目 02 —— openpilot-research/NATIVE_UTIL_IO_EXIT_CONTRACT_DEEP_DIVE.md(关联的原生工具专题,用于对照 C++ 与 Python 两套日期门差异,本文未展开其 I/O 实现)。

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

    更新记录

    首次发布 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