Files
StarPilot/openpilot/starpilot/conditional_mode/tests/test_button_actions.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

188 lines
9.5 KiB
Python

"""Real disposable Params tests for the six saved Ioniq media assignments."""
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from opendbc.car.car_helpers import interfaces
from opendbc.car.hyundai.values import CAR, HyundaiFlags
from openpilot.common.params import Params
from openpilot.starpilot.conditional_mode.button_actions import (
CONFIG_KEY, MEDIA_KEYS, RUNTIME_KEYS, SAFE_KEY, capture_sources, commit_assignment,
display_action, media_capability, runtime_map_ready,
)
from openpilot.starpilot.conditional_mode.manual import read_button_map
from openpilot.starpilot.saved_source import read_saved
def qualified_cp():
cp = interfaces[CAR.HYUNDAI_IONIQ_6].get_non_essential_params(CAR.HYUNDAI_IONIQ_6)
cp.flags = int(cp.flags | HyundaiFlags.CANFD_LKA_STEER_MSG)
cp.openpilotLongitudinalControl = True
return cp
class ButtonActionsTests(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.params = Params(temporary.name)
self.parked = True
self.cp = qualified_cp()
def snapshot(self):
config, _ = read_saved(self.params, CONFIG_KEY, 4096)
safe, _ = read_saved(self.params, SAFE_KEY, 8)
return capture_sources(self.params, config, safe)
def write(self, key, choice, sources=None):
sources = sources or self.snapshot()
cp = media_capability(self.cp)
return commit_assignment(self.params, key=key, choice=choice, expected=sources.raw(key),
dependencies=sources.dependencies,
authorized=lambda: self.parked and cp is not None and media_capability(self.cp) == cp)
def test_exact_platform_and_all_six_real_saved_actions(self):
self.assertIsNotNone(media_capability(self.cp))
wrong = qualified_cp()
wrong.flags = int(wrong.flags & ~HyundaiFlags.CANFD_LKA_STEER_MSG)
self.assertIsNone(media_capability(wrong))
wrong = qualified_cp()
wrong.passive = True
self.assertIsNone(media_capability(wrong))
self.assertTrue(runtime_map_ready(self.snapshot()))
for key in MEDIA_KEYS:
with self.subTest(key=key):
self.assertTrue(self.write(key, "Cycle conditional mode").verified)
self.assertEqual(read_saved(self.params, key, 8), (b"5", True))
self.assertEqual(getattr(read_button_map(self.params, include_ioniq_media=True),
{"ModeButtonControl": "mode", "LongModeButtonControl": "mode_long",
"VeryLongModeButtonControl": "mode_very_long", "StarButtonControl": "custom",
"LongStarButtonControl": "custom_long",
"VeryLongStarButtonControl": "custom_very_long"}[key]), 5)
self.assertTrue(self.write(key, "Off").verified)
self.assertEqual(read_saved(self.params, key, 8), (b"0", True))
self.assertTrue(self.write(key, "Toggle traffic mode").verified)
self.assertEqual(read_saved(self.params, key, 8), (b"6", True))
self.assertEqual(display_action(b"6")[:2], ("Toggle traffic mode", ("Off", "Cycle conditional mode", "Toggle traffic mode", "Switchback Mode")))
self.assertTrue(self.write(key, "Off").verified)
def test_corrupt_or_unsupported_saved_value_requires_explicit_repair(self):
key = MEDIA_KEYS[0]
Path(self.params.get_param_path(key)).write_bytes(b"05")
self.assertEqual(display_action(self.snapshot().raw(key)), ("Unsupported saved action", (), "Off"))
self.assertFalse(self.write(key, "Cycle conditional mode").committed)
self.assertTrue(self.write(key, "Off").verified)
Path(self.params.get_param_path(key)).write_bytes(b"14")
self.assertEqual(display_action(self.snapshot().raw(key))[2], "Off")
self.assertFalse(self.write(key, "Cycle conditional mode").committed)
self.assertTrue(self.write(key, "Off").verified)
self.assertFalse(self.write(key, "Pause longitudinal").committed)
self.assertFalse(self.write("CancelButtonControl", "Off").committed)
def test_invalid_saved_config_or_safe_mode_cannot_bypass_page(self):
key = MEDIA_KEYS[0]
Path(self.params.get_param_path(CONFIG_KEY)).write_bytes(b'{"version":9}')
self.assertFalse(self.write(key, "Off").committed)
self.assertFalse(self.write(key, "Cycle conditional mode").committed)
Path(self.params.get_param_path(CONFIG_KEY)).unlink()
Path(self.params.get_param_path(SAFE_KEY)).write_bytes(b"yes")
self.assertFalse(self.write(key, "Off").committed)
def test_exact_sources_parked_cp_and_cancel_dependency(self):
key = MEDIA_KEYS[0]
captured = self.snapshot()
Path(self.params.get_param_path("LongStarButtonControl")).write_bytes(b"5")
self.assertFalse(self.write(key, "Cycle conditional mode", captured).committed)
self.assertIsNone(read_saved(self.params, key, 8)[0])
captured = self.snapshot()
Path(self.params.get_param_path("CancelButtonControl")).write_bytes(b"5")
self.assertFalse(runtime_map_ready(self.snapshot()))
self.assertIsNone(read_button_map(self.params, include_ioniq_media=True))
self.assertFalse(self.write(key, "Cycle conditional mode", captured).committed)
captured = self.snapshot()
self.params.put_bool(SAFE_KEY, True, block=True)
self.assertFalse(self.write(key, "Cycle conditional mode", captured).committed)
self.parked = False
self.assertFalse(self.write(key, "Cycle conditional mode").committed)
self.parked = True
self.cp.flags = int(self.cp.flags & ~HyundaiFlags.CANFD_LKA_STEER_MSG)
self.assertFalse(self.write(key, "Cycle conditional mode").committed)
def test_unreadable_source_and_commit_uncertainty(self):
key = MEDIA_KEYS[0]
path = Path(self.params.get_param_path(key))
path.write_bytes(b"5" * 9)
self.assertFalse(self.snapshot().readable)
self.assertFalse(self.write(key, "Off").committed)
self.assertEqual(path.read_bytes(), b"5" * 9)
path.write_bytes(b"0")
from openpilot.starpilot import saved_document
with patch.object(saved_document.os, "fsync", side_effect=[None, OSError("post-rename failure")]):
result = self.write(key, "Cycle conditional mode")
self.assertTrue(result.committed)
self.assertFalse(result.verified)
self.assertEqual(path.read_bytes(), b"5")
def test_all_dependencies_captured_once_and_nonregular_source(self):
sources = self.snapshot()
self.assertEqual(tuple(name for name, _ in sources.dependencies), (CONFIG_KEY, SAFE_KEY, *RUNTIME_KEYS))
key = MEDIA_KEYS[0]
Path(self.params.get_param_path(key)).mkdir()
self.assertFalse(self.snapshot().readable)
self.assertFalse(self.write(key, "Cycle conditional mode").committed)
def test_stage_to_lock_rechecks_parked_cp_and_config(self):
from openpilot.starpilot import saved_document
key = MEDIA_KEYS[0]
original_fsync = saved_document.os.fsync
for change in ("parked", "cp", "config"):
with self.subTest(change=change):
self.parked = True
self.cp = qualified_cp()
config_path = Path(self.params.get_param_path(CONFIG_KEY))
config_path.unlink(missing_ok=True)
def change_at_stage(fd, scenario=change, path=config_path):
result = original_fsync(fd)
if scenario == "parked":
self.parked = False
elif scenario == "cp":
self.cp.flags = int(self.cp.flags & ~HyundaiFlags.CANFD_LKA_STEER_MSG)
else:
path.write_bytes(b'{"version":9}')
return result
with patch.object(saved_document.os, "fsync", side_effect=change_at_stage):
self.assertFalse(self.write(key, "Cycle conditional mode").committed)
self.assertFalse(Path(self.params.get_param_path(key)).exists())
if __name__ == "__main__":
unittest.main()
def test_gm_distance_configuration_is_exact_and_preserves_other_actions(tmp_path):
from opendbc.car.gm.tests.test_bolt_pedal import params as pedal_params
from opendbc.car.gm.tests.test_ascm_intercept import params as ascm_params
from opendbc.car.gm.values import CAR, PEDAL_BOLT_CAR
from openpilot.starpilot.conditional_mode.button_actions import distance_capability
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner
from openpilot.starpilot.ui.feature_settings_state import FeaturePage
params = Params(str(tmp_path))
for cp in [*(pedal_params(car, setting=True, pedal=True) for car in PEDAL_BOLT_CAR),
ascm_params(CAR.CHEVROLET_BOLT_EUV, alpha=True), ascm_params(CAR.CHEVROLET_VOLT, radar=True),
ascm_params(CAR.CHEVROLET_VOLT_ASCM, sascm=True, alpha=True)]:
assert distance_capability(cp) is not None
owner = FeatureSettingsOwner(params, lambda group: True, vehicle_fingerprint=lambda cp=cp: cp.carFingerprint, vehicle_params=lambda cp=cp: cp)
rows = owner.snapshot(FeaturePage.WHEEL, parked=True, system_long=True, lateral_context=False, metric=False).rows
rows = [row for row in rows if row.key.startswith('wheel:') and 'Distance' in row.key]
assert len(rows) == 3
assert all(row.available and 'Traffic mode' in row.choices and 'Quick Select 1' in row.choices for row in rows)
for car in (CAR.CHEVROLET_BOLT_EUV, CAR.CHEVROLET_VOLT_CAMERA, CAR.CHEVROLET_VOLT_2019):
assert distance_capability(ascm_params(car)) is None
params.put('DistanceButtonControl', 3, block=True)
rows = owner.snapshot(FeaturePage.WHEEL, parked=True, system_long=True, lateral_context=False, metric=False).rows
row = next(row for row in rows if row.key == 'wheel:DistanceButtonControl')
assert row.source == b'3' and row.value == 'Pause steering' and 'Traffic mode' in row.choices
assert params.get('DistanceButtonControl') == 3