This commit is contained in:
whoisdomi
2026-09-17 11:50:18 -05:00
parent e84eafd9d1
commit 947c6f6b10
3 changed files with 120 additions and 0 deletions
@@ -0,0 +1,113 @@
"""Onroad GNSS health readout.
Built to measure RF desense from the external GPU's USB-C link: with the eGPU unplugged the
receiver demodulates satellite time from ~93% of tracked satellites and fixes immediately, and
with it plugged in that collapses to 0% while the tracked satellite count actually rises. Raw
satellite counts are therefore misleading on their own - the demodulation rate and C/No are what
show whether a cable, ferrite or antenna placement change helped.
"""
import pyray as rl
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app, FontWeight
WIDTH = 300
HEIGHT = 150
MARGIN = 30
PADDING = 14
TITLE_SIZE = 26
ROW_SIZE = 32
_BG = rl.Color(0, 0, 0, 180)
_LABEL = rl.Color(255, 255, 255, 140)
_GOOD = rl.Color(34, 197, 94, 255)
_WARN = rl.Color(234, 179, 8, 255)
_BAD = rl.Color(239, 68, 68, 255)
# Demodulation rate thresholds. Anything under ~40% has never produced a fix in logged drives.
SAT_TIME_GOOD = 60.0
SAT_TIME_WARN = 25.0
# Carrier-to-noise, in the modem's raw units (~2550 average with the eGPU unplugged and a clean
# fix; the desensed drives report 0 for every satellite, so any non-zero reading is progress).
CNO_GOOD = 2000.0
CNO_WARN = 800.0
def _grade(value: float, good: float, warn: float) -> rl.Color:
if value >= good:
return _GOOD
if value >= warn:
return _WARN
return _BAD
class GnssHealth:
"""Bottom-right live GNSS quality readout."""
def __init__(self):
self._font = gui_app.font(FontWeight.SEMI_BOLD)
self._sat_time_pct = 0.0
self._cno = 0.0
self._gps_sv = 0
self._glonass_sv = 0
self._has_fix = False
def _update(self) -> None:
sm = ui_state.sm
if not sm.valid.get("qcomGnss", False):
return
gnss = sm["qcomGnss"]
if gnss.which() != "measurementReport":
return
report = gnss.measurementReport
svs = list(report.sv)
source = str(report.source)
if "glonass" in source:
self._glonass_sv = len(svs)
else:
self._gps_sv = len(svs)
if svs:
known = sum(1 for sv in svs if sv.measurementStatus.satelliteTimeIsKnown)
self._sat_time_pct = 100.0 * known / len(svs)
noise = [sv.carrierNoise for sv in svs if sv.carrierNoise > 0]
self._cno = sum(noise) / len(noise) if noise else 0.0
if sm.valid.get("gpsLocationExternal", False):
self._has_fix = sm["gpsLocationExternal"].hasFix
def render(self, bounds: rl.Rectangle) -> None:
self._update()
x = bounds.x + bounds.width - WIDTH - MARGIN
y = bounds.y + bounds.height - HEIGHT - MARGIN
rect = rl.Rectangle(x, y, WIDTH, HEIGHT)
rl.draw_rectangle_rounded(rect, 0.12, 10, _BG)
tx = int(x + PADDING)
ty = int(y + PADDING)
rl.draw_text_ex(self._font, "GNSS", rl.Vector2(tx, ty), TITLE_SIZE, 0, _LABEL)
fix_text = "FIX" if self._has_fix else "NO FIX"
fix_color = _GOOD if self._has_fix else _BAD
rl.draw_text_ex(self._font, fix_text, rl.Vector2(int(x + WIDTH - PADDING - 90), ty),
TITLE_SIZE, 0, fix_color)
ty += 34
rl.draw_text_ex(self._font, "time", rl.Vector2(tx, ty), ROW_SIZE, 0, _LABEL)
rl.draw_text_ex(self._font, f"{self._sat_time_pct:.0f}%", rl.Vector2(tx + 120, ty),
ROW_SIZE, 0, _grade(self._sat_time_pct, SAT_TIME_GOOD, SAT_TIME_WARN))
ty += 36
rl.draw_text_ex(self._font, "C/No", rl.Vector2(tx, ty), ROW_SIZE, 0, _LABEL)
rl.draw_text_ex(self._font, f"{self._cno:.0f}", rl.Vector2(tx + 120, ty),
ROW_SIZE, 0, _grade(self._cno, CNO_GOOD, CNO_WARN))
ty += 36
rl.draw_text_ex(self._font, "sats", rl.Vector2(tx, ty), ROW_SIZE, 0, _LABEL)
rl.draw_text_ex(self._font, f"{self._gps_sv}G {self._glonass_sv}R",
rl.Vector2(tx + 120, ty), ROW_SIZE, 0, rl.WHITE)
@@ -16,6 +16,7 @@ from openpilot.selfdrive.ui.onroad.starpilot.stopping_point import render_stoppi
from openpilot.selfdrive.ui.onroad.starpilot.pause_indicators import render_lateral_paused, render_longitudinal_paused
from openpilot.selfdrive.ui.onroad.starpilot.pulse_glide import get_pulse_glide_border_color, render_pulse_glide
from openpilot.selfdrive.ui.onroad.starpilot.pip_sidecam import PipSideCamera
from openpilot.selfdrive.ui.onroad.starpilot.gnss_health import GnssHealth
from openpilot.selfdrive.ui.onroad.starpilot.favorite_radial_menu import FavoriteRadialMenu
from openpilot.selfdrive.ui.onroad.starpilot.weather_icon import render_weather_icon
from openpilot.selfdrive.ui.lib.starpilot_status import (
@@ -49,6 +50,7 @@ class StarPilotOnroadView(AugmentedRoadView):
self._avg_fps = 0.0
self._pip_sidecam = self._child(PipSideCamera())
self._gnss_health = GnssHealth()
self._favorite_radial_menu = FavoriteRadialMenu(
ui_state.ui_params,
ui_state.params_memory,
@@ -143,6 +145,10 @@ class StarPilotOnroadView(AugmentedRoadView):
self._pip_sidecam.render(self._content_rect)
# Always on: this is a temporary instrument for the eGPU RF-desense testing, and gating it
# behind a param would mean registering a new key in params_keys.h, which needs a rebuild.
self._gnss_health.render(self._content_rect)
# The picker is an app-drawer modal, so it intentionally draws above
# PiP and other on-road overlays while active.
if self._draw_hud_controls and not self._full_alert_showing():
+1
View File
@@ -64,6 +64,7 @@ class UIState:
"selfdriveState",
"longitudinalPlan",
"gpsLocationExternal",
"qcomGnss",
"mapdOut",
"carOutput",
"carControl",