mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
126 lines
6.1 KiB
Python
126 lines
6.1 KiB
Python
"""Real binary position decoder -> publisher branch -> serialized GPS -> consumers.
|
|
|
|
Root-only runtime candidate. AST extraction omits modem I/O, not the producer
|
|
assignments; the real schema supplies defaults and round-trips every GPS field.
|
|
"""
|
|
import ast
|
|
import datetime
|
|
import math
|
|
import struct
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
import pytest
|
|
from openpilot.cereal import messaging, log
|
|
from openpilot.system.qcomgpsd.structs import position_report, parse_struct, dict_unpacker
|
|
from openpilot.starpilot.gps.source import GPS_SOURCES, select_location
|
|
from openpilot.starpilot.gps.acquisition import GpsAcquisition
|
|
from openpilot.starpilot.navigation.runtime import location
|
|
from openpilot.starpilot.navigation.status import NavigationStatusSource
|
|
from openpilot.starpilot.ui.onroad_map import MapFeed
|
|
|
|
|
|
def binary_position(axis, *, source=2, week=2400, vdop=2.):
|
|
fmt, names = parse_struct(position_report)
|
|
values: dict[str, int | float] = dict.fromkeys(names, 0)
|
|
values.update({'u_PosSource': source, 'w_GpsWeekNumber': week, 'q_GpsFixTimeMs': 10000,
|
|
't_DblFinalPosLatLon[0]': math.radians(40), 't_DblFinalPosLatLon[1]': math.radians(-90),
|
|
'q_FltFinalPosAlt': 200., 'q_FltHeadingRad': 1., 'q_FltHeadingUncRad': .1,
|
|
'q_FltVdop': vdop, 'q_FltEllipseSemimajorAxis': axis, 'q_FltEllipseSemiminorAxis': 2.,
|
|
'u_EllipseConfidence': 68, 'u_HorizontalReliability': 4,
|
|
'q_FltVelEnuMps[0]': 2., 'q_FltVelEnuMps[1]': 3.})
|
|
return struct.pack(fmt, *[values[name] for name in names])
|
|
|
|
|
|
def publish_position(data):
|
|
path = Path(__file__).parents[1] / 'qcomgpsd.py'
|
|
branch = next(node for node in ast.walk(ast.parse(path.read_text())) if isinstance(node, ast.If) and
|
|
ast.unparse(node.test) == 'log_type == LOG_GNSS_POSITION_REPORT')
|
|
unpacker, _ = dict_unpacker(position_report)
|
|
sent = []
|
|
class Publisher:
|
|
def send(self, service, event):
|
|
assert service == 'gpsLocation'
|
|
sent.append(event.to_bytes())
|
|
namespace = {'math': math, 'datetime': datetime, 'unpack_position': unpacker, 'log_payload': data,
|
|
'messaging': messaging, 'log': log, 'pm': Publisher()}
|
|
loop = ast.For(target=ast.Name(id='_once', ctx=ast.Store()),
|
|
iter=ast.Tuple(elts=[ast.Constant(0)], ctx=ast.Load()), body=branch.body, orelse=[])
|
|
exec(compile(ast.fix_missing_locations(ast.Module(body=[loop], type_ignores=[])), str(path), 'exec'), namespace)
|
|
return sent
|
|
|
|
|
|
class Messages:
|
|
def __init__(self, gps, stamp):
|
|
services = [*GPS_SOURCES, 'starpilotNavigation', 'deviceState']
|
|
self.valid = dict.fromkeys(services, False)
|
|
self.seen = dict.fromkeys(services, False)
|
|
self.alive = dict.fromkeys(services, False)
|
|
self.logMonoTime = dict.fromkeys(services, 0)
|
|
self.recv_time = dict.fromkeys(services, 0.)
|
|
self.valid['gpsLocation'] = self.seen['gpsLocation'] = self.alive['gpsLocation'] = True
|
|
self.logMonoTime['gpsLocation'] = stamp
|
|
self.recv_time['gpsLocation'] = stamp / 1e9
|
|
self.gps = gps
|
|
def __getitem__(self, service):
|
|
if service != 'gpsLocation':
|
|
raise KeyError(service)
|
|
return self.gps
|
|
def update(self, timeout):
|
|
pass
|
|
|
|
|
|
@pytest.mark.parametrize('axis,valid', [(4.25, True), (25., True), (0., False), (-1., False),
|
|
(25.125, False), (float('nan'), False), (float('inf'), False)])
|
|
def test_modem_horizontal_metric_reaches_real_wire_and_all_shared_consumers(axis, valid):
|
|
wire = publish_position(binary_position(axis))
|
|
assert len(wire) == 1
|
|
with log.Event.from_bytes(wire[0]) as event:
|
|
gps = event.gpsLocation
|
|
assert str(gps.source) == 'qcomdiag' and gps.hasFix
|
|
if math.isfinite(axis):
|
|
assert gps.horizontalAccuracy == pytest.approx(axis)
|
|
sm = Messages(gps, event.logMonoTime)
|
|
now = event.logMonoTime + 10_000_000
|
|
assert (select_location(sm, now) is not None) == valid
|
|
assert (location(sm, now) is not None) == valid
|
|
empty_acquisition = SimpleNamespace(update=lambda timeout: None,
|
|
updated={'gpsLocationExternal': False, 'qcomGnss': False}, valid={})
|
|
feed = MapFeed(sm, acquisition=GpsAcquisition(sm_factory=lambda: empty_acquisition))
|
|
with patch('openpilot.starpilot.ui.onroad_map.time.monotonic_ns', return_value=now), \
|
|
patch('openpilot.starpilot.ui.onroad_map.time.monotonic', return_value=now / 1e9):
|
|
assert (feed.read().fix is not None) == valid
|
|
clock = [event.logMonoTime - 100_000_000]
|
|
with patch('openpilot.cereal.messaging.SubMaster', return_value=sm):
|
|
status = NavigationStatusSource(mono_clock=lambda: clock[0], boot_clock=lambda: clock[0])
|
|
clock[0] = now
|
|
assert (status.search_position() is not None) == valid
|
|
# A consumer cannot use stale, future or wrong-source GPS.
|
|
assert select_location(sm, event.logMonoTime + 2_500_000_001) is None
|
|
assert select_location(sm, event.logMonoTime - 1) is None
|
|
wrong_source = event.as_builder()
|
|
wrong_source.gpsLocation.source = 'ublox'
|
|
with log.Event.from_bytes(wrong_source.to_bytes()) as mismatched:
|
|
assert select_location(Messages(mismatched.gpsLocation, mismatched.logMonoTime), now) is None
|
|
|
|
|
|
def test_report_rejections_and_modem_no_fix_sentinel_are_preserved():
|
|
assert not publish_position(binary_position(4.25, source=0))
|
|
assert not publish_position(binary_position(4.25, week=0xFFFF))
|
|
wire = publish_position(binary_position(4.25, vdop=500.))
|
|
with log.Event.from_bytes(wire[0]) as event:
|
|
assert not event.gpsLocation.hasFix
|
|
assert select_location(Messages(event.gpsLocation, event.logMonoTime), event.logMonoTime) is None
|
|
unpacker, _ = dict_unpacker(position_report)
|
|
with pytest.raises(struct.error):
|
|
unpacker(binary_position(4.25)[:-1])
|
|
|
|
|
|
def test_valid_modem_fix_is_not_lost_due_to_default_accuracy():
|
|
"""Stable root before/after entry point; fail for the known producer omission."""
|
|
wire = publish_position(binary_position(4.25))
|
|
assert len(wire) == 1
|
|
with log.Event.from_bytes(wire[0]) as event:
|
|
assert event.gpsLocation.horizontalAccuracy > 0, 'qcomdiag omitted horizontalAccuracy'
|
|
test_modem_horizontal_metric_reaches_real_wire_and_all_shared_consumers(4.25, True)
|