Files
StarPilot/openpilot/starpilot/ui/tests/test_traffic_status.py
T
firestar5683 a82935f326 User interfaces and Android Auto
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: pharmacomaniac <blittle65@gmail.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

149 lines
8.1 KiB
Python

"""Fresh Traffic receipts affect only the read-only C3/C4 status display."""
from dataclasses import replace
import tempfile
import unittest
from types import SimpleNamespace as NS
from unittest import mock
import pyray as rl
from opendbc.car.car_helpers import interfaces
from opendbc.car.hyundai.values import CAR, HyundaiFlags
from openpilot.cereal import messaging
from openpilot.common.params import Params
from openpilot.starpilot.conditional_mode.manual import read_button_map
from openpilot.starpilot.conditional_mode.runtime_settings import ConditionalSettingsOwner
from openpilot.starpilot.conditional_mode.status import settings_fingerprint
from openpilot.starpilot.ui.onroad_compact_widgets import MiciSidebarWidgets
from openpilot.starpilot.ui.onroad import OnroadView
from openpilot.starpilot.ui.presentation import FontRole
from openpilot.starpilot.ui.runtime_snapshot import RuntimeSnapshotAdapter
from openpilot.starpilot.ui.shell import ShellMode
from openpilot.starpilot.ui.tests.test_runtime_snapshot import ui_fake, BOOT_OFFSET_NS
from openpilot.starpilot.ui.traffic_status import TrafficDisplayProjector
NOW = 10_000_000_000
BOOT = NOW + 2_000_000_000
DRIVE = NOW - 1_000_000_000
SESSION = 'a' * 32
def receipt(settings_hash, map_hash, *, sequence=1, session=SESSION, reason='active',
accepted=True, effective=True, ready=True, observed=NOW, drive=DRIVE,
source_boot=BOOT - 5_000_000):
event = messaging.new_message('slcState', valid=False)
event.logMonoTime = observed - 1_000_000 # Outer SLC clock/validity belongs to another feature.
event.slcState.trafficMode = {
'version': 1, 'sessionId': session, 'sequence': sequence,
'observedMonoTime': observed, 'validUntilMonoTime': observed + 100_000_000,
'driveStartMonoTime': drive, 'sourceEpoch': 1, 'sourceBootTime': source_boot,
'accepted': accepted, 'effective': effective, 'reason': reason,
'settingsFingerprint': settings_hash, 'buttonMapFingerprint': map_hash,
'profileTargetReady': ready, 'profileReason': 'qualified' if ready else 'unsupported_traffic_follow_below_effective_floor',
}
return messaging.log_from_bytes(event.to_bytes())
class TestTrafficStatus(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.params = Params(temporary.name)
self.params.put('ModeButtonControl', 6, block=True)
self.settings_hash = settings_fingerprint(ConditionalSettingsOwner(self.params).refresh(NOW))
self.map_hash = read_button_map(self.params, include_ioniq_media=True).fingerprint()
self.reader = TrafficDisplayProjector()
def project(self, event, **changes):
args = {'now_mono_ns': NOW, 'now_boot_ns': BOOT, 'drive_id': DRIVE,
'settings_fingerprint': self.settings_hash, 'map_fingerprint': self.map_hash,
'map_assigned': True, 'profile_valid': True, 'long_active': True, 'selfdrive_enabled': True,
'car_valid': True, 'system_long': True}
args.update(changes)
return self.reader.project(event.slcState if event is not None else None, **args)
def test_no_traffic_wire_remains_absent_with_independent_vehicle_checks(self):
ui = ui_fake()
ui.CP = NS(carFingerprint="", openpilotLongitudinalControl=True, pcmCruise=False)
state = RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad
self.assertIsNone(state.traffic_display)
self.assertFalse(state.traffic_mode)
def test_actual_wire_states_and_clearing(self):
active = receipt(self.settings_hash, self.map_hash)
self.assertEqual(self.project(active).state, 'active')
self.assertEqual(self.project(active).state, 'active') # Same frame remains fresh, not a new source.
self.assertIsNone(self.project(active, now_mono_ns=NOW + 101_000_000))
self.assertIsNone(self.project(active, now_boot_ns=BOOT + 400_000_000))
paused = receipt(self.settings_hash, self.map_hash, sequence=2, observed=NOW + 10_000_000,
reason='authority_unavailable', effective=False)
self.assertEqual(self.project(paused, now_mono_ns=NOW + 10_000_000, long_active=False).state, 'paused')
off = receipt(self.settings_hash, self.map_hash, sequence=3, observed=NOW + 20_000_000,
reason='off', accepted=False, effective=False)
self.assertEqual(self.project(off, now_mono_ns=NOW + 20_000_000).state, 'off')
unavailable = receipt(self.settings_hash, self.map_hash, sequence=4, observed=NOW + 30_000_000, ready=False)
self.assertEqual(self.project(unavailable, now_mono_ns=NOW + 30_000_000).state, 'unavailable_profile')
self.assertIsNone(self.project(active)) # Old sequence cannot resurrect active status.
source = receipt(self.settings_hash, self.map_hash, sequence=5, observed=NOW + 40_000_000,
reason='media_unavailable', accepted=False, effective=False, source_boot=0)
self.assertEqual(self.project(source, now_mono_ns=NOW + 40_000_000).state, 'unavailable_source')
def test_wrong_drive_context_and_restarted_session_reject_old_source(self):
first = receipt(self.settings_hash, self.map_hash)
self.assertIsNone(self.project(first, drive_id=DRIVE + 1))
self.assertIsNone(self.project(first, settings_fingerprint='b' * 64))
self.assertIsNone(self.project(first, map_fingerprint='b' * 64))
self.assertIsNone(self.project(first, map_assigned=False))
self.assertIsNone(self.project(first, profile_valid=False, long_active=False))
self.assertEqual(self.project(first).state, 'active')
second = receipt(self.settings_hash, self.map_hash, session='c' * 32, observed=NOW + 10_000_000)
self.assertIsNone(self.project(second, now_mono_ns=NOW + 10_000_000))
next_frame = receipt(self.settings_hash, self.map_hash, session='c' * 32,
sequence=2, observed=NOW + 20_000_000)
self.assertEqual(self.project(next_frame, now_mono_ns=NOW + 20_000_000).state, 'active')
self.assertIsNone(self.project(first))
self.assertIsNone(self.project(next_frame, drive_id=DRIVE + 1))
def test_snapshot_and_compact_rail_use_only_live_effective_status(self):
ui = ui_fake()
ui.params = self.params
ui.CP = interfaces[CAR.HYUNDAI_IONIQ_6].get_non_essential_params(CAR.HYUNDAI_IONIQ_6)
ui.CP.flags = int(HyundaiFlags.CANFD_LKA_STEER_MSG)
ui.CP.openpilotLongitudinalControl = True
ui.sm['deviceState'].startedMonoTime = DRIVE
ui.sm['carState'].canValid = True
ui.sm['carState'].canTimeout = False
ui.sm['carControl'].longActive = True
ui.sm['selfdriveState'].enabled = True
ui.sm.put('controlsState', NS(longControlState='pid'))
event = receipt(self.settings_hash, self.map_hash)
ui.sm.put('slcState', event.slcState)
ui.sm.valid['slcState'] = False
adapter = RuntimeSnapshotAdapter(ui, mono_clock=lambda: NOW, boot_clock=lambda: NOW + BOOT_OFFSET_NS)
with mock.patch('openpilot.starpilot.ui.runtime_snapshot.paired_clocks_ns', return_value=(NOW, BOOT, 0)):
shown = adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad
self.assertTrue(shown.traffic_mode)
self.assertEqual(shown.traffic_display.label, 'TRAFFIC')
self.assertEqual(shown.speed_limit.kind.value, 'stale') # Invalid SLC does not veto independent Traffic status.
fonts = mock.Mock()
fonts.measure.return_value = NS(width=24, height=11)
rail = MiciSidebarWidgets(fonts)
with mock.patch('openpilot.starpilot.ui.onroad_compact_widgets._line'), mock.patch.object(rl, 'draw_triangle'):
rail._personality(rl.Rectangle(476, 160, 60, 80), shown)
self.assertEqual(fonts.draw.call_args.args[:3], ('TRF', FontRole.SEMI_BOLD, 11))
large = OnroadView.__new__(OnroadView)
large.fonts = fonts
large.projection_viewport = None
fonts.reset_mock()
for width, expected_x in ((1860, 1390), (2160, 1690)):
large._traffic_badge(replace(shown, viewport_width=width), 101)
self.assertEqual(fonts.draw.call_args.args[:5], ('TRAFFIC', FontRole.SEMI_BOLD, 25, expected_x, 101))
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW + 101_000_000).onroad.traffic_mode)
self.assertIsNone(adapter.build(ShellMode.ONROAD, now_ns=NOW + 101_000_000).onroad.traffic_display)
if __name__ == '__main__':
unittest.main()