Files
StarPilot/openpilot/starpilot/ui/tests/test_feature_settings_owner.py
T

885 lines
48 KiB
Python

"""Native saved-feature actions use real Params bytes and strict profile helpers."""
from pathlib import Path
from dataclasses import replace
from types import SimpleNamespace
import os
import tempfile
import unittest
from unittest.mock import patch
from openpilot.common.params import Params
from openpilot.starpilot.longitudinal.profile_document import migrate_profile_document
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner
from openpilot.starpilot.ui.feature_settings_state import FeatureRow, FeatureSettingsRequest, row_change, row_default
from openpilot.starpilot import saved_document
from openpilot.starpilot.ui import feature_settings_owner
def required_change(row: FeatureRow, direction: int = 1) -> FeatureSettingsRequest:
request = row_change(row, direction)
if request is None:
raise AssertionError(f"Expected an editable row: {row.key}")
return request
def required_document(raw: object) -> dict:
document = migrate_profile_document(raw)
if document is None:
raise AssertionError("Expected a valid saved profile document")
return document
class FeatureSettingsOwnerTests(unittest.TestCase):
def test_bolt_wheel_menu_exposes_only_received_buttons(self):
from opendbc.car.gm.tests.test_bolt_pedal import params
from opendbc.car.gm.values import CAR
cp = params(CAR.CHEVROLET_BOLT_CC_2018_2021, pedal=True, camera=True)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
self.params.put('LKASButtonControl', 9, block=True)
rows = self.owner.snapshot('wheel', parked=True, system_long=True, lateral_context=True, metric=False).rows
self.assertFalse(any(row.key == 'wheel:LKASButtonControl' for row in rows))
self.assertTrue(any(row.key == 'wheel:MainCruiseButtonControl' and row.available for row in rows))
self.assertTrue(any(row.key == 'wheel:DistanceButtonControl' and row.available for row in rows))
self.assertEqual(self.params.get('LKASButtonControl'), 9)
def test_hybrid_manual_settings_and_automatic_pedal_owner(self):
from opendbc.car import gen_empty_fingerprint
from opendbc.car.gm.interface import CarInterface
from opendbc.car.gm.values import CAR
from opendbc.car.gm.aol import qualified_gm
from opendbc.car.gm.tests.test_ordinary_cc import malibu_hybrid_params
from opendbc.car.gm.startup_preferences import prepare_disable_longitudinal
cp = CarInterface.get_params(CAR.CHEVROLET_MALIBU_HYBRID_CC, gen_empty_fingerprint(), [], False, False, False)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
self.owner.configuration_vehicle = lambda: True
original = cp.as_reader().as_builder().to_bytes()
row = self._row('aol', 'AlwaysOnLateral')
self.assertTrue(row.available)
self.assertTrue(self.owner.apply(required_change(row)))
self.assertFalse(qualified_gm(cp))
self.assertEqual(cp.as_reader().as_builder().to_bytes(), original)
self.owner.configuration_vehicle = lambda: False
cp = malibu_hybrid_params(pedal=True, removed=True)
row = self._row('vehicle', 'GMPedalLongitudinal')
self.assertEqual(row.value, 'Automatic')
self.assertFalse(row.available)
self.assertIn('Activates automatically', row.reason)
prepare_disable_longitudinal(cp, True)
self.assertFalse(cp.openpilotLongitudinalControl)
self.assertEqual(self._row('vehicle', 'GMPedalLongitudinal').value, 'Detected, inactive')
def test_prius_retrofit_manual_settings_do_not_grant_runtime(self):
from opendbc.car import gen_empty_fingerprint, structs
from opendbc.car.toyota.interface import CarInterface
from opendbc.car.toyota.values import CAR
from openpilot.starpilot.aol.vehicle import policy_for
self.params.put_bool('AlwaysOnLateral', False, block=True)
cp = CarInterface.get_params(CAR.TOYOTA_PRIUS_RETROFIT, gen_empty_fingerprint(), [], False, False, False)
# A manual choice can expose saved settings before any native configuration is available.
cp.safetyConfigs = []
original = cp.as_reader().as_builder().to_bytes()
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
self.owner.configuration_vehicle = lambda: True
row = self._row('aol', 'AlwaysOnLateral')
self.assertTrue(row.available)
self.assertIn('next startup', row.reason)
self.assertTrue(self.owner.apply(required_change(row)))
self.assertTrue(self.params.get_bool('AlwaysOnLateral'))
self.assertEqual(cp.as_reader().as_builder().to_bytes(), original)
self.assertFalse(policy_for(cp).runtime_supported)
self.assertFalse(policy_for(cp).intent_supported)
self.owner.configuration_vehicle = lambda: False
self.assertTrue(self.owner.apply(required_change(self._row('aol', 'AlwaysOnLateral'))))
self.assertFalse(self.params.get_bool('AlwaysOnLateral'))
self.assertFalse(self._row('aol', 'AlwaysOnLateral').available)
ordinary = CarInterface.get_params(CAR.TOYOTA_PRIUS, gen_empty_fingerprint(), [], False, False, False)
self.owner.vehicle_params = lambda: ordinary
self.fingerprint = ordinary.carFingerprint
self.owner.configuration_vehicle = lambda: True
self.assertEqual(ordinary.safetyConfigs[0].safetyParam, 0x6242)
self.assertTrue(self._row('aol', 'AlwaysOnLateral').available)
self.assertTrue(policy_for(ordinary).runtime_supported)
unconfigured = ordinary.as_reader().as_builder()
unconfigured.safetyConfigs = []
self.owner.vehicle_params = lambda: unconfigured
self.assertFalse(self._row('aol', 'AlwaysOnLateral').available)
self.assertFalse(policy_for(unconfigured).runtime_supported)
self.assertFalse(policy_for(unconfigured).intent_supported)
fw = [structs.CarParams.CarFw(ecu=structs.CarParams.Ecu.eps, address=0x7A1, brand='toyota', bus=0,
fwVersion=b'8965B47070\x00\x00\x00\x00\x00\x00')]
actual = CarInterface.get_params(CAR.TOYOTA_PRIUS, {0: {0x2FF: 4}, 1: {}, 2: {}}, fw, False, False, False)
self.owner.vehicle_params = lambda: actual
self.fingerprint = actual.carFingerprint
self.owner.configuration_vehicle = lambda: False
self.assertTrue(self._row('aol', 'AlwaysOnLateral').available)
self.assertTrue(policy_for(actual).runtime_supported)
def test_prius_retrofit_stock_settings_preserve_existing_runtime_profile(self):
from opendbc.car import gen_empty_fingerprint
from opendbc.car.toyota.interface import CarInterface
from opendbc.car.toyota.values import CAR
from openpilot.starpilot.aol.vehicle import policy_for
self.params.put_bool('AlwaysOnLateral', False, block=True)
cp = CarInterface.get_params(CAR.TOYOTA_PRIUS_RETROFIT, gen_empty_fingerprint(), [], False, False, False)
self.assertEqual(cp.safetyConfigs[0].safetyParam, 585)
self.assertFalse(cp.openpilotLongitudinalControl)
self.assertTrue(policy_for(cp).runtime_supported)
self.assertTrue(policy_for(cp).intent_supported)
original = cp.as_reader().as_builder().to_bytes()
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
for configuration in (True, False):
with self.subTest(configuration=configuration):
self.owner.configuration_vehicle = lambda configuration=configuration: configuration
row = self._row('aol', 'AlwaysOnLateral')
self.assertTrue(row.available)
self.assertNotIn('next startup', row.reason)
self.assertTrue(self.owner.apply(required_change(row)))
self.assertEqual(self.params.get_bool('AlwaysOnLateral'), configuration)
self.assertEqual(cp.as_reader().as_builder().to_bytes(), original)
self.assertTrue(policy_for(cp).runtime_supported)
self.assertTrue(policy_for(cp).intent_supported)
def test_toyota_hold_is_reachable_from_hub_with_correct_stop_description(self):
from opendbc.car import structs
from opendbc.car.toyota.interface import CarInterface
from opendbc.car.toyota.values import CAR
for hybrid in (True, False):
fw = [structs.CarParams.CarFw(ecu=structs.CarParams.Ecu.hybrid)] if hybrid else []
cp = CarInterface.get_params(CAR.TOYOTA_RAV4_TSS2, {0: {0x3F6: 8}, 1: {}, 2: {}}, fw, False, False, False)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda cp=cp: cp
hub = self.owner.snapshot("hub", parked=True, system_long=True, lateral_context=True, metric=False)
self.assertTrue(any(row.label == "Vehicle Settings" and row.available for row in hub.rows))
row = self._row("vehicle", "ToyotaAutoHold")
self.assertTrue(row.available)
self.assertIn("cruise disengaged", row.reason)
self.assertIn("over one second", row.reason)
self.assertIn("normal cruise stop-and-go is unchanged", row.reason)
request = replace(required_change(row), confirmation=True)
self.assertTrue(self.owner.apply(request))
self.params.put_bool("ToyotaAutoHold", False, block=True)
def test_gm_tune_configured_choice_preserves_intent_and_guards_commit(self):
from opendbc.car.gm.tests.test_volt_transitions import volt_camera_pedal_params
cp = volt_camera_pedal_params(release=True)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
self.owner.configuration_vehicle = lambda: True
row = self._row("vehicle", "GMLongitudinalTune")
self.assertEqual(row.choices, ("Vehicle Default", "Volt Tune"))
request = required_change(row)
self.assertEqual(request.value, "Volt Tune")
self.assertTrue(self.owner.apply(request))
self.assertFalse(self.owner.apply(request))
self.assertEqual(Path(self.params.get_param_path("GMLongitudinalTune")).read_bytes(), b"2")
self.owner.configuration_vehicle = lambda: False
self.assertNotIn("GMLongitudinalTune", [r.key for r in self.owner.snapshot(
"vehicle", parked=True, system_long=False, lateral_context=True, metric=False).rows])
self.assertEqual(Path(self.params.get_param_path("GMLongitudinalTune")).read_bytes(), b"2")
self.owner.configuration_vehicle = lambda: True
row = self._row("vehicle", "GMLongitudinalTune")
reset = row_default(row)
assert reset is not None
self.allowed = False
self.assertFalse(self.owner.apply(reset))
self.allowed = True
self.assertTrue(self.owner.apply(reset))
self.assertEqual(Path(self.params.get_param_path("GMLongitudinalTune")).read_bytes(), b"0")
def test_gm_tune_invalid_saved_choice_repairs_only_vehicle_default(self):
from opendbc.car.gm.tests.test_volt_transitions import volt_camera_pedal_params
cp = volt_camera_pedal_params()
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
path = Path(self.params.get_param_path("GMLongitudinalTune"))
path.write_bytes(b"2\n")
row = self._row("vehicle", "GMLongitudinalTune")
self.assertEqual(row.value, "Invalid saved choice")
reset = row_default(row)
assert reset is not None
self.assertFalse(self.owner.apply(replace(reset, value="Volt Tune")))
self.assertEqual(path.read_bytes(), b"2\n")
self.assertTrue(self.owner.apply(reset))
self.assertEqual(path.read_bytes(), b"0")
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.params = Params(self.temp.name)
self.allowed = True
self.groups = []
self.fingerprint = "TOYOTA COROLLA TSS2"
self.owner = FeatureSettingsOwner(self.params, self._authority, vehicle_fingerprint=lambda: self.fingerprint)
def _authority(self, group):
self.groups.append(group)
return self.allowed
def _row(self, page, key):
state = self.owner.snapshot(page, parked=True, system_long=True, lateral_context=True, metric=False)
return next(row for row in state.rows if row.key == key)
def test_camera_interceptor_setup_reports_actual_owner_and_preserves_preferences(self):
from opendbc.car.gm.tests.test_camera_acc_pedal import params
from opendbc.car.gm.startup_preferences import prepare_disable_longitudinal
cp = params()
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
self.params.put_bool('GMPedalLongitudinal', False, block=True)
pedal = self._row('vehicle', 'GMPedalLongitudinal')
self.assertEqual(pedal.value, 'Automatic')
self.assertFalse(pedal.available)
pitch = self._row('vehicle', 'LongPitch')
self.assertTrue(pitch.available)
request = replace(required_change(pitch), confirmation=True)
self.assertTrue(self.owner.apply(request))
self.assertFalse(self.params.get_bool('LongPitch'))
prepare_disable_longitudinal(cp, True)
self.assertEqual(self._row('vehicle', 'GMPedalLongitudinal').value, 'Detected, inactive')
self.assertFalse(self.owner.apply(request))
self.assertFalse(self.params.get_bool('GMPedalLongitudinal'))
self.assertNotIn('LongPitch', [row.key for row in self.owner.snapshot(
'vehicle', parked=True, system_long=False, lateral_context=True, metric=False).rows])
def test_malformed_numeric_default_has_no_reset_target(self):
self.params.put("StandardFollow", 2.8, block=True)
with patch.object(self.owner, "_default", return_value="not a number"):
row = self.owner._number_row("StandardFollow", "Follow", True)
self.assertIsNone(row.default_value)
self.assertIsNone(row_default(row))
def test_volt_cc_pedal_setup_and_grade_compensation_follow_actual_owner(self):
from opendbc.car.gm.tests.test_volt_transitions import volt_cc_pedal_params
from opendbc.car.gm.startup_preferences import prepare_disable_longitudinal
cp = volt_cc_pedal_params()
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
self.assertEqual(self._row('vehicle', 'GMPedalLongitudinal').value, 'Automatic')
pitch = self._row('vehicle', 'LongPitch')
self.assertTrue(pitch.available)
request = replace(required_change(pitch), confirmation=True)
self.assertTrue(self.owner.apply(request))
prepare_disable_longitudinal(cp, True)
self.assertEqual(self._row('vehicle', 'GMPedalLongitudinal').value, 'Detected, inactive')
self.assertFalse(self.owner.apply(request))
def test_source_profile_defaults_and_single_numeric_reset(self):
self.params.put("StandardFollow", 2.8, block=True)
row = self._row("standard", "StandardFollow")
self.assertEqual(float(row.default_value), float(self.params.get_default_value("StandardFollow")))
request = row_default(row)
assert request is not None
self.assertTrue(self.owner.apply(request))
self.assertFalse(self.owner.apply(request))
preset = self._row("standard/acceleration", "profile:standard:acceleration")
self.assertEqual(preset.default_value, "Selected Profile")
self.assertEqual(self._row("profiles", "profile:global_braking").default_value, "Comfort")
def test_strength_has_actual_runtime_admission_percent_range_and_source_guard(self):
from openpilot.starpilot.lateral.tests.test_lane_runtime import ioniq_candidate
_, tagged = ioniq_candidate()
root = Path(self.params.get_param_path("LaneCentering")).parent
registered = self.params
class Files:
def get_param_path(self, key):
return str(root / key)
def get_default_value(self, key):
return 1.5 if key == "LaneCenteringStrength" else registered.get_default_value(key)
def put(self, key, value, block=True):
(root / key).write_bytes(str(value).encode())
self.owner.params = Files()
self.owner.vehicle_params = lambda: None
row = self._row("lane", "LaneCenteringStrength")
self.assertFalse(row.available)
self.assertIn("vehicle configuration", row.reason)
self.owner.vehicle_params = lambda: tagged
self.fingerprint = tagged.carFingerprint
row = self._row("lane", "LaneCenteringStrength")
self.assertEqual((row.value, row.minimum, row.maximum, row.step), ("100", 33, 200, 1))
self.assertTrue(row.available)
request = FeatureSettingsRequest(row.key, row.source, "200", vehicle_fingerprint=row.vehicle_fingerprint)
self.assertTrue(self.owner.apply(request))
self.assertEqual((root / row.key).read_bytes(), b"3.0")
self.assertFalse(self.owner.apply(request))
reset_row = self._row("lane", "LaneCenteringStrength")
self.assertEqual(reset_row.default_value, "100")
self.assertTrue(self.owner.apply(FeatureSettingsRequest(reset_row.key, reset_row.source, reset_row.default_value,
vehicle_fingerprint=reset_row.vehicle_fingerprint)))
self.assertEqual((root / row.key).read_bytes(), b"1.5")
self.assertEqual(registered.get_default_value("LaneCenteringStrength"), 1.5)
for percent, expected in ((33, b"0.5"), (67, b"1.005"), (100, b"1.5"), (105, b"1.575"), (150, b"2.25"), (200, b"3.0")):
current = self._row("lane", "LaneCenteringStrength")
self.assertTrue(self.owner.apply(FeatureSettingsRequest(current.key, current.source, str(percent),
vehicle_fingerprint=current.vehicle_fingerprint)))
self.assertEqual((root / row.key).read_bytes(), expected)
(root / row.key).write_bytes(b"1.0")
preserved = self._row("lane", "LaneCenteringStrength")
self.assertEqual(preserved.value, "67")
self.assertEqual((root / row.key).read_bytes(), b"1.0")
row = self._row("lane", "LaneCenteringStrength")
for invalid in ("32", "201", "nan"):
self.assertFalse(self.owner.apply(FeatureSettingsRequest(row.key, row.source, invalid,
vehicle_fingerprint=row.vehicle_fingerprint)))
self.owner.vehicle_params = lambda: None
self.assertFalse(self.owner.apply(FeatureSettingsRequest(row.key, row.source, "100",
vehicle_fingerprint=row.vehicle_fingerprint)))
self.assertIsNone(registered.get("LaneCenteringE2EAuthority"))
def test_turn_assist_defaults_on_independent_of_tune_and_exact_source_guarded(self):
from openpilot.starpilot.lateral.tests.test_lane_runtime import ioniq_candidate
from openpilot.starpilot.lateral.controller_selection import DOCUMENT_KEY, ControllerMode, replace_mode
_, cp = ioniq_candidate()
self.owner.vehicle_params = lambda: cp
self.owner.controller.vehicle_params = self.owner.vehicle_params
self.fingerprint = cp.carFingerprint
row = self._row("torque", "TurnAssist")
self.assertEqual(row.value, "On")
self.assertTrue(row.available)
self.assertEqual(row.dependencies, ())
request = required_change(row)
self.assertTrue(self.owner.apply(request))
self.assertFalse(self.params.get_bool("TurnAssist"))
self.assertFalse(self.owner.apply(request))
import json
self.params.put(DOCUMENT_KEY, json.loads(replace_mode(None, cp, ControllerMode.STANDARD)), block=True)
row = self._row("torque", "TurnAssist")
self.assertTrue(row.available)
self.assertEqual(row.value, "Off")
self.assertFalse(self.params.get_bool("TurnAssist"))
view = self.owner.snapshot("torque", parked=False, system_long=True, lateral_context=True, metric=False)
self.assertFalse(next(row for row in view.rows if row.key == "TurnAssist").available)
self.owner.vehicle_params = lambda: None
view = self.owner.snapshot("torque", parked=True, system_long=True, lateral_context=True, metric=False)
self.assertFalse(any(row.key == "TurnAssist" for row in view.rows))
self.assertFalse(self.owner.apply(required_change(row)))
self.assertFalse(self.params.get_bool("TurnAssist"))
def test_turn_assist_nonpolicy_owner_saved_off_survives_restart(self):
from opendbc.car.car_helpers import interfaces
from opendbc.car.mazda.values import CAR
from openpilot.starpilot.lateral.controller_selection import policy_for
from openpilot.starpilot.vehicle_preferences import VehicleStartupPreferences
cp = interfaces[CAR.MAZDA_CX5_2022].get_non_essential_params(CAR.MAZDA_CX5_2022)
self.assertIsNone(policy_for(cp))
self.owner.vehicle_params = lambda: cp
self.owner.controller.vehicle_params = self.owner.vehicle_params
self.fingerprint = cp.carFingerprint
row = self._row("torque", "TurnAssist")
self.assertTrue(row.available)
self.assertEqual(row.value, "On")
self.assertTrue(self.owner.apply(required_change(row)))
self.assertFalse(VehicleStartupPreferences.read(self.params, enabled=True).turn_assist)
row = self._row("torque", "TurnAssist")
self.assertEqual(row.value, "Off")
cp.minSteerSpeed += 1.
self.assertFalse(self.owner.apply(required_change(row)))
cp.minSteerSpeed -= 1.
self.assertTrue(self.owner.apply(required_change(self._row("torque", "TurnAssist"))))
self.assertTrue(VehicleStartupPreferences.read(self.params, enabled=True).turn_assist)
def test_live_lane_owner_maps_only_explicit_keys(self):
from opendbc.car.car_helpers import interfaces
from opendbc.car.toyota.values import CAR
cp = interfaces[CAR.TOYOTA_COROLLA_TSS2].get_non_essential_params(CAR.TOYOTA_COROLLA_TSS2)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
self.owner.authority = lambda group: group == "lane_live"
view = self.owner.snapshot("lane", parked=False, system_long=False, lateral_context=False, metric=False)
row = next(row for row in view.rows if row.key == "LaneCentering")
self.assertTrue(row.available)
self.assertTrue(self.owner.apply(required_change(row)))
self.assertTrue(self.params.get("LaneCentering"))
view = self.owner.snapshot("lane", parked=False, system_long=False, lateral_context=False, metric=False)
pause = next(row for row in view.rows if row.key == "LaneCenteringPauseOnSignal")
self.assertTrue(pause.available)
self.assertTrue(self.owner.apply(required_change(pause)))
for key in ("AlwaysOnLateral", "CustomPersonalities", "LongPitch"):
self.assertFalse(self.owner.apply(FeatureSettingsRequest(key, None, "On", vehicle_fingerprint=self.fingerprint)))
def test_force_stop_disable_onroad_retains_source_vehicle_and_enable_gates(self):
self.params.put_bool("ForceStops", True, block=True)
self.owner.authority = lambda group: group == "preferences"
view = self.owner.snapshot("profiles", parked=False, system_long=False, lateral_context=True, metric=False)
row = next(row for row in view.rows if row.key == "ForceStops")
self.assertTrue(row.available)
request = required_change(row)
self.assertEqual(request.value, "Off")
self.fingerprint = "different vehicle"
self.assertFalse(self.owner.apply(request))
self.fingerprint = row.vehicle_fingerprint
self.assertTrue(self.owner.apply(request))
self.assertEqual(self.params.get("ForceStops"), False)
self.assertFalse(self.owner.apply(request))
enable = FeatureSettingsRequest("ForceStops", b"0", "On", vehicle_fingerprint=self.fingerprint)
self.assertFalse(self.owner.apply(enable))
self.assertIsNone(self.params.get("QOLLongitudinal"))
def test_force_stop_preserves_cruise_master_and_offset_bounds(self):
self.assertTrue(self._row("profiles", "ForceStops").available)
self.params.put_bool("QOLLongitudinal", True, block=True)
force = self._row("profiles", "ForceStops")
self.assertTrue(force.available)
self.assertEqual(force.value, "Off")
self.params.put_bool("ForceStops", True, block=True)
force = self._row("profiles", "ForceStops")
self.assertEqual(force.value, "On")
offset = self._row("profiles", "ForceStopDistanceOffset")
self.assertTrue(self.owner.apply(FeatureSettingsRequest(offset.key, offset.source, "4",
vehicle_fingerprint=offset.vehicle_fingerprint)))
self.assertEqual(self.params.get("ForceStopDistanceOffset"), 4)
offset = self._row("profiles", "ForceStopDistanceOffset")
for invalid in ("21", "-21", "0.5"):
self.assertFalse(self.owner.apply(FeatureSettingsRequest(offset.key, offset.source, invalid,
vehicle_fingerprint=offset.vehicle_fingerprint)))
self.assertTrue(self.owner.apply(required_change(force)))
self.assertFalse(self._row("profiles", "ForceStopDistanceOffset").available)
def test_corrupt_bytes_remain_visible_and_untouched(self):
for key, page, raw in (("LaneCenterOffset", "lane", b"nan"),
("SpeedLimitController", "slc", b"maybe"),
("SLCFallback", "slc", b"broken")):
with self.subTest(key=key):
path = Path(self.params.get_param_path(key))
path.write_bytes(raw)
row = self._row(page, key)
self.assertFalse(row.available)
self.assertIsNone(row_change(row))
self.assertEqual(path.read_bytes(), raw)
def test_readable_presets_keep_saved_tokens_and_numeric_help(self):
preset = self._row("aggressive/acceleration", "profile:aggressive:acceleration")
self.assertEqual(preset.value, "Selected Profile")
self.assertEqual(preset.choices, ("Selected Profile", "StarPilot Default", "Normal", "Comfort", "Sport", "Sport+"))
self.assertTrue(self.owner.apply(FeatureSettingsRequest(preset.key, preset.source, "Sport+",
vehicle_fingerprint=preset.vehicle_fingerprint)))
document = required_document(Path(self.params.get_param_path("LongitudinalPersonalityProfiles")).read_bytes())
self.assertEqual(document["profiles"]["aggressive"]["acceleration"]["preset"], "sport_plus")
self.assertIn("Higher values", self._row("aggressive", "AggressiveJerkAcceleration").reason)
self.assertFalse(any(row.key == "AggressivePersonalityProfile" for row in self.owner.snapshot(
"aggressive", parked=True, system_long=True, lateral_context=True, metric=False).rows))
self.assertIn("Sport+", self._row("aggressive/acceleration", "profile:aggressive:acceleration").value)
self.assertIsNone(self.params.get("CustomPersonalities"))
def test_release_rechecks_parked_authority_and_source(self):
from opendbc.car.car_helpers import interfaces
from opendbc.car.toyota.values import CAR
cp = interfaces[CAR.TOYOTA_COROLLA_TSS2].get_non_essential_params(CAR.TOYOTA_COROLLA_TSS2)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
row = self._row("lane", "LaneCentering")
request = required_change(row)
self.assertIsNotNone(request)
self.allowed = False
self.assertFalse(self.owner.apply(request))
self.allowed = True
self.params.put_bool("LaneCentering", True, block=True)
self.assertFalse(self.owner.apply(request))
self.assertTrue(self.params.get_bool("LaneCentering"))
self.assertIn("lane", self.groups)
fresh = self._row("lane", "LaneCentering")
self.fingerprint = "OTHER CAR"
self.assertFalse(self.owner.apply(required_change(fresh)))
def test_approaching_lead_setting_independent_and_exactly_guarded(self):
self.fingerprint = None
Path(self.params.get_param_path("LongitudinalPersonalityProfiles")).write_bytes(b"{invalid")
Path(self.params.get_param_path("CustomPersonalities")).write_bytes(b"invalid")
row = self._row("profiles", "LeadApproachBuffer")
self.assertEqual((row.label, row.value), ("Approaching lead buffer", "Off"))
self.assertTrue(row.available)
self.assertIsNone(row.vehicle_fingerprint)
self.assertIsNone(row.capability)
request = required_change(row)
self.allowed = False
self.assertFalse(self.owner.apply(request))
self.allowed = True
self.assertTrue(self.owner.apply(request))
saved = Path(self.params.get_param_path("LeadApproachBuffer"))
self.assertEqual(saved.read_bytes(), b"1")
self.assertFalse(self.owner.apply(request))
off = required_change(self._row("profiles", "LeadApproachBuffer"))
saved.write_bytes(b"0")
self.assertFalse(self.owner.apply(off))
self.assertEqual(saved.read_bytes(), b"0")
self.assertTrue(self._row("profiles", "LeadApproachBuffer").available)
def test_parent_and_units_guards(self):
from opendbc.car.car_helpers import interfaces
from opendbc.car.toyota.values import CAR
cp = interfaces[CAR.TOYOTA_COROLLA_TSS2].get_non_essential_params(CAR.TOYOTA_COROLLA_TSS2)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
self.assertFalse(any(row.key in ('SLCConfirmationLower', 'SLCConfirmationHigher') for row in
self.owner.snapshot('slc', parked=True, system_long=True, lateral_context=True, metric=False).rows))
self.assertFalse(self.owner.apply(FeatureSettingsRequest('SLCConfirmationLower', None, 'On', vehicle_fingerprint=self.fingerprint)))
parent = self._row("slc", "SLCConfirmation")
self.assertTrue(self.owner.apply(required_change(parent)))
self.assertTrue(self.params.get_bool('SLCConfirmation'))
authority = self._row("lane", "LaneCenteringE2EAuthority")
self.assertEqual(authority.value, "100.0")
self.assertTrue(self.owner.apply(required_change(authority, -1)))
self.assertAlmostEqual(self.params.get("LaneCenteringE2EAuthority"), 0.95)
offset = self._row("slc", "Offset1")
self.assertFalse(offset.available)
self.assertFalse(self.owner.apply(FeatureSettingsRequest("Offset1", offset.source, "5", vehicle_fingerprint=offset.vehicle_fingerprint)))
Path(self.params.get_param_path("IsMetric")).write_bytes(b"bad")
offset = self._row("slc", "Offset1")
self.assertIn("invalid saved units", offset.label)
self.assertEqual(Path(self.params.get_param_path("IsMetric")).read_bytes(), b"bad")
def test_limit_sign_preference_needs_parked_authority_not_long_control_or_vehicle(self):
self.fingerprint = None
self.owner = FeatureSettingsOwner(self.params, lambda group: self.allowed and group == "preferences",
vehicle_fingerprint=lambda: self.fingerprint)
page = self.owner.snapshot("slc", parked=True, system_long=False, lateral_context=False, metric=False)
fresh_sign = next(row for row in page.rows if row.key == "ShowSpeedLimits")
self.assertEqual((fresh_sign.value, fresh_sign.default_value), ('On', 'On'))
self.params.put_bool('ShowSpeedLimits', False, block=True)
self.params.put('SLCPriority1', 'Dashboard', block=True)
page = self.owner.snapshot("slc", parked=True, system_long=False, lateral_context=False, metric=False)
sign = next(row for row in page.rows if row.key == "ShowSpeedLimits")
self.assertTrue(sign.available)
self.assertIsNone(sign.vehicle_fingerprint)
for key in ("SpeedLimitController", "SLCConfirmation", "SLCPriority1", "SLCFallback"):
self.assertFalse(next(row for row in page.rows if row.key == key).available)
request = required_change(sign)
self.allowed = False
self.assertFalse(self.owner.apply(request))
self.allowed = True
self.assertTrue(self.owner.apply(request))
self.assertTrue(self.params.get_bool("ShowSpeedLimits"))
self.assertFalse(self.params.get_bool("SpeedLimitController"))
self.assertFalse(self.owner.apply(request))
def test_curve_saved_choices_and_invalid_learning(self):
hub = self.owner.snapshot("hub", parked=True, system_long=True, lateral_context=True, metric=False)
self.assertIn("curve", [row.page for row in hub.rows])
master = self._row("curve", "CurveSpeedController")
self.assertEqual(master.value, "Off")
self.assertTrue(self.owner.apply(required_change(master)))
self.assertIn("long", self.groups)
no_lead = self._row("curve", "CurveSpeedControllerNoLead")
self.assertTrue(self.owner.apply(required_change(no_lead)))
self.assertTrue(self.params.get_bool("CurveSpeedControllerNoLead"))
display = self._row("curve", "ShowCSCStatus")
self.assertTrue(self.owner.apply(required_change(display)))
self.assertTrue(self.params.get_bool("ShowCSCStatus"))
document = Path(self.params.get_param_path("CurveComfortData"))
document.write_bytes(b"{invalid")
self.assertFalse(self.owner.apply(required_change(master))) # Old document evidence cannot authorize another action.
enabled = self._row("curve", "CurveSpeedController")
self.assertTrue(enabled.available)
self.assertTrue(self.owner.apply(required_change(enabled))) # Off remains available, preserving corrupt bytes.
self.assertEqual(document.read_bytes(), b"{invalid")
self.assertFalse(self._row("curve", "CurveSpeedControllerNoLead").available)
self.assertFalse(self._row("curve", "CurveSpeedController").available)
self.assertTrue(self._row("curve", "ShowCSCStatus").available)
Path(self.params.get_param_path("CurveSpeedController")).write_bytes(b"maybe")
invalid_master = self._row("curve", "CurveSpeedController")
self.assertFalse(invalid_master.available)
self.assertEqual(invalid_master.reason, "Invalid saved value")
def test_curve_requires_fresh_long_authority_and_legacy_source(self):
legacy = Path(self.params.get_param_path("CurvatureData"))
legacy.write_bytes(b"{}")
master = self._row("curve", "CurveSpeedController")
request = required_change(master)
legacy.write_bytes(b'{"0.001":{"average":2.0,"count":2}}')
self.assertFalse(self.owner.apply(request))
self.allowed = False
self.assertFalse(self._row("curve", "CurveSpeedController").available)
self.allowed = True
fresh = self._row("curve", "CurveSpeedController")
self.fingerprint = "OTHER CAR"
self.assertFalse(self.owner.apply(required_change(fresh)))
def test_curve_learning_automatic_readout_and_confirmed_reset(self):
legacy = Path(self.params.get_param_path("CurvatureData"))
legacy_raw = b'{"0.001":{"average":2.4,"count":2}}'
legacy.write_bytes(legacy_raw)
state = self.owner.snapshot("curve", parked=True, system_long=True, lateral_context=True, metric=False)
self.assertFalse(any(row.key == "curve_adopt" for row in state.rows))
progress = next(row for row in state.rows if row.label == "Learning progress")
self.assertNotEqual(progress.value, "Unavailable")
comfort = next(row for row in state.rows if row.label == "Saved cornering comfort")
self.assertIn("m/s²", comfort.value)
canonical = Path(self.params.get_param_path("CurveComfortData"))
self.assertFalse(canonical.exists())
self.assertEqual(legacy.read_bytes(), legacy_raw)
reset = self._row("curve", "curve_reset")
request = FeatureSettingsRequest(reset.key, reset.source, "confirm", confirmation=True,
vehicle_fingerprint=reset.vehicle_fingerprint, dependencies=reset.dependencies)
self.assertFalse(self.owner.apply(replace(request, confirmation=False)))
legacy.write_bytes(b'{"0.001":{"average":2.0,"count":2}}')
self.assertFalse(self.owner.apply(request))
reset = self._row("curve", "curve_reset")
request = FeatureSettingsRequest(reset.key, reset.source, "confirm", confirmation=True,
vehicle_fingerprint=reset.vehicle_fingerprint, dependencies=reset.dependencies)
self.assertTrue(self.owner.apply(request))
self.assertEqual(canonical.read_bytes(), b'{"version":1,"buckets":{}}')
self.assertEqual(legacy.read_bytes(), b'{"0.001":{"average":2.0,"count":2}}')
def test_curve_learning_action_rechecks_parked_vehicle(self):
Path(self.params.get_param_path("CurvatureData")).write_bytes(b'{"0.001":{"average":2.0,"count":2}}')
adopt = self._row("curve", "curve_reset")
request = FeatureSettingsRequest(adopt.key, adopt.source, "confirm", confirmation=True,
vehicle_fingerprint=adopt.vehicle_fingerprint, dependencies=adopt.dependencies)
self.allowed = False
self.assertFalse(self.owner.apply(request))
self.allowed = True
self.fingerprint = "OTHER CAR"
self.assertFalse(self.owner.apply(request))
self.assertIsNone(self.params.get("CurveComfortData"))
def test_profile_document_round_trip_and_explicit_invalid_reset(self):
self.params.put("LongitudinalPersonalityProfiles", {}, block=True)
preset = self._row("aggressive/acceleration", "profile:aggressive:acceleration")
self.assertTrue(self.owner.apply(FeatureSettingsRequest(preset.key, preset.source, "custom", vehicle_fingerprint=preset.vehicle_fingerprint)))
raw = Path(self.params.get_param_path("LongitudinalPersonalityProfiles")).read_bytes()
doc = required_document(raw)
self.assertEqual(doc["profiles"]["aggressive"]["acceleration"]["preset"], "custom")
point = self._row("aggressive/acceleration", "profile:aggressive:acceleration:0")
self.assertTrue(self.owner.apply(required_change(point, -1)))
self.assertNotEqual(Path(self.params.get_param_path("LongitudinalPersonalityProfiles")).read_bytes(), raw)
path = Path(self.params.get_param_path("LongitudinalPersonalityProfiles"))
path.write_bytes(b"{invalid")
self.params.put_bool("CustomPersonalities", True, block=True)
reset = self._row("profiles", "reset_profiles")
self.assertFalse(self.owner.apply(FeatureSettingsRequest(reset.key, reset.source, "reset", vehicle_fingerprint=reset.vehicle_fingerprint)))
self.assertEqual(path.read_bytes(), b"{invalid")
self.assertTrue(self.owner.apply(FeatureSettingsRequest(reset.key, reset.source, "reset", confirmation=True,
vehicle_fingerprint=reset.vehicle_fingerprint)))
restored = required_document(path.read_bytes())
self.assertIsNotNone(restored)
self.assertFalse(restored["enabled"])
self.assertFalse(self.params.get_bool("CustomPersonalities"))
def test_profile_enable_lost_authority_keeps_boolean_disabled(self):
self.owner.vehicle_params = lambda: SimpleNamespace(
carFingerprint=self.fingerprint, openpilotLongitudinalControl=True, pcmCruise=False,
passive=False, notCar=False, dashcamOnly=False, carVin="VIN1")
self.params.put("LongitudinalPersonalityProfiles", {}, block=True)
row = self._row("profiles", "CustomPersonalities")
request = required_change(row)
original_replace = os.replace
replacements = []
def lose_authority_after_document(source, destination):
original_replace(source, destination)
replacements.append(destination)
self.allowed = False
with patch.object(saved_document.os, "replace", side_effect=lose_authority_after_document):
self.assertFalse(self.owner.apply(request))
self.assertEqual(len(replacements), 1)
self.assertFalse(self.params.get_bool("CustomPersonalities"))
def test_staged_profile_edit_rechecks_master_vehicle_and_parked_authority(self):
original_fsync = os.fsync
for changed in ("master", "vehicle", "authority"):
with self.subTest(changed=changed):
self.allowed = True
self.fingerprint = "TOYOTA COROLLA TSS2"
self.params.put_bool("CustomPersonalities", False, block=True)
request = required_change(self._row("profiles", "profile:global_braking"))
staged = []
def invalidate_after_stage(fd, changed=changed, staged=staged):
original_fsync(fd)
if not staged:
staged.append(True)
if changed == "master":
self.params.put_bool("CustomPersonalities", True, block=True)
elif changed == "vehicle":
self.fingerprint = "OTHER CAR"
else:
self.allowed = False
with patch.object(saved_document.os, "fsync", side_effect=invalidate_after_stage):
self.assertFalse(self.owner.apply(request))
self.assertTrue(staged)
self.assertIsNone(self.params.get("LongitudinalPersonalityProfiles"))
def test_profile_enable_does_not_adopt_a_competing_document(self):
self.owner.vehicle_params = lambda: SimpleNamespace(
carFingerprint=self.fingerprint, openpilotLongitudinalControl=True, pcmCruise=False,
passive=False, notCar=False, dashcamOnly=False, carVin="VIN1")
request = required_change(self._row("profiles", "CustomPersonalities"))
writes = []
def replace_after_verified_commit(*args, **kwargs):
result = saved_document.commit_exact(*args, **kwargs)
self.assertTrue(result.committed and result.verified)
competing = required_document(self.params.get("LongitudinalPersonalityProfiles"))
competing["globalBrakingResponse"] = "sport"
self.params.put("LongitudinalPersonalityProfiles", competing, block=True)
writes.append(True)
return result
with patch.object(feature_settings_owner, "commit_exact", side_effect=replace_after_verified_commit):
self.assertFalse(self.owner.apply(request))
self.assertEqual(writes, [True])
self.assertFalse(self.params.get_bool("CustomPersonalities"))
self.assertEqual(self.params.get("LongitudinalPersonalityProfiles")["globalBrakingResponse"], "sport")
def test_profile_enable_rechecks_document_while_staging_switch(self):
self.owner.vehicle_params = lambda: SimpleNamespace(
carFingerprint=self.fingerprint, openpilotLongitudinalControl=True, pcmCruise=False,
passive=False, notCar=False, dashcamOnly=False, carVin="VIN1")
request = required_change(self._row("profiles", "CustomPersonalities"))
original_fsync = os.fsync
calls = []
def competing_edit(fd):
original_fsync(fd)
calls.append(fd)
if len(calls) == 3:
document = self.params.get("LongitudinalPersonalityProfiles")
document["globalBrakingResponse"] = "sport"
self.params.put("LongitudinalPersonalityProfiles", document, block=True)
with patch.object(saved_document.os, "fsync", side_effect=competing_edit):
self.assertFalse(self.owner.apply(request))
self.assertEqual(len(calls), 3)
self.assertFalse(self.params.get_bool("CustomPersonalities"))
self.assertEqual(self.params.get("LongitudinalPersonalityProfiles")["globalBrakingResponse"], "sport")
def test_global_braking_saved_choice_is_parked_source_bound_and_preserved_by_profile_edit(self):
row = self._row("profiles", "profile:global_braking")
self.assertEqual((row.value, row.choices), ("Comfort", ("StarPilot Default", "Normal", "Comfort", "Sport")))
self.assertFalse(self.params.get_bool("CustomPersonalities"))
change = required_change(row)
assert change is not None
self.allowed = False
self.assertFalse(self.owner.apply(change))
self.allowed = True
self.assertTrue(self.owner.apply(change))
saved = required_document(self.params.get("LongitudinalPersonalityProfiles"))
self.assertEqual(saved["selectedDecelerationProfile"], "sport")
self.assertFalse(saved["enabled"])
self.assertFalse(self.owner.apply(change))
category = self._row("standard/braking", "profile:standard:braking")
self.assertTrue(self.owner.apply(required_change(category)))
self.assertEqual(required_document(self.params.get("LongitudinalPersonalityProfiles"))["selectedDecelerationProfile"], "sport")
unparked = self.owner.snapshot("profiles", parked=False, system_long=True, lateral_context=True, metric=False)
self.assertFalse(next(row for row in unparked.rows if row.key == "profile:global_braking").available)
if __name__ == "__main__":
unittest.main()
class TruckTuningFeatureTests(unittest.TestCase):
setUp = FeatureSettingsOwnerTests.setUp
_authority = FeatureSettingsOwnerTests._authority
_row = FeatureSettingsOwnerTests._row
def test_truck_saved_choice_exact_owner_and_stale_request(self):
from opendbc.car.gm.tests.test_ordinary_camera import params
from opendbc.car.gm.values import CAR
cp = params(CAR.CHEVROLET_SILVERADO, alpha=True)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
row = self._row('vehicle', 'TruckTuning')
self.assertEqual((row.value, row.default_value), ('Off', 'Off'))
request = required_change(row)
self.assertTrue(self.owner.apply(request))
self.assertEqual(Path(self.params.get_param_path('TruckTuning')).read_bytes(), b'1')
self.assertFalse(self.owner.apply(request))
stale = required_change(self._row('vehicle', 'TruckTuning'))
cp = params(CAR.CHEVROLET_SILVERADO, alpha=True, release=True)
self.assertFalse(self.owner.apply(stale))
self.assertFalse(any(row.key == 'TruckTuning' for row in self.owner.snapshot(
'vehicle', parked=True, system_long=False, lateral_context=True, metric=False).rows))
self.assertEqual(Path(self.params.get_param_path('TruckTuning')).read_bytes(), b'1')
def test_truck_invalid_choice_repairs_only_off_and_respects_authority(self):
from opendbc.car.gm.tests.test_ordinary_camera import params
from opendbc.car.gm.values import CAR
cp = params(CAR.CHEVROLET_SILVERADO, alpha=True, camera=False)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
path = Path(self.params.get_param_path('TruckTuning'))
path.write_bytes(b'1\n')
row = self._row('vehicle', 'TruckTuning')
request = row_default(row)
assert request is not None
self.assertFalse(self.owner.apply(replace(request, value='On')))
self.allowed = False
self.assertFalse(self.owner.apply(request))
self.allowed = True
self.assertTrue(self.owner.apply(request))
self.assertEqual(path.read_bytes(), b'0')
class EvPresetFeatureTests(unittest.TestCase):
setUp = FeatureSettingsOwnerTests.setUp
_authority = FeatureSettingsOwnerTests._authority
_row = FeatureSettingsOwnerTests._row
def test_saved_ev_choice_is_atomic_and_stock_owner_cannot_change_it(self):
from opendbc.car.gm.tests.test_ordinary_camera import params
from opendbc.car.gm.values import CAR
cp = params(CAR.CHEVROLET_SILVERADO, alpha=True)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
row = self._row('vehicle', 'EVTuning')
self.assertEqual(row.value, 'Off')
self.assertIsNone(row.source)
request = required_change(row)
self.assertTrue(self.owner.apply(request))
self.assertTrue(self.params.get_bool('EVTuning'))
self.assertFalse(self.owner.apply(request))
stale = required_change(self._row('vehicle', 'EVTuning'))
cp = params(CAR.CHEVROLET_SILVERADO, alpha=True, release=True)
self.assertFalse(self.owner.apply(stale))
self.assertFalse(any(r.key == 'EVTuning' for r in self.owner.snapshot('vehicle', parked=True, system_long=False, lateral_context=True, metric=False).rows))
self.assertTrue(self.params.get_bool('EVTuning'))
def test_invalid_ev_choice_repairs_only_current_powertrain_default(self):
from opendbc.car.gm.tests.test_ordinary_camera import params
from opendbc.car.gm.values import CAR
cp = params(CAR.CHEVROLET_SILVERADO, alpha=True)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
path = Path(self.params.get_param_path('EVTuning'))
path.write_bytes(b'1\n')
request = row_default(self._row('vehicle','EVTuning'))
assert request is not None
self.assertFalse(self.owner.apply(replace(request,value='On')))
self.allowed = False
self.assertFalse(self.owner.apply(request))
self.allowed = True
self.assertTrue(self.owner.apply(request))
self.assertEqual(path.read_bytes(),b'0')
def test_ev_default_row_follows_final_direct_powertrain_without_saving(self):
from opendbc.car.gm.tests.test_bolt_pedal import params
from opendbc.car.gm.values import CAR
cp = params(CAR.CHEVROLET_BOLT_CC_2022_2023,pedal=True,camera=True)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
row = self._row('vehicle','EVTuning')
self.assertEqual((row.value,row.default_value),('On','On'))
self.assertIsNone(row.source)
self.assertFalse(Path(self.params.get_param_path('EVTuning')).exists())
def test_current_vehicle_loss_during_ev_commit_denies_without_saving(self):
from opendbc.car.gm.tests.test_ordinary_camera import params
from opendbc.car.gm.values import CAR
cp = params(CAR.CHEVROLET_SILVERADO, alpha=True)
self.fingerprint = cp.carFingerprint
self.owner.vehicle_params = lambda: cp
request = required_change(self._row('vehicle', 'EVTuning'))
reads = []
def current_vehicle():
reads.append(len(reads))
return cp if len(reads) == 1 else None
self.owner.vehicle_params = current_vehicle
self.assertFalse(self.owner.apply(request))
self.assertEqual(len(reads), 2)
self.assertFalse(Path(self.params.get_param_path('EVTuning')).exists())