mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
1095 lines
58 KiB
Python
1095 lines
58 KiB
Python
"""Offline service and Params fakes; no IPC, device, or settings writes."""
|
|
from unittest.mock import Mock
|
|
from openpilot.starpilot.ui.presentation import BitmapFonts
|
|
|
|
|
|
from types import SimpleNamespace as NS
|
|
import time
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
import pyray as rl
|
|
import pytest
|
|
|
|
from openpilot.cereal import log, messaging
|
|
from openpilot.starpilot.ui.onroad_state import AlertSize, ObservationKind
|
|
from openpilot.starpilot.ui.runtime_snapshot import RuntimeSnapshotAdapter as NativeRuntimeSnapshotAdapter, current_alert, current_message, display_message
|
|
from openpilot.starpilot.ui.settings_state import Destination
|
|
from openpilot.starpilot.ui.onroad_customization import default_document
|
|
from openpilot.starpilot.ui.shell import ShellMode
|
|
|
|
|
|
NOW = 10_000_000_000
|
|
BOOT_OFFSET_NS = time.clock_gettime_ns(getattr(time, "CLOCK_BOOTTIME", time.CLOCK_MONOTONIC)) - time.monotonic_ns()
|
|
|
|
|
|
class RuntimeSnapshotAdapter(NativeRuntimeSnapshotAdapter):
|
|
def __init__(self, *args, **kwargs):
|
|
self._fixture_now_ns = NOW
|
|
if "mono_clock" not in kwargs:
|
|
first = True
|
|
|
|
def mono_clock():
|
|
nonlocal first
|
|
if first:
|
|
first = False
|
|
return NOW - 1
|
|
return self._fixture_now_ns
|
|
|
|
kwargs["mono_clock"] = mono_clock
|
|
kwargs["boot_clock"] = lambda: self._fixture_now_ns + BOOT_OFFSET_NS
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def build(self, *args, now_ns=None, **kwargs):
|
|
self._fixture_now_ns = NOW if now_ns is None else now_ns
|
|
return super().build(*args, now_ns=now_ns, **kwargs)
|
|
|
|
|
|
class ParamsFake:
|
|
def __init__(self):
|
|
self.values = {"Version": "1.2.3", "GitCommit": "abc1234", "GitCommitDate": "1710000000 2024-03-09",
|
|
"LongitudinalPersonality": "1", "DongleId": "device", "DisableUpdates": "0"}
|
|
|
|
def get(self, key):
|
|
return self.values.get(key)
|
|
|
|
def get_bool(self, key):
|
|
return self.values.get(key) == "1"
|
|
|
|
|
|
class SubMasterFake:
|
|
def __init__(self):
|
|
self.frame = 3
|
|
self.messages = {}
|
|
self.valid = {}
|
|
self.alive = {}
|
|
self.seen = {}
|
|
self.updated = {}
|
|
self.logMonoTime = {}
|
|
self.recv_frame = {}
|
|
self.recv_time = {}
|
|
|
|
def put(self, service, message, *, age_ns=0, frame=3):
|
|
self.messages[service] = message
|
|
self.valid[service] = True
|
|
self.alive[service] = True
|
|
self.seen[service] = True
|
|
self.updated[service] = True
|
|
self.logMonoTime[service] = NOW - age_ns + (BOOT_OFFSET_NS if service == "pandaStates" else 0)
|
|
self.recv_frame[service] = frame
|
|
self.recv_time[service] = (NOW - age_ns) / 1e9
|
|
|
|
def __getitem__(self, service):
|
|
return self.messages[service]
|
|
|
|
|
|
def ui_fake():
|
|
sm = SubMasterFake()
|
|
sm.put("deviceState", NS(started=True, networkType=NS(raw=1), networkStrength=NS(raw=2), lastAthenaPingTime=NOW,
|
|
maxTempC=65.6, thermalStatus=log.DeviceState.ThermalStatus.ok))
|
|
sm.put("pandaStates", [NS(pandaType=log.PandaState.PandaType.tres, ignitionLine=True, ignitionCan=False)])
|
|
sm.put("carState", NS(vEgoCluster=15.0, vEgo=16.0, vCruiseCluster=80.0))
|
|
sm.put("carControl", NS(latActive=True, longActive=False, actuators=NS(torque=0.3)))
|
|
sm.put("selfdriveState", NS(experimentalMode=False, alertSize=NS(raw=0), alertStatus=NS(raw=0),
|
|
alertText1="", alertText2=""))
|
|
sm.put("slcState", NS(enabled=True, displayOnly=False, observationKind="valid", source="dashboard", speedLimit=20.0, offset=1.0,
|
|
pendingSpeedLimit=0.0, effectiveCap=21.0, acceptedSpeedLimit=0.0,
|
|
hasPending=False, hasCeiling=True, hasAccepted=False, sessionId="uuid-text",
|
|
decisionId=7, presentationId=9, status="active"))
|
|
return NS(sm=sm, params=ParamsFake(), prime_state=NS(is_paired=lambda: True), started=True, started_frame=1, is_metric=False,
|
|
has_longitudinal_control=True, CP=None, recording_audio=False)
|
|
|
|
|
|
class TestRuntimeSnapshot(unittest.TestCase):
|
|
def test_home_temperature_uses_live_units_and_warning_status(self):
|
|
from openpilot.starpilot.ui.home import HomeView
|
|
from openpilot.starpilot.ui.presentation import Profile
|
|
|
|
ui = ui_fake()
|
|
ui.started = False
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
view = HomeView.__new__(HomeView)
|
|
view.fonts = Mock(spec=BitmapFonts, profile=Profile.LARGE)
|
|
with patch.object(view, '_icon'), patch.object(view, '_text') as text, \
|
|
patch.object(view, '_measure', return_value=NS(width=80, height=35)), \
|
|
patch('openpilot.starpilot.ui.home.rl.draw_circle'), \
|
|
patch('openpilot.starpilot.ui.home.clip.begin_scissor_mode'), patch('openpilot.starpilot.ui.home.clip.end_scissor_mode'), \
|
|
patch('openpilot.starpilot.ui.home.outside_rounded_border'), \
|
|
patch('openpilot.starpilot.ui.home.rl.draw_rectangle_rounded') as edge:
|
|
for temperature, high, celsius, fahrenheit in ((0., False, '0°C', '32°F'), (65.6, False, '66°C', '150°F'),
|
|
(107., True, '107°C', '225°F'), (None, False, '--', '--')):
|
|
ui.sm['deviceState'].maxTempC = temperature
|
|
ui.sm['deviceState'].thermalStatus = log.DeviceState.ThermalStatus.critical if high else log.DeviceState.ThermalStatus.ok
|
|
ui.sm.valid['deviceState'] = temperature is not None
|
|
for metric, expected in ((True, celsius), (False, fahrenheit), (True, celsius)):
|
|
with self.subTest(temperature=temperature, metric=metric):
|
|
ui.is_metric = metric
|
|
home = adapter.build(ShellMode.HOME, now_ns=NOW).home
|
|
self.assertEqual(home.temperature_c, temperature)
|
|
self.assertEqual(home.metric, metric)
|
|
text.reset_mock()
|
|
edge.reset_mock()
|
|
view._sidebar(home)
|
|
self.assertEqual(text.call_args_list[2].args[0], expected)
|
|
expected_color = (201, 34, 49, 255) if high else (255, 255, 255, 255)
|
|
self.assertEqual(rl.color_to_int(edge.call_args_list[0].args[-1]), rl.color_to_int(expected_color))
|
|
|
|
# Temporary old-route replay compatibility: remove this entire test when support is removed.
|
|
# Cleanup instructions: openpilot/starpilot/ui/old_starpilot_route_shim.py.
|
|
def test_legacy_slc_is_replay_only_display_fallback(self):
|
|
from openpilot.tools.replay.display_clock import ClockSample
|
|
from openpilot.starpilot.ui.unified_speed_presentation import resolve_unified_speed
|
|
ui = ui_fake()
|
|
legacy = messaging.new_message('customReserved6', valid=True).customReserved6
|
|
legacy.slcSpeedLimit, legacy.slcSpeedLimitOffset, legacy.slcSpeedLimitSource = 13.4112, 2.2352, 'None'
|
|
ui.sm.put('customReserved6', legacy)
|
|
ui.sm.valid['slcState'] = False
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
self.assertNotEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.speed_limit.kind, ObservationKind.VALID)
|
|
ui.replay_sample = ui.sm.replay_sample = ClockSample(NOW, None, NOW, epoch=1, valid=True)
|
|
state = adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad
|
|
observation = state.speed_limit
|
|
self.assertAlmostEqual(observation.speed_limit_mps, 13.4112, places=4)
|
|
self.assertAlmostEqual(observation.offset_mps, 2.2352, places=4)
|
|
self.assertAlmostEqual(observation.effective_cap_mps, 15.6464, places=4)
|
|
self.assertEqual(observation.source, 'unknown')
|
|
self.assertFalse(observation.action_enabled)
|
|
self.assertIsNone(observation.decision_id)
|
|
presentation = resolve_unified_speed(state)
|
|
self.assertEqual(presentation.posted_text, '30')
|
|
self.assertEqual(presentation.limit_text, '35')
|
|
self.assertEqual(presentation.offset_text, '+5')
|
|
self.assertEqual(presentation.active_side, 'none')
|
|
ui.params.values['SpeedLimitController'] = '1'
|
|
ui.sm['carState'].canValid, ui.sm['carState'].canTimeout = True, False
|
|
ui.sm['carControl'].longActive = True
|
|
state = adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad
|
|
presentation = resolve_unified_speed(state)
|
|
self.assertEqual((presentation.active_side, presentation.status), ('max', 'Using set speed'))
|
|
from openpilot.starpilot.ui.onroad_state import slc_controls
|
|
from openpilot.starpilot.ui.presentation import Profile
|
|
self.assertEqual(slc_controls(Profile.LARGE, state), ())
|
|
ui.sm['carState'].vCruiseCluster = 40.0
|
|
presentation = resolve_unified_speed(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad)
|
|
self.assertEqual((presentation.active_side, presentation.status), ('max', 'Using set speed'))
|
|
legacy.slcOverriddenSpeed = 20.0
|
|
presentation = resolve_unified_speed(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad)
|
|
self.assertEqual((presentation.active_side, presentation.status), ('max', 'Set-speed override'))
|
|
ui.sm['carState'].gasPressed = True
|
|
presentation = resolve_unified_speed(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad)
|
|
self.assertEqual((presentation.active_side, presentation.status), ('max', 'Set-speed override'))
|
|
ui.sm['carState'].gasPressed = False
|
|
legacy.slcOverriddenSpeed = 0.0
|
|
ui.sm['carControl'].longActive = False
|
|
presentation = resolve_unified_speed(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad)
|
|
self.assertEqual((presentation.active_side, presentation.status), ('none', 'Cruise off'))
|
|
ui.sm.valid['slcState'] = True
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.speed_limit.source, 'dashboard')
|
|
ui.sm.valid['slcState'] = False
|
|
ui.sm.logMonoTime['customReserved6'] = NOW - 1_000_000_001
|
|
self.assertNotEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.speed_limit.kind, ObservationKind.VALID)
|
|
|
|
def test_replay_display_survives_qlog_intervals_and_expires(self):
|
|
from openpilot.tools.replay.display_clock import ClockSample
|
|
for service, interval_ns in (('vehicleParameters', 250_000_000),
|
|
('driverStateV2', 500_000_000), ('driverMonitoringState', 500_000_000)):
|
|
with self.subTest(service=service):
|
|
sm = SubMasterFake()
|
|
message = NS()
|
|
sm.put(service, message, age_ns=interval_ns + 20_000_000)
|
|
self.assertIsNone(display_message(sm, service, NOW, after_frame=1))
|
|
sm.replay_sample = ClockSample(NOW, None, NOW, epoch=1, valid=True)
|
|
self.assertIs(display_message(sm, service, NOW, after_frame=1), message)
|
|
self.assertIsNone(current_message(sm, service, NOW, after_frame=1))
|
|
self.assertIsNone(display_message(sm, service, NOW + 2 * interval_ns, after_frame=1))
|
|
self.assertIsNone(display_message(sm, service, NOW, after_frame=3))
|
|
self.assertIsNone(display_message(sm, service, NOW - 2 * interval_ns, after_frame=1))
|
|
sm.valid[service] = False
|
|
self.assertIsNone(display_message(sm, service, NOW, after_frame=1))
|
|
sm.valid[service] = True
|
|
sm.replay_sample = ClockSample(None, None, NOW, epoch=2)
|
|
self.assertIsNone(display_message(sm, service, NOW, after_frame=1))
|
|
|
|
def test_replay_uses_recorded_time_without_rewriting_messages(self):
|
|
from openpilot.tools.replay.display_clock import ClockSample
|
|
ui = ui_fake()
|
|
ui.replay_clock = object()
|
|
ui.replay_sample = ClockSample(NOW, None, NOW * 1000, epoch=1, valid=True)
|
|
ui.sm.replay_sample = ui.replay_sample
|
|
ui.sm['selfdriveState'].personality = 'relaxed'
|
|
before = dict(ui.sm.logMonoTime)
|
|
state = NativeRuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD).onroad
|
|
self.assertTrue(state.engaged)
|
|
self.assertEqual(state.personality, 2)
|
|
self.assertEqual(state.observed_ns, NOW)
|
|
self.assertEqual(ui.sm.logMonoTime, before)
|
|
ui.replay_sample = ClockSample(None, None, NOW * 1000, epoch=1)
|
|
ui.sm.replay_sample = ui.replay_sample
|
|
self.assertFalse(NativeRuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD).onroad.engaged)
|
|
|
|
def test_replay_pause_holds_visuals_but_publisher_death_does_not(self):
|
|
from openpilot.tools.replay.display_clock import ClockSample
|
|
ui = ui_fake()
|
|
ui.replay_clock = object()
|
|
ui.replay_sample = ClockSample(NOW, None, NOW * 1000, epoch=1, paused=True, valid=True)
|
|
ui.sm.replay_sample = ui.replay_sample
|
|
ui.sm['selfdriveState'].alertText1 = 'Recorded alert'
|
|
ui.sm['selfdriveState'].alertSize = NS(raw=1)
|
|
state = NativeRuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD).onroad
|
|
self.assertTrue(state.engaged)
|
|
self.assertEqual(state.alert.text1, 'Recorded alert')
|
|
ui.sm.replay_sample = ClockSample(None, None, NOW * 1000, epoch=1, paused=True)
|
|
self.assertEqual(current_alert(ui.sm, NOW, after_frame=1).size, AlertSize.NONE)
|
|
|
|
def test_replay_has_no_parked_control_authority(self):
|
|
from openpilot.tools.replay.display_clock import ClockSample
|
|
ui = ui_fake()
|
|
ui.replay_clock = object()
|
|
ui.started = False
|
|
ui.replay_sample = ClockSample(NOW, NOW, NOW * 1000, epoch=1, valid=True)
|
|
ui.sm.replay_sample = ui.replay_sample
|
|
adapter = NativeRuntimeSnapshotAdapter(ui)
|
|
self.assertFalse(adapter.confirmed_offroad())
|
|
self.assertFalse(adapter.build(ShellMode.HOME).device.offroad)
|
|
|
|
def test_replay_epoch_resets_projectors_and_saved_layout_cache(self):
|
|
from openpilot.tools.replay.display_clock import ClockSample
|
|
ui = ui_fake()
|
|
ui.replay_clock = object()
|
|
ui.replay_sample = ClockSample(NOW, None, NOW * 1000, epoch=1, valid=True)
|
|
ui.sm.replay_sample = ui.replay_sample
|
|
adapter = NativeRuntimeSnapshotAdapter(ui)
|
|
adapter.build(ShellMode.ONROAD)
|
|
previous = adapter._conditional
|
|
ui.replay_sample = ClockSample(NOW - 1_000_000_000, None, NOW * 1000, epoch=2, valid=True)
|
|
ui.sm.replay_sample = ui.replay_sample
|
|
state = adapter.build(ShellMode.ONROAD).onroad
|
|
self.assertIsNot(adapter._conditional, previous)
|
|
self.assertFalse(state.engaged)
|
|
|
|
def test_home_panda_presence_is_independent_of_ignition(self):
|
|
for panda_type in (log.PandaState.PandaType.dos, log.PandaState.PandaType.tres, log.PandaState.PandaType.cuatro):
|
|
for ignition in (False, True):
|
|
with self.subTest(panda_type=panda_type, ignition=ignition):
|
|
ui = ui_fake()
|
|
ui.started = False
|
|
ui.sm['deviceState'].started = False
|
|
panda = messaging.new_message('pandaStates', 1).pandaStates[0]
|
|
panda.pandaType = panda_type
|
|
panda.ignitionLine = ignition
|
|
ui.sm.put('pandaStates', [panda])
|
|
self.assertTrue(RuntimeSnapshotAdapter(ui).build(ShellMode.HOME, now_ns=NOW).home.vehicle_online)
|
|
|
|
def test_home_does_not_report_missing_or_unhealthy_panda_online(self):
|
|
for failure in ('empty', 'unknown', 'invalid', 'dead', 'old', 'future', 'old_receipt'):
|
|
with self.subTest(failure=failure):
|
|
ui = ui_fake()
|
|
if failure == 'empty':
|
|
ui.sm.messages['pandaStates'] = []
|
|
elif failure == 'unknown':
|
|
ui.sm['pandaStates'][0].pandaType = log.PandaState.PandaType.unknown
|
|
elif failure == 'invalid':
|
|
ui.sm.valid['pandaStates'] = False
|
|
elif failure == 'dead':
|
|
ui.sm.alive['pandaStates'] = False
|
|
elif failure == 'old':
|
|
ui.sm.logMonoTime['pandaStates'] -= 1_000_000_000
|
|
elif failure == 'future':
|
|
ui.sm.logMonoTime['pandaStates'] += 1_000_000_000
|
|
else:
|
|
ui.sm.recv_time['pandaStates'] -= 1
|
|
self.assertFalse(RuntimeSnapshotAdapter(ui).build(ShellMode.HOME, now_ns=NOW).home.vehicle_online)
|
|
|
|
def test_every_registered_settings_destination_stays_selected(self):
|
|
adapter = RuntimeSnapshotAdapter(ui_fake())
|
|
state = adapter.build(ShellMode.SETTINGS)
|
|
for item in state.settings.availability:
|
|
if item.available:
|
|
with self.subTest(destination=item.destination):
|
|
self.assertEqual(adapter.build(ShellMode.SETTINGS, item.destination).selected, item.destination)
|
|
self.assertEqual(adapter.build(ShellMode.SETTINGS, 'unknown').selected, Destination.STAR)
|
|
self.assertEqual(adapter.build(ShellMode.HOME, Destination.BLUETOOTH).selected, Destination.STAR)
|
|
|
|
def test_home_keeps_live_fields_without_querying_leaf_panels(self):
|
|
ui = ui_fake()
|
|
ui.started = False
|
|
forbidden = {'DongleId', 'HardwareSerial', 'UpdaterState', 'UpdaterFetchAvailable',
|
|
'DisableUpdates', 'UpdaterTargetBranch', 'OpenpilotEnabledToggle',
|
|
'DisengageOnAccelerator', 'IsLdwEnabled', 'AlwaysOnDM', 'RecordFront', 'RecordAudio'}
|
|
original_get, original_bool = ui.params.get, ui.params.get_bool
|
|
|
|
def get(key):
|
|
self.assertNotIn(key, forbidden)
|
|
return original_get(key)
|
|
|
|
def get_bool(key):
|
|
self.assertNotIn(key, forbidden)
|
|
return original_bool(key)
|
|
|
|
ui.params.get, ui.params.get_bool = get, get_bool
|
|
access = NS(status=lambda: self.fail('Home queried Galaxy leaf credentials'))
|
|
adapter = RuntimeSnapshotAdapter(ui, galaxy_access=access, bluetooth_powered=lambda: True)
|
|
for frame in range(3):
|
|
ui.params.values.update(GitCommit=f'commit{frame}', GitBranch=f'branch{frame}',
|
|
UpdaterCurrentDescription=f'upstream / detail{frame}')
|
|
ui.prime_state.is_paired = lambda paired=frame % 2 == 0: paired
|
|
snapshot = adapter.build(ShellMode.HOME, now_ns=NOW + frame * 500_000_000)
|
|
self.assertEqual(snapshot.home.commit, f'commit{frame}')
|
|
self.assertEqual(snapshot.home.branch, f'branch{frame}')
|
|
self.assertEqual(snapshot.home.description, f'7.0 / detail{frame}')
|
|
self.assertEqual(snapshot.home.paired, frame % 2 == 0)
|
|
self.assertTrue(snapshot.home.bluetooth)
|
|
self.assertFalse(snapshot.onroad.camera_available)
|
|
ui.params.get, ui.params.get_bool = original_get, original_bool
|
|
adapter.galaxy_access = None
|
|
ui.params.values['DongleId'] = 'changed'
|
|
self.assertEqual(adapter.build(ShellMode.SETTINGS, Destination.DEVICE).device.dongle_id, 'changed')
|
|
ui.started = True
|
|
self.assertTrue(adapter.build(ShellMode.ONROAD).onroad.camera_available)
|
|
|
|
def test_compact_menu_preserves_consumed_state_and_temporal_transitions(self):
|
|
full_ui, menu_ui = ui_fake(), ui_fake()
|
|
full, menu = RuntimeSnapshotAdapter(full_ui), RuntimeSnapshotAdapter(menu_ui)
|
|
modes = (ShellMode.ONROAD, ShellMode.SETTINGS, ShellMode.SETTINGS, ShellMode.ONROAD, ShellMode.HOME)
|
|
for frame, mode in enumerate(modes):
|
|
now = NOW + frame * 500_000_000
|
|
for ui in (full_ui, menu_ui):
|
|
ui.sm.frame += 1
|
|
ui.params.values['LongitudinalPersonality'] = str(frame % 3)
|
|
ui.params.values['ShowCSCStatus'] = str(frame % 2)
|
|
ui.prime_state.is_paired = lambda paired=frame % 2 == 0: paired
|
|
for destination in Destination:
|
|
expected = full.build(mode, destination, compact_scroll_x=frame * -25, now_ns=now)
|
|
actual = menu.build(mode, destination, compact_scroll_x=frame * -25, now_ns=now, menu_only=True)
|
|
self.assertEqual((actual.home, actual.onroad, actual.settings, actual.selected, actual.device.offroad),
|
|
(expected.home, expected.onroad, expected.settings, expected.selected, expected.device.offroad))
|
|
if mode != ShellMode.SETTINGS:
|
|
self.assertEqual(actual, expected)
|
|
|
|
def test_compact_menu_does_not_read_native_leaf_preferences(self):
|
|
ui = ui_fake()
|
|
reads = []
|
|
original_get, original_bool = ui.params.get, ui.params.get_bool
|
|
ui.params.get = lambda key: (reads.append(key), original_get(key))[1]
|
|
ui.params.get_bool = lambda key: (reads.append(key), original_bool(key))[1]
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
adapter.build(ShellMode.SETTINGS, now_ns=NOW, menu_only=True)
|
|
leaf_keys = {'DongleId', 'HardwareSerial', 'UpdaterState', 'UpdaterFetchAvailable', 'UpdaterTargetBranch',
|
|
'DisableUpdates', 'OpenpilotEnabledToggle', 'DisengageOnAccelerator', 'RecordFront', 'RecordAudio'}
|
|
self.assertFalse(leaf_keys.intersection(reads))
|
|
reads.clear()
|
|
adapter.build(ShellMode.SETTINGS, now_ns=NOW + 1)
|
|
self.assertTrue(leaf_keys.issubset(reads))
|
|
|
|
def test_home_branch_and_native_chestnut_state(self):
|
|
ui = ui_fake()
|
|
ui.params.values["GitBranch"] = "Domathon"
|
|
ui.chestnut_state = NS(value="loading")
|
|
home = RuntimeSnapshotAdapter(ui).build(ShellMode.HOME, now_ns=NOW).home
|
|
self.assertEqual(home.branch, "Domathon")
|
|
self.assertEqual(home.gpu_state, "loading")
|
|
|
|
def test_temporary_eps_alert_keeps_upstream_status_and_visual(self):
|
|
ui = ui_fake()
|
|
ui.sm.put("selfdriveState", NS(experimentalMode=False, alertSize=NS(raw=1), alertStatus=NS(raw=1),
|
|
alertText1="Steering Temporarily Unavailable", alertText2="",
|
|
alertType="steerTempUnavailableSilent/warning", alertHudVisual=NS(raw=1)))
|
|
alert = RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.alert
|
|
self.assertEqual(alert.size, AlertSize.SMALL)
|
|
self.assertTrue(alert.user_prompt)
|
|
self.assertFalse(alert.critical)
|
|
self.assertEqual(alert.visual_alert, 1)
|
|
|
|
def test_onroad_ancillary_reads_are_bounded_and_settings_refreshes_immediately(self):
|
|
ui = ui_fake()
|
|
ui.params.values['UpdaterState'] = 'idle'
|
|
reads = []
|
|
original_get = ui.params.get
|
|
original_bool = ui.params.get_bool
|
|
ui.params.get = lambda key: (reads.append(key), original_get(key))[1]
|
|
ui.params.get_bool = lambda key: (reads.append(key), original_bool(key))[1]
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
first = adapter.build(ShellMode.ONROAD, now_ns=NOW)
|
|
self.assertGreaterEqual(len(reads), 15)
|
|
reads.clear()
|
|
ui.params.values['UpdaterState'] = 'downloading'
|
|
same_second = adapter.build(ShellMode.ONROAD, now_ns=NOW + 10_000_000)
|
|
self.assertEqual(len(reads), 3)
|
|
self.assertEqual(same_second.software, first.software)
|
|
refreshed = adapter.build(ShellMode.ONROAD, now_ns=NOW + 1_000_000_000)
|
|
self.assertNotEqual(refreshed.software, first.software)
|
|
ui.params.values['UpdaterState'] = 'idle'
|
|
settings = adapter.build(ShellMode.SETTINGS, now_ns=NOW + 1_000_000_001)
|
|
self.assertEqual(settings.software, first.software)
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW + 1_000_000_002).software, first.software)
|
|
|
|
def test_customization_uses_existing_refresh_and_invalidation(self):
|
|
adapter = RuntimeSnapshotAdapter(ui_fake())
|
|
first = default_document()
|
|
second = default_document()
|
|
second["layouts"]["compact"]["max_speed"]["x"] = 200
|
|
with patch("openpilot.starpilot.ui.runtime_snapshot.read_customization", side_effect=[first, second, first]) as read:
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.customization, first)
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW + 1).onroad.customization, first)
|
|
self.assertEqual(read.call_count, 1)
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW + 1_000_000_000).onroad.customization, second)
|
|
adapter.invalidate_appearance()
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW + 1_000_000_001).onroad.customization, first)
|
|
self.assertEqual(read.call_count, 3)
|
|
|
|
def test_lead_indicator_requires_fresh_model_and_radar_in_same_drive(self):
|
|
ui = ui_fake()
|
|
ui.sm.put("modelV2", NS())
|
|
ui.sm.put("radarState", NS())
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
self.assertTrue(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.lead_indicator_source_fresh)
|
|
ui.sm.alive["radarState"] = False
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.lead_indicator_source_fresh)
|
|
ui.sm.alive["radarState"] = True
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW + 201_000_000).onroad.lead_indicator_source_fresh)
|
|
ui.sm.logMonoTime["radarState"] = NOW + 202_000_000
|
|
ui.sm.logMonoTime["modelV2"] = NOW + 202_000_000
|
|
ui.sm.logMonoTime["deviceState"] = NOW + 202_000_000
|
|
ui.sm.logMonoTime["pandaStates"] = NOW + BOOT_OFFSET_NS + 202_000_000
|
|
ui.sm.recv_time["pandaStates"] = (NOW + 202_000_000) / 1e9
|
|
ui.started_frame = 3
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW + 202_000_000).onroad.lead_indicator_source_fresh)
|
|
ui.sm.recv_frame["modelV2"] = 4
|
|
ui.sm.recv_frame["radarState"] = 4
|
|
self.assertTrue(adapter.build(ShellMode.ONROAD, now_ns=NOW + 202_000_000).onroad.lead_indicator_source_fresh)
|
|
ui.sm.valid["radarState"] = False
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW + 202_000_000).onroad.lead_indicator_source_fresh)
|
|
|
|
def test_stock_confidence_source_requires_current_post_start_model(self):
|
|
ui = ui_fake()
|
|
ui.sm.put("modelV2", NS(meta=NS(disengagePredictions=NS(brakeDisengageProbs=[0.1], steerOverrideProbs=[0.1]))))
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
initial = adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad
|
|
self.assertTrue(initial.stock_confidence_source_fresh)
|
|
self.assertEqual(initial.stock_confidence_source_stamp_ns, NOW)
|
|
self.assertEqual(initial.stock_confidence_drive_frame, 1)
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW + 201_000_000).onroad.stock_confidence_source_fresh)
|
|
ui.sm.logMonoTime["modelV2"] = NOW + 202_000_000
|
|
ui.sm.logMonoTime["deviceState"] = NOW + 202_000_000
|
|
ui.sm.logMonoTime["pandaStates"] = NOW + BOOT_OFFSET_NS + 202_000_000
|
|
ui.sm.recv_time["pandaStates"] = (NOW + 202_000_000) / 1e9
|
|
ui.sm.recv_frame["modelV2"] = 4
|
|
ui.started_frame = 4
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW + 202_000_000).onroad.stock_confidence_source_fresh)
|
|
ui.sm.recv_frame["modelV2"] = 5
|
|
self.assertTrue(adapter.build(ShellMode.ONROAD, now_ns=NOW + 202_000_000).onroad.stock_confidence_source_fresh)
|
|
ui.sm.alive["modelV2"] = False
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW + 202_000_000).onroad.stock_confidence_source_fresh)
|
|
|
|
def test_personality_notice_uses_observed_change_once_and_native_alert_wins(self):
|
|
ui = ui_fake()
|
|
ui.sm.messages['selfdriveState'].personality = NS(raw=1)
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.size, AlertSize.NONE)
|
|
|
|
ui.sm.messages['selfdriveState'].personality = NS(raw=0)
|
|
ui.sm.logMonoTime['selfdriveState'] = NOW + 100_000_000
|
|
changed = adapter.build(ShellMode.ONROAD, now_ns=NOW + 100_000_000).onroad.alert
|
|
self.assertEqual((changed.size, changed.text1, changed.text2),
|
|
(AlertSize.MID, 'Aggressive', 'Driving Personality'))
|
|
|
|
ui.sm.logMonoTime['selfdriveState'] = NOW + 1_000_000_000
|
|
ui.sm.logMonoTime['deviceState'] = NOW + 1_000_000_000
|
|
ui.sm.logMonoTime['pandaStates'] = NOW + BOOT_OFFSET_NS + 1_000_000_000
|
|
ui.sm.recv_time['pandaStates'] = (NOW + 1_000_000_000) / 1e9
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW + 1_000_000_000).onroad.alert.text1, 'Aggressive')
|
|
ui.sm.logMonoTime['selfdriveState'] = NOW + 1_700_000_000
|
|
ui.sm.logMonoTime['deviceState'] = NOW + 1_700_000_000
|
|
ui.sm.logMonoTime['pandaStates'] = NOW + BOOT_OFFSET_NS + 1_700_000_000
|
|
ui.sm.recv_time['pandaStates'] = (NOW + 1_700_000_000) / 1e9
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW + 1_700_000_000).onroad.alert.size, AlertSize.NONE)
|
|
|
|
ui.sm.messages['selfdriveState'].personality = NS(raw=2)
|
|
ui.sm.messages['selfdriveState'].alertSize = NS(raw=3)
|
|
ui.sm.messages['selfdriveState'].alertText1 = 'Take control'
|
|
ui.sm.messages['selfdriveState'].alertText2 = 'Now'
|
|
ui.sm.logMonoTime['selfdriveState'] = NOW + 1_800_000_000
|
|
ui.sm.recv_time['selfdriveState'] = (NOW + 1_800_000_000) / 1e9
|
|
native = adapter.build(ShellMode.ONROAD, now_ns=NOW + 1_800_000_000).onroad.alert
|
|
self.assertEqual((native.size, native.text1), (AlertSize.FULL, 'Take control'))
|
|
|
|
def test_hiding_personality_notices_keeps_native_safety_alerts_and_clears_pending_notice(self):
|
|
from dataclasses import replace
|
|
from openpilot.starpilot.ui.appearance_preferences import OnroadAppearance
|
|
from openpilot.starpilot.ui.runtime_snapshot import PersonalityNotice
|
|
from openpilot.starpilot.ui.onroad_state import OnroadAlert
|
|
ui = ui_fake()
|
|
ui.sm.messages['selfdriveState'].personality = NS(raw=1)
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
adapter.build(ShellMode.ONROAD, now_ns=NOW)
|
|
adapter._appearance_value = OnroadAppearance(hide_personality_alerts=True)
|
|
ui.sm.messages['selfdriveState'].personality = NS(raw=0)
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.size, AlertSize.NONE)
|
|
source = ui.sm.messages['selfdriveState']
|
|
source.alertSize, source.alertType = NS(raw=1), 'personalityChanged/warning'
|
|
source.alertText1 = 'Driving Personality: Aggressive'
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.size, AlertSize.NONE)
|
|
source.alertType, source.alertText1, source.alertSize = 'controlsMismatch/immediateDisable', 'Take control', NS(raw=3)
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.text1, 'Take control')
|
|
source.alertSize = NS(raw=0)
|
|
adapter._appearance_value = replace(adapter._appearance_value, hide_personality_alerts=False)
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.size, AlertSize.NONE)
|
|
source.personality = NS(raw=2)
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.text1, 'Relaxed')
|
|
notice = PersonalityNotice()
|
|
notice.update(NS(personality=1), drive_frame=1, event_ns=NOW, now_ns=NOW, native_alert=OnroadAlert())
|
|
for safety in (OnroadAlert(AlertSize.FULL, 'Take control', critical=True, alert_type='personalityChanged/warning'),
|
|
OnroadAlert(AlertSize.SMALL, 'Warning', user_prompt=True, alert_type='personalityChanged/warning')):
|
|
self.assertEqual(notice.update(NS(personality=2), drive_frame=1, event_ns=NOW, now_ns=NOW,
|
|
native_alert=safety, hide=True), safety)
|
|
|
|
def test_personality_notice_clears_on_invalid_source_and_new_drive(self):
|
|
ui = ui_fake()
|
|
ui.sm.messages['selfdriveState'].personality = NS(raw=1)
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
adapter.build(ShellMode.ONROAD, now_ns=NOW)
|
|
ui.sm.messages['selfdriveState'].personality = NS(raw=2)
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.text1, 'Relaxed')
|
|
ui.sm.valid['selfdriveState'] = False
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.size, AlertSize.NONE)
|
|
ui.sm.valid['selfdriveState'] = True
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.size, AlertSize.NONE)
|
|
ui.sm.messages['selfdriveState'].personality = NS(raw=0)
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.text1, 'Aggressive')
|
|
ui.started = False
|
|
self.assertEqual(adapter.build(ShellMode.HOME, now_ns=NOW).onroad.alert.size, AlertSize.NONE)
|
|
ui.started = True
|
|
ui.started_frame = 2
|
|
self.assertEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.size, AlertSize.NONE)
|
|
|
|
def test_display_survives_ui_frame_gap_without_extending_control_authority(self):
|
|
ui = ui_fake()
|
|
ui.CP = NS(carFingerprint="", openpilotLongitudinalControl=True, pcmCruise=False)
|
|
ui.sm.messages['carState'].canValid = True
|
|
ui.sm.messages['carState'].canTimeout = False
|
|
ui.sm.messages['carState'].vCruiseCluster = 86.0
|
|
ui.sm.messages['carControl'].longActive = True
|
|
ui.sm.messages['selfdriveState'].experimentalMode = True
|
|
ui.sm.messages['selfdriveState'].enabled = True
|
|
ui.sm.put('controlsState', NS(longControlState='pid'))
|
|
for service in ('carState', 'carControl', 'controlsState', 'selfdriveState'):
|
|
ui.sm.logMonoTime[service] = NOW - 50_000_000
|
|
self.assertIsNone(current_message(ui.sm, service, NOW, after_frame=ui.started_frame))
|
|
shown = RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad
|
|
self.assertTrue(shown.lateral_active and shown.longitudinal_active)
|
|
self.assertTrue(shown.experimental_enabled)
|
|
self.assertEqual(shown.cruise_kph, 86.0)
|
|
self.assertFalse(shown.slc_system_long_available)
|
|
ui.sm.logMonoTime['carControl'] = NOW - 201_000_000
|
|
expired = RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad
|
|
self.assertFalse(expired.lateral_active or expired.longitudinal_active)
|
|
ui.sm.logMonoTime['carControl'] = NOW
|
|
ui.sm.valid['carControl'] = False
|
|
invalid = RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad
|
|
self.assertFalse(invalid.lateral_active or invalid.longitudinal_active)
|
|
ui.sm.valid['carControl'] = True
|
|
ui.params.values['ExperimentalMode'] = '1'
|
|
ui.sm.logMonoTime['selfdriveState'] = NOW - 201_000_000
|
|
self.assertFalse(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.experimental_enabled)
|
|
|
|
def test_set_speed_uses_upstream_control_fallback_only_for_zero_cluster_value(self):
|
|
ui = ui_fake()
|
|
ui.sm.messages['carState'].canValid = True
|
|
ui.sm.messages['carState'].canTimeout = False
|
|
ui.sm.messages['carState'].vCruiseCluster = 0.0
|
|
ui.sm.put('controlsState', NS(vCruiseDEPRECATED=93.0))
|
|
self.assertEqual(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.cruise_kph, 93.0)
|
|
ui.sm.logMonoTime['controlsState'] = NOW - 201_000_000
|
|
self.assertIsNone(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.cruise_kph)
|
|
|
|
def test_alert_uses_upstream_display_lifetime_without_relaxing_control_data(self):
|
|
ui = ui_fake()
|
|
ui.sm.put('selfdriveState', NS(experimentalMode=False, enabled=False, alertSize=NS(raw=2),
|
|
alertStatus=NS(raw=1), alertText1='Communication issue', alertText2='Check system'),
|
|
age_ns=50_000_000)
|
|
self.assertIsNone(current_message(ui.sm, 'selfdriveState', NOW, after_frame=ui.started_frame))
|
|
self.assertEqual(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.text1,
|
|
'Communication issue')
|
|
ui.sm.put('selfdriveState', NS(experimentalMode=False, enabled=False, alertSize=NS(raw=0),
|
|
alertStatus=NS(raw=0), alertText1='', alertText2=''))
|
|
self.assertEqual(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.size, AlertSize.NONE)
|
|
|
|
def test_alert_rejects_invalid_and_old_drive_then_uses_upstream_lost_source_warning(self):
|
|
ui = ui_fake()
|
|
ui.sm.put('selfdriveState', NS(experimentalMode=False, enabled=True, alertSize=NS(raw=3),
|
|
alertStatus=NS(raw=2), alertText1='Warning', alertText2=''), age_ns=5_100_000_000)
|
|
self.assertEqual(current_alert(ui.sm, NOW, after_frame=ui.started_frame).text2, 'System Unresponsive')
|
|
ui.sm.valid['selfdriveState'] = False
|
|
self.assertEqual(current_alert(ui.sm, NOW, after_frame=ui.started_frame).size, AlertSize.NONE)
|
|
ui.sm.valid['selfdriveState'] = True
|
|
ui.started_frame = ui.sm.recv_frame['selfdriveState']
|
|
self.assertEqual(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.alert.size, AlertSize.NONE)
|
|
|
|
def test_home_indicators_use_live_power_and_fresh_chestnut_source(self):
|
|
ui = ui_fake()
|
|
ui.sm.messages["deviceState"].chestnutPresent = True
|
|
ui.chestnut_state = NS(value="active")
|
|
state = RuntimeSnapshotAdapter(ui, bluetooth_powered=lambda: True).build(ShellMode.HOME, now_ns=NOW).home
|
|
self.assertTrue(state.bluetooth)
|
|
self.assertTrue(state.gpu_present)
|
|
self.assertTrue(state.gpu_active)
|
|
self.assertFalse(RuntimeSnapshotAdapter(ui, bluetooth_powered=lambda: True).build(ShellMode.ONROAD, now_ns=NOW).home.bluetooth)
|
|
ui.sm.valid["deviceState"] = False
|
|
state = RuntimeSnapshotAdapter(ui, bluetooth_powered=lambda: False).build(ShellMode.HOME, now_ns=NOW).home
|
|
self.assertEqual(state.bluetooth, "off")
|
|
for value in ("off", "on", "connected"):
|
|
self.assertEqual(RuntimeSnapshotAdapter(ui, bluetooth_powered=lambda value=value: value).build(ShellMode.HOME, now_ns=NOW).home.bluetooth, value)
|
|
self.assertFalse(state.gpu_present)
|
|
self.assertFalse(state.gpu_active)
|
|
ui.sm.valid["deviceState"] = True
|
|
ui.usb_unknown = True
|
|
self.assertFalse(RuntimeSnapshotAdapter(ui).build(ShellMode.HOME, now_ns=NOW).home.gpu_present)
|
|
|
|
def test_home_shows_starpilot_version_without_changing_upstream_params(self):
|
|
ui = ui_fake()
|
|
ui.params.values["Version"] = "0.11.2"
|
|
ui.params.values["UpdaterCurrentDescription"] = "0.11.2 / Domathon"
|
|
snapshot = RuntimeSnapshotAdapter(ui).build(ShellMode.HOME, now_ns=NOW)
|
|
self.assertEqual(snapshot.home.version, "7.0")
|
|
self.assertEqual(snapshot.home.description, "7.0 / Domathon")
|
|
self.assertEqual(ui.params.values["Version"], "0.11.2")
|
|
self.assertEqual(ui.params.values["UpdaterCurrentDescription"], "0.11.2 / Domathon")
|
|
ui.params.values.pop("UpdaterCurrentDescription")
|
|
self.assertEqual(RuntimeSnapshotAdapter(ui).build(ShellMode.HOME, now_ns=NOW).home.description, "7.0")
|
|
|
|
def test_nested_curve_independent_of_outer_slc_validity(self):
|
|
ui = ui_fake()
|
|
ui.params.values["ShowCSCStatus"] = "1"
|
|
ui.CP = NS(carFingerprint="", openpilotLongitudinalControl=True, pcmCruise=False)
|
|
ui.sm.messages["carState"].canValid = True
|
|
ui.sm.messages["carState"].canTimeout = False
|
|
ui.sm.messages["carControl"].longActive = True
|
|
ui.sm.messages["selfdriveState"].enabled = True
|
|
ui.sm.put("controlsState", NS(longControlState="pid"))
|
|
event = messaging.new_message("slcState")
|
|
event.valid = False # SLC-specific envelope validity cannot veto independent Curve evidence.
|
|
event.logMonoTime = NOW
|
|
event.slcState.curve = {
|
|
"version": 1, "sessionId": "curve-session", "sequence": 1,
|
|
"observedMonoTime": NOW, "validUntilMonoTime": NOW + 100_000_000,
|
|
"modelMonoTime": NOW - 20_000_000, "configured": True, "documentValid": True,
|
|
"hasCandidate": True, "candidateMps": 15.0, "hasCeiling": True, "ceilingMps": 14.0,
|
|
"applied": True, "controlling": True, "training": False,
|
|
"calibrationProgress": 20.0, "comfortAccel": 2.0, "bindingDistance": 60.0,
|
|
"reason": "available", "plannerStatus": "selected", "persistenceStatus": "saved",
|
|
"glow": True, "curveOnly": True, "hasRoadCurvature": True, "roadCurvature": 0.02,
|
|
}
|
|
decoded = messaging.log_from_bytes(event.to_bytes())
|
|
ui.sm.put("slcState", decoded.slcState)
|
|
ui.sm.valid["slcState"] = decoded.valid
|
|
snapshot = RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW)
|
|
self.assertEqual(snapshot.onroad.speed_limit.kind, ObservationKind.STALE)
|
|
self.assertIsNotNone(snapshot.onroad.curve)
|
|
self.assertTrue(snapshot.onroad.curve.curve_only)
|
|
curve = snapshot.onroad.curve
|
|
self.assertIsNotNone(curve)
|
|
if curve is None:
|
|
self.fail("the fresh curve is missing")
|
|
curvature = curve.road_curvature
|
|
self.assertIsNotNone(curvature)
|
|
if curvature is None:
|
|
self.fail("the fresh curve has no curvature")
|
|
self.assertAlmostEqual(curvature, 0.02, places=5)
|
|
self.assertTrue(snapshot.onroad.slc_system_long_available)
|
|
ui.sm.logMonoTime["slcState"] = NOW - 300_000_000
|
|
self.assertIsNone(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.curve)
|
|
ui.sm.logMonoTime["slcState"] = NOW
|
|
ui.sm.recv_frame["slcState"] = ui.started_frame
|
|
self.assertIsNone(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.curve)
|
|
ui.sm.recv_frame["slcState"] = ui.started_frame + 1
|
|
ui.sm.valid["slcState"] = True
|
|
invalid = messaging.new_message("slcState")
|
|
invalid.slcState.curve.version = 0
|
|
ui.sm.put("slcState", messaging.log_from_bytes(invalid.to_bytes()).slcState)
|
|
self.assertIsNone(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.curve)
|
|
|
|
def test_fresh_state_tracks_each_control_axis_and_text_session(self):
|
|
ui = ui_fake()
|
|
ui.CP = NS(carFingerprint="vehicle-platform-id")
|
|
snapshot = RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW)
|
|
self.assertTrue(snapshot.onroad.lateral_active)
|
|
self.assertFalse(snapshot.onroad.longitudinal_active)
|
|
self.assertTrue(snapshot.onroad.engaged)
|
|
self.assertEqual(snapshot.onroad.speed_limit.session_id, "uuid-text")
|
|
self.assertEqual(snapshot.onroad.speed_limit.kind, ObservationKind.VALID)
|
|
self.assertEqual(snapshot.onroad.speed_limit.effective_cap_mps, 21.0)
|
|
self.assertIsNone(snapshot.home.stats)
|
|
self.assertEqual(snapshot.home.model_label, "")
|
|
|
|
def test_zero_speed_remains_visible_across_signed_filtered_estimates(self):
|
|
import pyray as rl
|
|
from openpilot.starpilot.ui.onroad_large_widgets import CurrentSpeedHud
|
|
|
|
for metric in (False, True):
|
|
ui = ui_fake()
|
|
ui.is_metric = metric
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
ui.sm.messages["carState"].vEgoCluster = 0.0
|
|
for speed in (-0.238205, -0.001, 0.0, 0.001, 0.44704):
|
|
with self.subTest(metric=metric, speed=speed):
|
|
ui.sm.messages["carState"].vEgo = speed
|
|
state = adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad
|
|
self.assertEqual(state.speed_mps, max(0.0, speed))
|
|
fonts = Mock(spec=BitmapFonts)
|
|
fonts.measure.return_value = NS(width=10, height=20)
|
|
fonts.vertical_ink.return_value = (0., 20.)
|
|
CurrentSpeedHud(fonts).render(rl.Rectangle(0, 0, 1800, 1020), state)
|
|
self.assertEqual(fonts.draw.call_args_list[0].args[0], "1" if speed == 0.44704 and not metric else
|
|
str(round(max(0., speed) * (3.6 if metric else 2.2369362921))))
|
|
self.assertEqual(fonts.draw.call_args_list[1].args[0], "km/h" if metric else "mph")
|
|
ui.sm.logMonoTime["carState"] = NOW - 2_000_000_000
|
|
self.assertIsNone(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.speed_mps)
|
|
|
|
def test_stale_and_malformed_values_never_become_displayed_speed(self):
|
|
ui = ui_fake()
|
|
ui.sm.logMonoTime["slcState"] = NOW - 500_000_000
|
|
ui.sm.messages["carState"].vEgoCluster = float("nan")
|
|
ui.sm.messages["carState"].vEgo = float("inf")
|
|
snapshot = RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW)
|
|
self.assertEqual(snapshot.onroad.speed_limit.kind, ObservationKind.STALE)
|
|
self.assertIsNone(snapshot.onroad.speed_mps)
|
|
ui.sm.put("slcState", NS(**{**vars(ui.sm.messages["slcState"]), "speedLimit": float("nan")}))
|
|
snapshot = RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW)
|
|
self.assertEqual(snapshot.onroad.speed_limit.kind, ObservationKind.STALE)
|
|
|
|
def test_pretransition_and_invalid_transport_are_rejected(self):
|
|
ui = ui_fake()
|
|
ui.sm.recv_frame["carControl"] = ui.started_frame
|
|
ui.sm.valid["slcState"] = False
|
|
self.assertIsNone(current_message(ui.sm, "carControl", NOW, after_frame=ui.started_frame))
|
|
snapshot = RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW)
|
|
self.assertFalse(snapshot.onroad.engaged)
|
|
self.assertEqual(snapshot.onroad.speed_limit.kind, ObservationKind.STALE)
|
|
|
|
def test_signed_torque_and_ambiguous_started_never_enable_offroad_actions(self):
|
|
ui = ui_fake()
|
|
ui.sm.messages["carState"].canValid = True
|
|
ui.sm.messages["carState"].canTimeout = False
|
|
ui.sm.messages["carControl"].actuators.torque = -0.9
|
|
ui.sm.put("carOutput", NS(actuatorsOutput=NS(torque=0.4)))
|
|
ui.sm.put("controlsState", NS(lateralControlState=NS(which=lambda: "torqueState")))
|
|
snapshot = RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW)
|
|
self.assertAlmostEqual(snapshot.onroad.torque_utilization, -0.4)
|
|
self.assertTrue(snapshot.onroad.torque_source_available)
|
|
ui.sm.logMonoTime["deviceState"] = NOW - 2_000_000_000
|
|
snapshot = RuntimeSnapshotAdapter(ui).build(ShellMode.SETTINGS, now_ns=NOW)
|
|
self.assertFalse(snapshot.device.offroad)
|
|
self.assertTrue(snapshot.settings.destination(Destination.DEVICE).available)
|
|
self.assertFalse(snapshot.device.available_actions)
|
|
self.assertTrue(snapshot.onroad.engaged)
|
|
self.assertEqual(snapshot.onroad.speed_mps, 15.0)
|
|
self.assertTrue(snapshot.onroad.camera_available)
|
|
|
|
def test_health_gaps_do_not_restart_presentation_or_set_speed_timer(self):
|
|
from pathlib import Path
|
|
from openpilot.starpilot.ui.onroad_compact_widgets import CompactHudRenderer
|
|
|
|
ui = ui_fake()
|
|
ui.sm['deviceState'].startedMonoTime = NOW - 1_000_000_000
|
|
ui.sm['carControl'].longActive = True
|
|
ui.sm['carState'].canValid = True
|
|
ui.sm['carState'].canTimeout = False
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
hud = CompactHudRenderer(Mock(spec=BitmapFonts, **vars(NS())), Path('/unused'))
|
|
began = None
|
|
for frame in range(240):
|
|
now = NOW + frame * 50_000_000
|
|
for service in ui.sm.messages:
|
|
ui.sm.logMonoTime[service] = now + (BOOT_OFFSET_NS if service == 'pandaStates' else 0)
|
|
ui.sm.recv_time[service] = now / 1e9
|
|
ui.sm.alive['pandaStates'] = frame % 3 != 0
|
|
ui.sm.alive['deviceState'] = frame % 5 != 0
|
|
if frame % 7 == 0:
|
|
ui.sm.logMonoTime['deviceState'] -= 2_000_000_000
|
|
view = adapter.build(ShellMode.SETTINGS if frame % 13 == 0 else ShellMode.ONROAD, now_ns=now)
|
|
self.assertEqual((view.onroad.drive_frame, view.onroad.torque_drive_frame), (1, 1))
|
|
self.assertTrue(view.onroad.camera_available and view.onroad.engaged)
|
|
self.assertFalse(view.device.offroad)
|
|
opacity = hud._set_speed_opacity(view.onroad, now)
|
|
if began is None:
|
|
began = hud._set_speed_changed_ns
|
|
self.assertEqual(hud._set_speed_changed_ns, began)
|
|
if frame > 100:
|
|
self.assertLess(opacity, .01)
|
|
ui.started = False
|
|
ended = adapter.build(ShellMode.HOME, now_ns=now).onroad
|
|
self.assertFalse(ended.camera_available or ended.engaged)
|
|
self.assertIsNone(ended.drive_frame)
|
|
hud._set_speed_opacity(ended, now)
|
|
self.assertIsNone(hud._set_speed_changed_ns)
|
|
|
|
def test_torque_feedback_requires_actual_current_drive_output(self):
|
|
ui = ui_fake()
|
|
ui.sm.messages["carState"].canValid = True
|
|
ui.sm.messages["carState"].canTimeout = False
|
|
ui.sm.put("controlsState", NS(lateralControlState=NS(which=lambda: "torqueState")))
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.torque_source_available)
|
|
ui.sm.put("carOutput", NS(actuatorsOutput=NS(torque=0.4)))
|
|
self.assertAlmostEqual(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.torque_utilization, -0.4)
|
|
for service in ("carOutput", "controlsState", "carState"):
|
|
with self.subTest(service=service):
|
|
ui.sm.valid[service] = False
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.torque_source_available)
|
|
ui.sm.valid[service] = True
|
|
ui.sm.recv_frame[service] = ui.started_frame
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.torque_source_available)
|
|
ui.sm.recv_frame[service] = 3
|
|
ui.sm.logMonoTime[service] = NOW - 201_000_000
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.torque_source_available)
|
|
ui.sm.logMonoTime[service] = NOW
|
|
ui.started = False
|
|
self.assertFalse(adapter.build(ShellMode.HOME, now_ns=NOW).onroad.torque_source_available)
|
|
|
|
def test_angle_torque_feedback_uses_current_vehicle_parameters(self):
|
|
ui = ui_fake()
|
|
ui.CP = NS(carFingerprint="", maxLateralAccel=3.0, openpilotLongitudinalControl=False, pcmCruise=True)
|
|
ui.sm.messages["carState"].canValid = True
|
|
ui.sm.messages["carState"].canTimeout = False
|
|
ui.sm.put("controlsState", NS(lateralControlState=NS(which=lambda: "angleState"),
|
|
curvature=0.001, desiredCurvature=0.002))
|
|
ui.sm.put("vehicleParameters", NS(valid=True, roll=0.01))
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
state = adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad
|
|
self.assertTrue(state.torque_source_available)
|
|
self.assertAlmostEqual(state.torque_utilization, (0.002 * 16 ** 2 - 0.01 * 9.81) / 3)
|
|
ui.sm.logMonoTime["vehicleParameters"] = NOW - 1_000_000_000
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.torque_source_available)
|
|
|
|
def test_slc_controls_require_fresh_system_long_independent_of_pcm_cruise(self):
|
|
ui = ui_fake()
|
|
ui.CP = NS(carFingerprint="", openpilotLongitudinalControl=True, pcmCruise=False)
|
|
ui.sm.messages["carState"].canTimeout = False
|
|
ui.sm.messages["carState"].canValid = True
|
|
ui.sm.messages["carControl"].longActive = True
|
|
ui.sm.messages["selfdriveState"].enabled = True
|
|
ui.sm.put("controlsState", NS(longControlState="pid"))
|
|
self.assertTrue(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.slc_system_long_available)
|
|
ui.CP.pcmCruise = True
|
|
self.assertTrue(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.slc_system_long_available)
|
|
ui.CP.openpilotLongitudinalControl = False
|
|
self.assertFalse(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.slc_system_long_available)
|
|
ui.CP.openpilotLongitudinalControl = True
|
|
ui.sm.messages["carState"].canValid = False
|
|
self.assertFalse(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.slc_system_long_available)
|
|
ui.sm.messages["carState"].canValid = True
|
|
ui.sm.logMonoTime["controlsState"] = NOW - 200_000_000
|
|
self.assertFalse(RuntimeSnapshotAdapter(ui).build(ShellMode.ONROAD, now_ns=NOW).onroad.slc_system_long_available)
|
|
|
|
|
|
@pytest.mark.parametrize('brand,standstill,cruise_standstill,cruise_enabled,expected', [
|
|
('honda', False, False, True, True),
|
|
('toyota', False, False, True, True),
|
|
('toyota', True, False, True, False),
|
|
('toyota', False, True, True, False),
|
|
('toyota', False, False, False, False),
|
|
])
|
|
def test_wheel_confirmation_hint_preserves_toyota_resume(brand, standstill, cruise_standstill, cruise_enabled, expected):
|
|
ui = ui_fake()
|
|
ui.CP = NS(carFingerprint='', brand=brand, openpilotLongitudinalControl=True, pcmCruise=brand == 'toyota')
|
|
car = ui.sm['carState']
|
|
car.canTimeout, car.canValid, car.standstill = False, True, standstill
|
|
car.cruiseState = NS(available=True, enabled=cruise_enabled, standstill=cruise_standstill)
|
|
ui.sm['carControl'].longActive = ui.sm['carControl'].enabled = True
|
|
ui.sm['selfdriveState'].enabled = True
|
|
ui.sm.put('controlsState', NS(longControlState='pid'))
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
assert adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.slc_wheel_confirmation_available == expected
|
|
ui.sm.logMonoTime['carControl'] = NOW - 200_000_000
|
|
assert not adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.slc_wheel_confirmation_available
|
|
|
|
|
|
class TestParkedClockDomains(unittest.TestCase):
|
|
def setUp(self):
|
|
self.ui = ui_fake()
|
|
self.ui.started = False
|
|
self.ui.sm["deviceState"].started = False
|
|
self.ui.sm["pandaStates"][0].ignitionLine = False
|
|
self.mono = 100_000_000_000
|
|
self.boot = 110_000_000_000
|
|
self.adapter = RuntimeSnapshotAdapter(self.ui, mono_clock=lambda: self.mono, boot_clock=lambda: self.boot)
|
|
self.mono += 150_000_000
|
|
self.boot += 150_000_000
|
|
self.refresh_both()
|
|
|
|
def refresh_both(self):
|
|
sm = self.ui.sm
|
|
sm.logMonoTime["deviceState"] = self.mono - 50_000_000
|
|
sm.logMonoTime["pandaStates"] = self.boot - 50_000_000
|
|
sm.recv_time["deviceState"] = (self.mono - 30_000_000) / 1e9
|
|
sm.recv_time["pandaStates"] = (self.mono - 30_000_000) / 1e9
|
|
sm.updated["deviceState"] = True
|
|
sm.updated["pandaStates"] = True
|
|
|
|
def parked(self):
|
|
current = self.adapter.confirmed_offroad()
|
|
self.assertEqual(current, self.adapter.build(ShellMode.SETTINGS, now_ns=self.mono).device.offroad)
|
|
return current
|
|
|
|
def test_authority_check_does_not_read_preferences_or_change_display_mode(self):
|
|
self.adapter._last_mode = ShellMode.ONROAD
|
|
with patch.object(self.ui.params, 'get', side_effect=AssertionError('unexpected preference read')), \
|
|
patch.object(self.ui.params, 'get_bool', side_effect=AssertionError('unexpected preference read')), \
|
|
patch.object(self.adapter._conditional, 'project') as conditional:
|
|
self.assertTrue(self.adapter.confirmed_offroad())
|
|
self.assertEqual(self.adapter._last_mode, ShellMode.ONROAD)
|
|
conditional.assert_not_called()
|
|
self.ui.sm['pandaStates'][0].ignitionCan = True
|
|
self.assertTrue(self.adapter.confirmed_offroad())
|
|
|
|
def test_forced_offroad_unlocks_settings_and_connectivity_with_ignition(self):
|
|
self.ui.sm['pandaStates'][0].ignitionCan = True
|
|
self.assertTrue(self.adapter.connectivity_allowed())
|
|
self.assertTrue(self.adapter.confirmed_offroad())
|
|
snapshot = self.adapter.build(ShellMode.SETTINGS, Destination.NETWORK, now_ns=self.mono)
|
|
self.assertTrue(snapshot.settings.destination(Destination.NETWORK).available)
|
|
self.assertTrue(snapshot.device.offroad)
|
|
self.ui.started = True
|
|
self.assertFalse(self.adapter.connectivity_allowed())
|
|
self.ui.started = False
|
|
self.ui.sm['deviceState'].started = True
|
|
self.assertFalse(self.adapter.connectivity_allowed())
|
|
|
|
def test_connectivity_rejects_invalid_stale_empty_and_future_sources(self):
|
|
self.ui.sm['pandaStates'][0].ignitionLine = True
|
|
self.assertTrue(self.adapter.connectivity_allowed())
|
|
sm = self.ui.sm
|
|
for service in ('deviceState', 'pandaStates'):
|
|
for field in ('seen', 'alive', 'valid'):
|
|
with self.subTest(service=service, field=field):
|
|
getattr(sm, field)[service] = False
|
|
self.assertFalse(self.adapter.connectivity_allowed())
|
|
getattr(sm, field)[service] = True
|
|
old = sm.logMonoTime[service]
|
|
sm.logMonoTime[service] -= 2_000_000_000
|
|
self.assertFalse(self.adapter.connectivity_allowed())
|
|
sm.logMonoTime[service] = old + 2_000_000_000
|
|
self.assertFalse(self.adapter.connectivity_allowed())
|
|
sm.logMonoTime[service] = old
|
|
pandas = sm.messages['pandaStates']
|
|
sm.messages['pandaStates'] = []
|
|
self.assertFalse(self.adapter.connectivity_allowed())
|
|
sm.messages['pandaStates'] = pandas
|
|
self.assertTrue(self.adapter.connectivity_allowed())
|
|
|
|
def test_connectivity_suspend_requires_sources_published_after_barrier(self):
|
|
self.ui.sm['pandaStates'][0].ignitionLine = True
|
|
self.assertTrue(self.adapter.connectivity_allowed())
|
|
self.boot += 10_000_000_000
|
|
self.ui.sm.updated['deviceState'] = False
|
|
self.ui.sm.updated['pandaStates'] = False
|
|
self.assertFalse(self.adapter.connectivity_allowed())
|
|
self.ui.sm.logMonoTime['pandaStates'] = self.boot - 20_000_000
|
|
self.ui.sm.recv_time['pandaStates'] = (self.mono - 10_000_000) / 1e9
|
|
self.assertFalse(self.adapter.connectivity_allowed())
|
|
self.mono += 100_000_000
|
|
self.boot += 100_000_000
|
|
self.refresh_both()
|
|
self.assertTrue(self.adapter.connectivity_allowed())
|
|
self.assertTrue(self.adapter.confirmed_offroad())
|
|
|
|
def test_fresh_publishers_use_their_own_clock_domains(self):
|
|
self.assertIsNotNone(current_message(self.ui.sm, "pandaStates", self.mono, boot_now_ns=self.boot))
|
|
self.assertTrue(self.parked())
|
|
self.assertIsNone(current_message(self.ui.sm, "pandaStates", self.mono, boot_now_ns=self.mono))
|
|
|
|
def test_suspend_denies_cached_evidence_until_both_sources_refresh(self):
|
|
self.assertTrue(self.parked())
|
|
self.boot += 10_000_000_000
|
|
self.ui.sm.updated["deviceState"] = False
|
|
self.ui.sm.updated["pandaStates"] = False
|
|
self.assertFalse(self.parked())
|
|
self.assertIsNone(current_message(self.ui.sm, "pandaStates", self.mono, boot_now_ns=self.boot))
|
|
self.ui.sm.logMonoTime["pandaStates"] = self.boot - 20_000_000
|
|
self.ui.sm.recv_time["pandaStates"] = (self.mono - 10_000_000) / 1e9
|
|
self.ui.sm.updated["pandaStates"] = True
|
|
self.assertFalse(self.parked())
|
|
self.mono += 100_000_000
|
|
self.boot += 100_000_000
|
|
self.ui.sm.logMonoTime["deviceState"] = self.mono - 20_000_000
|
|
self.ui.sm.recv_time["deviceState"] = (self.mono - 10_000_000) / 1e9
|
|
self.ui.sm.updated["deviceState"] = True
|
|
self.assertTrue(self.parked())
|
|
|
|
def test_missing_invalid_onroad_and_future_sources_deny(self):
|
|
self.assertTrue(self.parked())
|
|
cases = ((lambda: self.ui.sm.seen.__setitem__("pandaStates", False),
|
|
lambda: self.ui.sm.seen.__setitem__("pandaStates", True)),
|
|
(lambda: self.ui.sm.valid.__setitem__("deviceState", False),
|
|
lambda: self.ui.sm.valid.__setitem__("deviceState", True)),
|
|
(lambda: setattr(self.ui, "started", True), lambda: setattr(self.ui, "started", False)),
|
|
(lambda: self.ui.sm.logMonoTime.__setitem__("deviceState", self.mono + 1_000_000),
|
|
lambda: self.refresh_both()),
|
|
(lambda: self.ui.sm.logMonoTime.__setitem__("pandaStates", self.boot + 1_000_000),
|
|
lambda: self.refresh_both()))
|
|
for corrupt, restore in cases:
|
|
corrupt()
|
|
self.assertFalse(self.parked())
|
|
restore()
|
|
self.assertTrue(self.parked())
|
|
|
|
def test_failed_clock_pair_denies_and_requires_a_new_device_sample(self):
|
|
self.assertTrue(self.parked())
|
|
original = self.adapter._mono_clock
|
|
self.adapter._mono_clock = lambda: (_ for _ in ()).throw(OSError("clock unavailable"))
|
|
self.assertFalse(self.parked())
|
|
self.adapter._mono_clock = original
|
|
boot_clock = self.adapter._boot_clock
|
|
self.adapter._boot_clock = lambda: 0
|
|
self.assertFalse(self.parked())
|
|
self.adapter._boot_clock = boot_clock
|
|
self.assertFalse(self.parked())
|
|
self.mono += 100_000_000
|
|
self.boot += 100_000_000
|
|
self.refresh_both()
|
|
self.assertTrue(self.parked())
|
|
|
|
|
|
class TestHostOffroad(unittest.TestCase):
|
|
def test_only_private_parked_host_ui_has_authority_without_publishers(self):
|
|
env = {'SP_HOST_PARKED': '1', 'SP_HOST_RUNTIME': '1', 'OPENPILOT_PREFIX': 'starpilot-dev-test', 'SP_HOST_PREFIX': 'starpilot-dev-test'}
|
|
for overrides, expected in (({}, True), ({'SP_HOST_PARKED': '0'}, False), ({'SP_HOST_RUNTIME': '0'}, False),
|
|
({'SP_HOST_PREFIX': 'other'}, False), ({'OPENPILOT_PREFIX': ''}, False)):
|
|
with self.subTest(overrides=overrides), patch.dict('os.environ', {**env, **overrides}, clear=True):
|
|
ui = ui_fake()
|
|
ui.started = False
|
|
ui.sm = SubMasterFake()
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
self.assertEqual(adapter.confirmed_offroad(), expected)
|
|
self.assertEqual(adapter.build(ShellMode.HOME).device.offroad, expected)
|
|
ui.started = True
|
|
self.assertFalse(adapter.confirmed_offroad())
|
|
ui.started = False
|
|
ui.replay_clock = object()
|
|
self.assertFalse(adapter.confirmed_offroad())
|
|
self.assertFalse(adapter.build(ShellMode.HOME).device.offroad)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|
|
|
|
|
|
@pytest.mark.parametrize('phase,label,present', [('active', 'Jetlink Model', True),
|
|
('ready', 'Jetlink Model', True), ('connecting', '', True),
|
|
('disconnected', '', False), ('unavailable', '', False),
|
|
('off', '', False)])
|
|
def test_home_jetlink_model_name_and_gpu_share_existing_feed(phase, label, present):
|
|
from openpilot.starpilot.models.jetlink_status import JetlinkStatus
|
|
ui = ui_fake()
|
|
feed = Mock()
|
|
feed.snapshot.return_value = JetlinkStatus('Computer', phase, port='connected' if present else None)
|
|
ui.jetlink_status_feed = feed
|
|
ui.chestnut_state = NS(value='active' if phase == 'active' else 'loading')
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
with patch.object(adapter._jetlink, 'set_enabled') as enable:
|
|
home = adapter.build(ShellMode.HOME, now_ns=NOW).home
|
|
enable.assert_called_once_with(False)
|
|
assert home.model_label == label
|
|
assert home.gpu_present is present
|
|
assert home.gpu_active is (phase == 'active')
|