mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-10 14:33:42 +08:00
a82935f326
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>
274 lines
12 KiB
Python
274 lines
12 KiB
Python
from types import SimpleNamespace as NS
|
|
from unittest.mock import Mock
|
|
import pytest
|
|
from openpilot.starpilot.drive_state.owner import DriveStateOwner, Mode, Rejected
|
|
from openpilot.starpilot.drive_state.control import DriveStateControl
|
|
from openpilot.starpilot.drive_state.tests.test_owner import Params, BOOT
|
|
|
|
|
|
@pytest.fixture
|
|
def fixture(tmp_path):
|
|
params = Params()
|
|
owner = DriveStateOwner(params, tmp_path / 'lock', alive=lambda _: True)
|
|
owner.initialize(pid=1, birth=2, boot=BOOT)
|
|
physical = NS(allowed=Mock(return_value=True))
|
|
now = [0.0]
|
|
control = DriveStateControl(owner, physical, effective=lambda: False, clock=lambda: now[0])
|
|
return NS(owner=owner, control=control, physical=physical, now=now)
|
|
|
|
|
|
def test_requested_state_does_not_claim_effective_started_and_status_reads_bounded(fixture):
|
|
f = fixture
|
|
f.owner.snapshot = Mock(wraps=f.owner.snapshot)
|
|
status = f.control.snapshot()
|
|
f.control.cycle(status, lambda: True)
|
|
status = f.control.snapshot()
|
|
assert status['mode'] == 'offroad' and status['effective'] == 'offroad'
|
|
f.control.cycle(status, lambda: True)
|
|
status = f.control.snapshot()
|
|
assert status['mode'] == 'onroad' and status['effective'] == 'offroad'
|
|
calls = f.owner.snapshot.call_count
|
|
for _ in range(100):
|
|
f.control.snapshot()
|
|
assert f.owner.snapshot.call_count == calls
|
|
f.now[0] += 0.5
|
|
f.control.snapshot()
|
|
assert f.owner.snapshot.call_count == calls + 1
|
|
|
|
|
|
def test_small_cycle_and_expired_physical_source_explicit_auto_recovery(fixture):
|
|
f = fixture
|
|
for mode in ('offroad', 'onroad', 'auto'):
|
|
status = f.control.snapshot()
|
|
assert f.control.next_mode(status).value == mode
|
|
f.control.cycle(status, lambda: True)
|
|
f.control.cycle(f.control.snapshot(), lambda: True)
|
|
f.physical.allowed.return_value = False
|
|
f.now[0] += 0.5
|
|
status = f.control.snapshot()
|
|
assert status['mode'] == 'offroad' and f.control.next_mode(status) == Mode.ONROAD
|
|
f.control.cycle(status, lambda: True)
|
|
assert f.control.snapshot()['mode'] == 'onroad'
|
|
f.control.cycle(f.control.snapshot(), lambda: True)
|
|
assert f.control.snapshot()['mode'] == 'auto'
|
|
|
|
|
|
def test_local_offroad_ignores_park_admission_but_rechecks_authorization(fixture):
|
|
f = fixture
|
|
status = f.control.snapshot()
|
|
f.physical.allowed.return_value = False
|
|
f.control.change('offroad', status['revision'], lambda: True)
|
|
assert f.owner.snapshot().mode == Mode.OFFROAD
|
|
status = f.control.snapshot()
|
|
with pytest.raises(Rejected):
|
|
f.control.cycle(status, lambda: False)
|
|
assert f.owner.snapshot().mode == Mode.OFFROAD
|
|
|
|
|
|
def test_actual_native_small_and_big_dispatch_same_owner(fixture, monkeypatch):
|
|
from openpilot.starpilot.ui.runtime_app import StarShellSession
|
|
from openpilot.starpilot.ui.shell import ShellMode, ShellRequest
|
|
from openpilot.starpilot.ui.presentation import Profile
|
|
from openpilot.starpilot.ui.settings_state import (
|
|
Destination, DestinationAvailability, SettingsAction, SettingsActionKind,
|
|
)
|
|
from openpilot.starpilot.ui.feature_settings_state import (
|
|
FeatureInput, FeatureRow, FeatureSettingsState, FEATURE_ROW_HEIGHT, feature_row_top, feature_action_rects,
|
|
)
|
|
from openpilot.starpilot.ui.display_preferences import MASTER
|
|
|
|
f = fixture
|
|
native = object.__new__(StarShellSession)
|
|
native.drive_state = f.control
|
|
native.profile = Profile.COMPACT
|
|
native._mode = ShellMode.SETTINGS
|
|
native.selected = Destination.STAR
|
|
native._snapshot_cache = None
|
|
native._emit(
|
|
ShellRequest(
|
|
'settings',
|
|
SettingsAction(
|
|
SettingsActionKind.REQUEST_DESTINATION,
|
|
DestinationAvailability(Destination.FORCE_DRIVE, True, request_value="offroad", request_revision=f.owner.snapshot().revision),
|
|
),
|
|
)
|
|
)
|
|
assert f.owner.snapshot().mode == Mode.OFFROAD
|
|
native.profile = Profile.LARGE
|
|
native.selected = Destination.SYSTEM
|
|
monkeypatch.setattr(native, 'display_snapshot', lambda: FeatureSettingsState(rows=(
|
|
FeatureRow(MASTER, 'Custom Display Settings', 'Off', choices=('Off', 'On')),
|
|
)), raising=False)
|
|
monkeypatch.setattr(native, 'power_snapshot', lambda: FeatureSettingsState(rows=()), raising=False)
|
|
monkeypatch.setattr(native, 'map_snapshot', lambda: NS(rows=()), raising=False)
|
|
inputs = FeatureInput(native._display_ui)
|
|
for index, expected in ((0, Mode.AUTO), (2, Mode.ONROAD), (0, Mode.AUTO)):
|
|
state = native.system_snapshot()
|
|
row_index, row = next((i, row) for i, row in enumerate(state.rows) if row.key == 'drive_state')
|
|
assert row.value.lower() == f.owner.snapshot().mode.value
|
|
assert row.source.decode() == f.owner.snapshot().revision
|
|
x, y, width, height = feature_action_rects(row, feature_row_top(state) + row_index * FEATURE_ROW_HEIGHT)[index]
|
|
inputs.press(x + width / 2, y + height / 2, state)
|
|
inputs.release(x + width / 2, y + height / 2, state)
|
|
assert f.owner.snapshot().mode == expected
|
|
assert not row.choices # Large UI directly selects a mode; Compact retains its cycle action.
|
|
|
|
|
|
def test_actual_settings_gesture_tracks_pipeline_epoch_not_physical_parked_flag(monkeypatch):
|
|
from unittest.mock import patch
|
|
from openpilot.starpilot.ui.runtime_app import StarShellSession
|
|
from openpilot.starpilot.ui.settings_state import Destination
|
|
|
|
native = object.__new__(StarShellSession)
|
|
native.selected = Destination.STAR
|
|
native.cancel = Mock()
|
|
monkeypatch.setattr(native, 'confirmed_offroad', lambda: False, raising=False)
|
|
snapshot = NS(selected=Destination.STAR, device=NS(offroad=False))
|
|
monkeypatch.setattr(native, '_settings_touch', snapshot, raising=False)
|
|
native._settings_pipeline = (False, 10)
|
|
fake = NS(started=False, started_frame=10, is_offroad=lambda: True)
|
|
with patch('openpilot.starpilot.ui.runtime_app.ui_state', fake):
|
|
assert native._settings_gesture_snapshot() is snapshot
|
|
# Even an Off->On->Off sequence changes the existing drive epoch.
|
|
fake.started_frame = 20
|
|
assert native._settings_gesture_snapshot() is None
|
|
fake.started = True
|
|
native._settings_pipeline = (True, 20)
|
|
assert native._settings_gesture_snapshot() is snapshot
|
|
# Ordinary onroad setting gesture remains valid; parked authority still revokes.
|
|
snapshot.device.offroad = True
|
|
assert native._settings_gesture_snapshot() is None
|
|
|
|
|
|
def test_small_rendered_action_revision_is_bound_not_reinterpreted(fixture):
|
|
from openpilot.starpilot.ui.runtime_app import StarShellSession
|
|
from openpilot.starpilot.ui.shell import ShellMode, ShellRequest
|
|
from openpilot.starpilot.ui.presentation import Profile
|
|
from openpilot.starpilot.ui.settings_state import Destination, DestinationAvailability, SettingsAction, SettingsActionKind
|
|
|
|
f = fixture
|
|
old = f.control.snapshot()
|
|
action = SettingsAction(
|
|
SettingsActionKind.REQUEST_DESTINATION, DestinationAvailability(Destination.FORCE_DRIVE, True, request_value='offroad', request_revision=old['revision'])
|
|
)
|
|
f.control.change('onroad', old['revision'], lambda: True)
|
|
native = object.__new__(StarShellSession)
|
|
native.drive_state = f.control
|
|
native.profile = Profile.COMPACT
|
|
native._mode = ShellMode.SETTINGS
|
|
native.selected = Destination.STAR
|
|
native._snapshot_cache = None
|
|
native._emit(ShellRequest('settings', action))
|
|
assert f.owner.snapshot().mode == Mode.ONROAD
|
|
assert 'refresh' in native.notice
|
|
|
|
|
|
def test_actual_manager_process_predicate_stops_park_producers_and_auto_recovers(fixture):
|
|
from openpilot.starpilot.drive_state.tests.test_evidence import Messages
|
|
from openpilot.starpilot.drive_state.evidence import PhysicalSource
|
|
from openpilot.system.manager.process_config import procs, only_onroad
|
|
|
|
assert only_onroad(True, fixture.owner.params, None)
|
|
assert not only_onroad(False, fixture.owner.params, None)
|
|
processes = {process.name: process for process in procs}
|
|
assert processes['card'].should_run is only_onroad
|
|
assert processes['selfdrived'].should_run is only_onroad
|
|
messages = Messages()
|
|
now = [1_000_000_000]
|
|
physical = PhysicalSource(messages, mono=lambda: now[0], boot=lambda: now[0] + 10_000_000_000)
|
|
now[0] = 2_100_000_000
|
|
pandas = messages.values['pandaStates']
|
|
assert isinstance(pandas, list)
|
|
pandas[0].ignitionLine = True
|
|
control = DriveStateControl(fixture.owner, physical, effective=lambda: False, clock=lambda: now[0] / 1e9)
|
|
control.cycle(control.snapshot(), lambda: True)
|
|
now[0] += 500_000_000
|
|
messages.logMonoTime['pandaStates'] = now[0] + 10_000_000_000
|
|
messages.recv_time['pandaStates'] = now[0] / 1e9
|
|
status = control.snapshot()
|
|
assert not status['overrideAllowed'] and control.next_mode(status) == Mode.ONROAD
|
|
control.cycle(status, lambda: True)
|
|
assert fixture.owner.snapshot().mode == Mode.ONROAD
|
|
control.change('auto', fixture.owner.snapshot().revision, lambda: True)
|
|
assert fixture.owner.snapshot().mode == Mode.AUTO
|
|
|
|
|
|
def test_concurrent_old_snapshot_cannot_repopulate_cache_after_acknowledged_change(fixture):
|
|
import threading
|
|
|
|
f = fixture
|
|
read, release, committed = threading.Event(), threading.Event(), threading.Event()
|
|
original_snapshot = f.owner.snapshot
|
|
original_request = f.owner.request
|
|
first = [True]
|
|
|
|
def held_snapshot():
|
|
state = original_snapshot()
|
|
if first[0]:
|
|
first[0] = False
|
|
read.set()
|
|
assert release.wait(2)
|
|
return state
|
|
|
|
def request(*args, **kwargs):
|
|
state = original_request(*args, **kwargs)
|
|
committed.set()
|
|
return state
|
|
|
|
revision = original_snapshot().revision
|
|
f.owner.snapshot = held_snapshot
|
|
f.owner.request = request
|
|
results = []
|
|
reader = threading.Thread(target=lambda: results.append(f.control.snapshot()))
|
|
writer = threading.Thread(target=lambda: f.control.change('onroad', revision, lambda: True))
|
|
reader.start()
|
|
assert read.wait(2)
|
|
writer.start()
|
|
assert committed.wait(2) # Owner writes do not join the snapshot/cache lock.
|
|
release.set()
|
|
reader.join(2)
|
|
writer.join(2)
|
|
assert not reader.is_alive() and not writer.is_alive()
|
|
assert results[0]['mode'] == 'auto'
|
|
assert f.control.snapshot()['mode'] == 'onroad'
|
|
|
|
|
|
def test_native_offroad_confirmation_preserves_revision_without_park_admission(fixture):
|
|
from unittest.mock import patch
|
|
from openpilot.starpilot.ui.runtime_app import StarShellSession
|
|
from openpilot.starpilot.ui.shell import ShellMode
|
|
from openpilot.starpilot.ui.settings_state import Destination
|
|
from openpilot.system.ui.widgets import DialogResult
|
|
|
|
f = fixture
|
|
native = object.__new__(StarShellSession)
|
|
native.drive_state = f.control
|
|
from openpilot.starpilot.ui.presentation import Profile
|
|
native.profile = Profile.LARGE
|
|
native._mode = ShellMode.SETTINGS
|
|
native.selected = Destination.SYSTEM
|
|
native._snapshot_cache = None
|
|
fake = NS(started=True, started_frame=10)
|
|
revision = f.owner.snapshot().revision
|
|
with patch('openpilot.starpilot.ui.runtime_app.ui_state', fake), \
|
|
patch('openpilot.system.ui.widgets.confirm_dialog.ConfirmDialog',
|
|
side_effect=lambda *args, callback, **kwargs: NS(_callback=callback)), \
|
|
patch('openpilot.starpilot.ui.runtime_app.gui_app.push_widget') as push:
|
|
native._drive_change('offroad', revision)
|
|
assert f.owner.snapshot().mode == Mode.AUTO
|
|
dialog = push.call_args.args[0]
|
|
dialog._callback(DialogResult.CANCEL)
|
|
assert f.owner.snapshot().mode == Mode.AUTO
|
|
fake.started_frame = 11
|
|
dialog._callback(DialogResult.CONFIRM)
|
|
assert f.owner.snapshot().mode == Mode.AUTO
|
|
native._drive_change('offroad', revision)
|
|
native.display_page = 'display'
|
|
push.call_args.args[0]._callback(DialogResult.CONFIRM)
|
|
assert f.owner.snapshot().mode == Mode.AUTO
|
|
native.display_page = 'system'
|
|
native._drive_change('offroad', revision)
|
|
f.physical.allowed.return_value = False
|
|
push.call_args.args[0]._callback(DialogResult.CONFIRM)
|
|
assert f.owner.snapshot().mode == Mode.OFFROAD
|