mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
251 lines
14 KiB
Python
251 lines
14 KiB
Python
"""Generic feature displays follow actual capability and fresh owner receipts."""
|
|
|
|
from dataclasses import replace
|
|
from pathlib import Path
|
|
from types import SimpleNamespace as NS
|
|
import tempfile
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
from opendbc.car import gen_empty_fingerprint
|
|
from opendbc.car.car_helpers import interfaces
|
|
from opendbc.car.ford.values import CAR as FordCar
|
|
from opendbc.car.honda.values import CAR as HondaCar
|
|
from opendbc.car.nissan.values import CAR as NissanCar
|
|
from opendbc.car.toyota.values import CAR as ToyotaCar
|
|
from opendbc.car.volkswagen.interface import CarInterface as VolkswagenInterface
|
|
from opendbc.car.volkswagen.values import CAR as VolkswagenCar
|
|
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.aol.vehicle import policy_for
|
|
from openpilot.starpilot.controllers.tests.test_wheel_actions import Publisher, services, state, TYPE
|
|
from openpilot.starpilot.controllers.wheel_actions import ACTIONS, WheelConsumer, WheelPublisher
|
|
from openpilot.starpilot.galaxy.settings import AuthorityContext, _qualified
|
|
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner, LANE_LIVE_KEYS
|
|
from openpilot.starpilot.ui.feature_settings_state import FeatureSettingsRequest, row_change
|
|
from openpilot.starpilot.ui.runtime_snapshot import RuntimeSnapshotAdapter
|
|
from openpilot.starpilot.ui.shell import ShellMode
|
|
from openpilot.starpilot.ui.tests.test_runtime_snapshot import BOOT_OFFSET_NS, ui_fake
|
|
from openpilot.starpilot.ui.tests.test_traffic_status import BOOT, DRIVE, NOW, receipt
|
|
from openpilot.starpilot.ui.wheel_feature import WheelFeature
|
|
|
|
|
|
def params_for(candidate):
|
|
return interfaces[candidate].get_non_essential_params(candidate)
|
|
|
|
|
|
class TestGlobalFeaturePresentation(unittest.TestCase):
|
|
def setUp(self):
|
|
directory = tempfile.TemporaryDirectory()
|
|
self.addCleanup(directory.cleanup)
|
|
self.params = Params(directory.name)
|
|
|
|
def traffic_ui(self, cp, *, controller_source):
|
|
ui = ui_fake()
|
|
ui.CP, ui.params = cp, self.params
|
|
ui.sm['deviceState'].startedMonoTime = DRIVE
|
|
ui.sm['carState'].canValid, ui.sm['carState'].canTimeout = True, False
|
|
ui.sm['carControl'].longActive = True
|
|
ui.sm['selfdriveState'].enabled = True
|
|
ui.sm.put('controlsState', NS(longControlState='pid'))
|
|
self.params.put('DistanceButtonControl', 6, block=True)
|
|
snapshot = ConditionalSettingsOwner(self.params).refresh(NOW)
|
|
mapping = read_button_map(self.params, include_ioniq_media=True, cp=cp)
|
|
self.assertIsNotNone(mapping)
|
|
event = receipt(settings_fingerprint(snapshot), '' if controller_source else mapping.fingerprint()).as_builder()
|
|
event.slcState.trafficMode.controllerSource = controller_source
|
|
if controller_source:
|
|
event.slcState.trafficMode.sourceBootTime = 0
|
|
ui.sm.put('slcState', messaging.log_from_bytes(event.to_bytes()).slcState)
|
|
# SLC is an independent owner. Its validity cannot veto a valid nested Traffic receipt.
|
|
ui.sm.valid['slcState'] = False
|
|
return ui
|
|
|
|
def test_actual_toyota_and_pid_honda_traffic_receipts_reach_onroad_state(self):
|
|
for candidate in (ToyotaCar.TOYOTA_RAV4_TSS2, HondaCar.HONDA_CIVIC):
|
|
for controller_source in (False, True):
|
|
with self.subTest(candidate=candidate, controller_source=controller_source):
|
|
cp = params_for(candidate)
|
|
self.assertTrue(cp.openpilotLongitudinalControl)
|
|
ui = self.traffic_ui(cp, controller_source=controller_source)
|
|
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)):
|
|
state = adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad
|
|
self.assertTrue(state.traffic_mode)
|
|
self.assertEqual((state.traffic_display.state, state.traffic_display.label), ('active', 'TRAFFIC'))
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW + 100_000_001).onroad.traffic_mode)
|
|
|
|
def test_traffic_never_claims_active_without_capability_or_current_effective_control(self):
|
|
for changed in ('stock_long', 'passive', 'dashcam', 'not_car', 'empty_identity', 'missing_cp',
|
|
'long_inactive', 'selfdrive_disabled', 'invalid_can', 'ineffective', 'wrong_settings', 'wrong_drive'):
|
|
with self.subTest(changed=changed):
|
|
cp = params_for(ToyotaCar.TOYOTA_RAV4_TSS2)
|
|
ui = self.traffic_ui(cp, controller_source=True)
|
|
if changed == 'stock_long':
|
|
cp.openpilotLongitudinalControl = False
|
|
elif changed == 'passive':
|
|
cp.passive = True
|
|
elif changed == 'dashcam':
|
|
cp.dashcamOnly = True
|
|
elif changed == 'not_car':
|
|
cp.notCar = True
|
|
elif changed == 'empty_identity':
|
|
cp.carFingerprint = ''
|
|
elif changed == 'missing_cp':
|
|
ui.CP = None
|
|
elif changed == 'long_inactive':
|
|
ui.sm['carControl'].longActive = False
|
|
elif changed == 'selfdrive_disabled':
|
|
ui.sm['selfdriveState'].enabled = False
|
|
elif changed == 'invalid_can':
|
|
ui.sm['carState'].canValid = False
|
|
else:
|
|
event = messaging.new_message('slcState', valid=False)
|
|
event.slcState = ui.sm['slcState'].to_dict()
|
|
if changed == 'ineffective':
|
|
event.slcState.trafficMode.effective = False
|
|
elif changed == 'wrong_settings':
|
|
event.slcState.trafficMode.settingsFingerprint = 'b' * 64
|
|
else:
|
|
event.slcState.trafficMode.driveStartMonoTime = DRIVE + 1
|
|
ui.sm.put('slcState', messaging.log_from_bytes(event.to_bytes()).slcState)
|
|
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)):
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.traffic_mode)
|
|
|
|
def test_switchback_badge_and_settings_are_generic_lateral_with_original_receipt_expiry(self):
|
|
for candidate in (ToyotaCar.TOYOTA_RAV4_TSS2, HondaCar.HONDA_CIVIC, FordCar.FORD_ESCAPE_MK4):
|
|
with self.subTest(candidate=candidate):
|
|
cp = params_for(candidate)
|
|
ui = ui_fake()
|
|
ui.CP = cp
|
|
ui.sm['deviceState'].startedMonoTime = DRIVE
|
|
ui.sm['carState'].canValid, ui.sm['carState'].canTimeout = True, False
|
|
event = messaging.new_message('slcState', valid=True, logMonoTime=NOW)
|
|
event.slcState.switchbackMode = {
|
|
'version': 1, 'sessionId': 'c' * 32, 'sequence': 1,
|
|
'observedMonoTime': NOW, 'validUntilMonoTime': NOW + 100_000_000,
|
|
'driveStartMonoTime': DRIVE, 'requested': True, 'effective': True,
|
|
'sourceCarControlMonoTime': NOW,
|
|
}
|
|
ui.sm.put('slcState', messaging.log_from_bytes(event.to_bytes()).slcState)
|
|
adapter = RuntimeSnapshotAdapter(ui)
|
|
self.assertTrue(adapter.build(ShellMode.ONROAD, now_ns=NOW).onroad.switchback_mode)
|
|
self.assertFalse(adapter.build(ShellMode.ONROAD, now_ns=NOW + 100_000_001).onroad.switchback_mode)
|
|
self.assertTrue(_qualified(AuthorityContext(False, cp, None), 'switchback_wheel'))
|
|
cp.passive = True
|
|
self.assertFalse(_qualified(AuthorityContext(False, cp, None), 'switchback_wheel'))
|
|
|
|
def test_every_lane_control_matches_runtime_and_rechecks_before_writing(self):
|
|
cp = params_for(ToyotaCar.TOYOTA_RAV4_TSS2)
|
|
owner = FeatureSettingsOwner(self.params, lambda _group: True,
|
|
vehicle_fingerprint=lambda: cp.carFingerprint, vehicle_params=lambda: cp)
|
|
rows = owner.snapshot('lane', parked=True, system_long=True, lateral_context=True, metric=False).rows
|
|
row = next(row for row in rows if row.key == 'LaneCentering')
|
|
request = row_change(row, 1)
|
|
self.assertIsNotNone(request)
|
|
assert request is not None
|
|
self.assertTrue(row.available)
|
|
self.assertTrue(owner.apply(request))
|
|
self.assertTrue(self.params.get_bool('LaneCentering'))
|
|
rows = owner.snapshot('lane', parked=True, system_long=True, lateral_context=True, metric=False).rows
|
|
self.assertTrue(all(row.available for row in rows if row.key in LANE_LIVE_KEYS))
|
|
requests = [FeatureSettingsRequest(row.key, row.source, row.value, vehicle_fingerprint=row.vehicle_fingerprint)
|
|
for row in rows if row.key in LANE_LIVE_KEYS]
|
|
before = {key: Path(self.params.get_param_path(key)).read_bytes()
|
|
if Path(self.params.get_param_path(key)).exists() else None for key in LANE_LIVE_KEYS}
|
|
cp.dashcamOnly = True
|
|
blocked = owner.snapshot('lane', parked=True, system_long=True, lateral_context=True, metric=False).rows
|
|
self.assertTrue(all(not row.available for row in blocked if row.key in LANE_LIVE_KEYS))
|
|
self.assertTrue(all(not owner.apply(request) for request in requests))
|
|
after = {key: Path(self.params.get_param_path(key)).read_bytes()
|
|
if Path(self.params.get_param_path(key)).exists() else None for key in LANE_LIVE_KEYS}
|
|
self.assertEqual(before, after)
|
|
|
|
def test_reported_distance_actions_do_not_depend_on_aol_policy(self):
|
|
nissan = params_for(NissanCar.NISSAN_ROGUE)
|
|
fingerprint = gen_empty_fingerprint()
|
|
fingerprint[1][0x40] = 8
|
|
volkswagen = VolkswagenInterface.get_params(VolkswagenCar.VOLKSWAGEN_GOLF_MK7,
|
|
fingerprint, [], True, False, False)
|
|
self.assertFalse(nissan.openpilotLongitudinalControl)
|
|
self.assertTrue(volkswagen.alphaLongitudinalAvailable)
|
|
self.assertTrue(volkswagen.openpilotLongitudinalControl)
|
|
for cp, options in ((nissan, ('Bookmark', 'Switchback Mode')),
|
|
(volkswagen, ('Bookmark', 'Cycle personality', 'Experimental override', 'Traffic mode', 'Switchback Mode'))):
|
|
with self.subTest(candidate=cp.carFingerprint):
|
|
actual_policy = policy_for(cp)
|
|
self.assertTrue(actual_policy.settings_supported)
|
|
for settings_supported in (False, True):
|
|
with self.subTest(settings_supported=settings_supported), mock.patch(
|
|
"openpilot.starpilot.ui.wheel_feature.policy_for",
|
|
return_value=replace(actual_policy, settings_supported=settings_supported)):
|
|
|
|
owner = FeatureSettingsOwner(self.params, lambda _group: True,
|
|
vehicle_fingerprint=lambda cp=cp: cp.carFingerprint, vehicle_params=lambda cp=cp: cp)
|
|
wheel = WheelFeature(owner)
|
|
choices = wheel.choices('DistanceButtonControl')
|
|
self.assertTrue(set(options).issubset(choices))
|
|
aol_actions = {'Pause steering', 'Pause longitudinal', 'Toggle AOL'}
|
|
if settings_supported:
|
|
self.assertTrue(aol_actions.issubset(choices))
|
|
else:
|
|
self.assertTrue(aol_actions.isdisjoint(choices))
|
|
self.assertEqual(wheel.choices('LKASButtonControl'), ())
|
|
self.assertEqual(wheel.choices('MainCruiseButtonControl'), ())
|
|
self.assertEqual(wheel.choices('ModeButtonControl'), ())
|
|
self.assertEqual(wheel.choices('StarButtonControl'), ())
|
|
for option in options:
|
|
with self.subTest(option=option):
|
|
row = next(row for row in wheel.rows() if row.key == 'wheel:DistanceButtonControl')
|
|
request = FeatureSettingsRequest(row.key, row.source, option, capability=row.capability)
|
|
self.assertTrue(wheel.apply(request))
|
|
publisher = WheelPublisher(self.params)
|
|
publisher.observe(self.params, cp, state(), now_ns=NOW, drive_id=DRIVE)
|
|
publisher.observe(self.params, cp, state(True), now_ns=NOW + 10_000_000, drive_id=DRIVE)
|
|
when = NOW + 20_000_000
|
|
commands = publisher.observe(self.params, cp, state(False), now_ns=when, drive_id=DRIVE)
|
|
self.assertEqual(commands, (('DistanceButtonControl', ACTIONS[option]),))
|
|
output = Publisher()
|
|
publisher.publish(commands, cp, output, now_ns=when, drive_id=DRIVE,
|
|
source_car_ns=when, source_control_ns=when)
|
|
event = messaging.log_from_bytes(output.events[0][1])
|
|
sm = services(cp, now=when)
|
|
# Use this test's drive identifier in the exact original wire checks.
|
|
sm['deviceState'].startedMonoTime = DRIVE
|
|
command = WheelConsumer().accept(event, self.params, cp, sm, now_ns=when)
|
|
self.assertIsNotNone(command)
|
|
self.assertEqual((command.key, command.action), ('DistanceButtonControl', ACTIONS[option]))
|
|
self.params.put('DistanceButtonControl', 0, block=True)
|
|
self.assertIsNone(WheelConsumer().accept(event, self.params, cp, sm, now_ns=when))
|
|
|
|
def test_toyota_lkas_generic_bookmark_still_preserves_fixed_main_and_absent_cancel(self):
|
|
cp = params_for(ToyotaCar.TOYOTA_RAV4_TSS2)
|
|
owner = FeatureSettingsOwner(self.params, lambda _group: True,
|
|
vehicle_fingerprint=lambda: cp.carFingerprint, vehicle_params=lambda: cp)
|
|
wheel = WheelFeature(owner)
|
|
self.assertIn('Bookmark', wheel.choices('LKASButtonControl'))
|
|
self.assertEqual(wheel.choices('MainCruiseButtonControl'), ())
|
|
self.assertEqual(wheel.choices('CancelButtonControl'), ())
|
|
row = next(row for row in wheel.rows() if row.key == 'wheel:LKASButtonControl')
|
|
self.assertTrue(wheel.apply(FeatureSettingsRequest(row.key, row.source, 'Bookmark', capability=row.capability)))
|
|
publisher = WheelPublisher(self.params)
|
|
publisher.observe(self.params, cp, state(), now_ns=NOW, drive_id=DRIVE)
|
|
when = NOW + 10_000_000
|
|
commands = publisher.observe(self.params, cp, state(True, button=TYPE.lkas), now_ns=when, drive_id=DRIVE)
|
|
self.assertEqual(commands, (('LKASButtonControl', 8),))
|
|
output = Publisher()
|
|
publisher.publish(commands, cp, output, now_ns=when, drive_id=DRIVE,
|
|
source_car_ns=when, source_control_ns=when)
|
|
sm = services(cp, now=when)
|
|
sm['deviceState'].startedMonoTime = DRIVE
|
|
event = messaging.log_from_bytes(output.events[0][1])
|
|
self.assertEqual(WheelConsumer().accept(event, self.params, cp, sm, now_ns=when).action, 8)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|