Build matching Outback MAIN lateral firmware

This commit is contained in:
firestar5683
2026-10-30 13:15:36 -05:00
60 changed files with 411 additions and 23 deletions
@@ -85,6 +85,7 @@ class CarController(CarControllerBase):
self.last_angle = 0
self.alert_active = False
self.standstill_req = False
self.sng_hack = False
self.last_standstill = False
self.prius_longitudinal = prius_longitudinal.enabled(CP)
self.sdsu_hybrid = (eps8_sdsu.configured(CP) or legacy_sdsu.configured(CP)) and bool(CP.flags & ToyotaFlags.HYBRID)
@@ -360,6 +361,9 @@ class CarController(CarControllerBase):
if not should_resume and CS.out.cruiseState.standstill:
self.standstill_req = True
if self.sng_hack and long_active:
self.standstill_req = False
if (bypass_long or rav4h_long or pedal_owner or advert_long or original_prius_long) and (not filter_allowed or not long_active and CS.out.standstill):
self.standstill_req = True
+14
View File
@@ -0,0 +1,14 @@
"""StarPilot stop-latch override for existing Toyota longitudinal owners."""
from opendbc.car import structs
from opendbc.car.toyota.values import ToyotaFlags
def eligible(cp):
from opendbc.car.toyota import prius_longitudinal, rav4h_filter, advert_filter, legacy_stop_timer
return bool(cp is not None and cp.brand == 'toyota' and cp.openpilotLongitudinalControl and
not cp.autoResumeSng and not cp.deprecated.enableGasInterceptor and
not cp.flags & ToyotaFlags.GAS_INTERCEPTOR and
not cp.passive and not cp.dashcamOnly and not cp.notCar and
len(cp.safetyConfigs) == 1 and cp.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.toyota and
(prius_longitudinal.enabled(cp) or rav4h_filter.enabled(cp) or
advert_filter.configured(cp) or legacy_stop_timer.enabled(cp)))
Binary file not shown.
Binary file not shown.
+1
View File
@@ -21,6 +21,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"PromptVolume", {PERSISTENT, INT}},
{"AlwaysOnLateral", {PERSISTENT, BOOL, "1"}},
{"NostalgiaMode", {PERSISTENT, BOOL, "0"}},
{"SNGHack", {PERSISTENT, BOOL, "0"}},
{"ToyotaAutoHold", {PERSISTENT, BOOL, "0"}},
{"JeepBrakeHold", {PERSISTENT, BOOL, "0"}},
{"ToyotaRadarMode", {PERSISTENT, INT, "0"}},
@@ -0,0 +1,103 @@
"""The saved donor option reaches the real settings and controller owners."""
from dataclasses import replace
import pytest
from opendbc.car.toyota.interface import CarInterface
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.car.toyota.tests.test_gas_override_cancel import setup, acc_frames, PROFILES
from openpilot.starpilot.car.toyota.tests.test_prius_sdsu_filter import factory
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner
from openpilot.starpilot.ui.feature_settings_state import row_change
from openpilot.starpilot.vehicle_preferences import VehicleStartupPreferences
def eligible(cp):
from opendbc.car.toyota.sng import eligible as check
return check(cp)
def owner_for(params, cp, parked=True):
return FeatureSettingsOwner(params, lambda _: parked, vehicle_params=lambda: cp,
vehicle_fingerprint=lambda: cp.carFingerprint)
def rows(owner, parked=True):
return owner.snapshot('vehicle', parked=parked, system_long=True, lateral_context=True, metric=False).rows
def test_saved_sng_option_is_exposed():
with OpenpilotPrefix():
params = Params()
assert any(row.key == 'SNGHack' for row in rows(owner_for(params, factory()))), 'TOYOTA_SNG_OPTION_MISSING'
def test_saved_ui_choice_binds_next_controller_without_changing_cp():
with OpenpilotPrefix():
params, cp = Params(), factory()
before = cp.to_dict()
owner = owner_for(params, cp)
row = next(row for row in rows(owner) if row.key == 'SNGHack')
assert row.available and row.value == 'Off'
request = row_change(row)
assert request is not None
request = replace(request, confirmation=True)
from openpilot.starpilot.ui.vehicle_bool import VEHICLE_BOOL_KEYS, confirmation_question
assert request.key in VEHICLE_BOOL_KEYS and 'Toyota stop-and-go' in confirmation_question(request)
assert owner.apply(request) and params.get_bool('SNGHack')
assert not owner.apply(request)
ci = CarInterface(cp)
prefs = VehicleStartupPreferences.read(params, enabled=True)
prefs.configure_controller(ci)
assert ci.CC.sng_hack and cp.to_dict() == before
params.put_bool('SNGHack', False, block=True)
assert ci.CC.sng_hack
VehicleStartupPreferences.read(params, enabled=True).configure_controller(ci)
assert not ci.CC.sng_hack
@pytest.mark.parametrize('veto', ('stock', 'automatic', 'pedal', 'passive', 'dashcam', 'notcar', 'native'))
def test_donor_eligibility_and_native_identity_are_preserved(veto):
cp = factory()
assert eligible(cp)
if veto == 'stock':
cp.openpilotLongitudinalControl = False
elif veto == 'pedal':
cp.deprecated.enableGasInterceptor = True
elif veto == 'native':
cp.safetyConfigs[0].safetyModel = 'noOutput'
else:
setattr(cp, {'automatic': 'autoResumeSng', 'passive': 'passive', 'dashcam': 'dashcamOnly', 'notcar': 'notCar'}[veto], True)
assert not eligible(cp)
with OpenpilotPrefix():
assert all(row.key != 'SNGHack' for row in rows(owner_for(Params(), cp)))
def test_onroad_and_safe_mode_cannot_enable_override():
with OpenpilotPrefix():
params, cp = Params(), factory()
owner = owner_for(params, cp, parked=False)
assert not next(row for row in rows(owner, False) if row.key == 'SNGHack').available
params.put_bool('SNGHack', True, block=True)
params.put_bool('SafeMode', True, block=True)
ci = CarInterface(cp)
VehicleStartupPreferences.read(params, enabled=True).configure_controller(ci)
assert not ci.CC.sng_hack
@pytest.mark.parametrize('profile', PROFILES)
@pytest.mark.parametrize('active', (False, True))
def test_stop_latch_override_requires_qualified_active_long(profile, active):
cp, controller, command, state = setup(profile)
controller.sng_hack = eligible(cp)
state.out.gasPressed = False
state.out.standstill = state.out.cruiseState.standstill = True
state.out.vEgo = state.out.vEgoRaw = 0.
state.pcm_acc_status = 8
command.longActive = active
command.actuators.accel = -.5
controller.standstill_req = True
frames = acc_frames(cp, controller, command, state)
assert frames
assert all(bool(frame['RELEASE_STANDSTILL']) == (active and eligible(cp)) for frame in frames)
assert all(not frame['CANCEL_REQ'] for frame in frames)
@@ -0,0 +1,33 @@
"""Saved SNG opt-in retains the physical/native stop-engagement contract."""
import pytest
from opendbc.car.toyota.sng import eligible
from openpilot.common.params import Params
from openpilot.starpilot.vehicle_preferences import VehicleStartupPreferences
from openpilot.starpilot.car.toyota.tests import test_prius_stop_engagement_callers as helpers
from openpilot.starpilot.car.toyota.tests.test_toyota_cpp_transport import host_heartbeat_bridge as host_heartbeat_bridge
source_matched_native = helpers.source_matched_native
daemon_gc_scope = helpers.daemon_gc_scope
@pytest.mark.parametrize('advert', (False, True))
@pytest.mark.parametrize('stock', (False, True))
def test_saved_sng_reaches_real_native_stop_callers(monkeypatch, host_heartbeat_bridge, advert, stock):
put, configure = Params.put_bool, VehicleStartupPreferences.configure_controller
bound = []
def saved(params, key, value, *args, **kwargs):
result = put(params, key, value, *args, **kwargs)
if key == 'ToyotaAutoHold':
put(params, 'SNGHack', True, block=True)
return result
def bind(prefs, ci):
configure(prefs, ci)
assert ci.CC.sng_hack == eligible(ci.CP)
bound.append(ci.CC.sng_hack)
monkeypatch.setattr(Params, 'put_bool', saved)
monkeypatch.setattr(VehicleStartupPreferences, 'configure_controller', bind)
helpers.test_prius_accepts_stop_engagement_before_brake_release(monkeypatch, host_heartbeat_bridge, advert, stock)
assert bound and bound[-1] == (not stock)
@@ -1121,7 +1121,7 @@ class SettingsGatewayTest(unittest.TestCase):
from opendbc.car.mazda.values import CAR
from openpilot.starpilot.lateral.controller_selection import policy_for
cp = interfaces[CAR.MAZDA_CX5_2022].get_non_essential_params(CAR.MAZDA_CX5_2022)
self.assertIsNone(policy_for(cp))
self.assertEqual(policy_for(cp), 'generic')
self.context.value = AuthorityContext(True, cp, cp.as_reader().as_builder().to_bytes())
page = self.page('torque')
index = next(i for i, row in enumerate(page['rows']) if row['label'] == 'Turn Assist')
@@ -5,9 +5,11 @@ from __future__ import annotations
from dataclasses import dataclass
from enum import StrEnum
import json
import math
from pathlib import Path
from opendbc.car import structs
from opendbc.car.values import PLATFORMS
DOCUMENT_KEY = 'LateralControllerSelection'
@@ -62,6 +64,25 @@ KNOWN_POLICIES.update(dict.fromkeys((
'HYUNDAI_KONA_EV_2022',
), 'hyundai'))
KNOWN_POLICIES.update({str(identity): platform.__class__.__module__.split('.')[-2]
for identity, platform in PLATFORMS.items()})
def generic_torque_supported(cp) -> bool:
try:
platform = PLATFORMS.get(str(cp.carFingerprint))
if (platform is None or platform.__class__.__module__.split('.')[-2] != str(cp.brand) or
cp.notCar or cp.passive or cp.dashcamOnly or
cp.steerControlType != structs.CarParams.SteerControlType.torque or cp.lateralTuning.which() != 'torque'):
return False
tune = cp.lateralTuning.torque
return (math.isfinite(tune.latAccelFactor) and tune.latAccelFactor > 0 and
math.isfinite(tune.latAccelOffset) and math.isfinite(tune.friction) and tune.friction >= 0 and
math.isfinite(tune.steeringAngleDeadzoneDeg) and tune.steeringAngleDeadzoneDeg >= 0)
except (AttributeError, TypeError, ValueError, OverflowError):
return False
class ControllerMode(StrEnum):
STANDARD = 'standard'
STARPILOT = 'starpilot'
@@ -75,7 +96,7 @@ class ControllerSelection:
def policy_for(CP) -> str | None:
"""Return only an already-implemented, exact torque policy identity."""
"""Prefer exact vehicle policies, otherwise use the shared torque law."""
if CP.notCar:
return None
from openpilot.starpilot.lateral.hkg_vehicle_policy import policy_for as hkg_vehicle_policy
@@ -132,7 +153,7 @@ def policy_for(CP) -> str | None:
CP.steerControlType == structs.CarParams.SteerControlType.torque and
CP.lateralTuning.which() == 'torque' and not CP.dashcamOnly and not CP.passive):
return 'ioniq6'
return None
return "generic" if generic_torque_supported(CP) else None
def turn_assist_supported(CP) -> bool:
@@ -151,7 +172,7 @@ def default_selection(CP) -> ControllerSelection:
policy = policy_for(CP)
from openpilot.starpilot.lateral.hkg_vehicle_policy import PROFILES
forte_non_scc = policy == 'kia_forte' and str(CP.carFingerprint) in ('KIA_FORTE_2019_NON_SCC', 'KIA_FORTE_2021_NON_SCC')
mode = ControllerMode.STARPILOT if policy and (policy not in PROFILES or forte_non_scc) else ControllerMode.STANDARD
mode = ControllerMode.STARPILOT if policy and policy != 'generic' and (policy not in PROFILES or forte_non_scc) else ControllerMode.STANDARD
return ControllerSelection(mode, policy, 'default')
@@ -239,9 +260,12 @@ def learning_allowed(params, CP, *, selection: ControllerSelection | None = None
preference = read_preference(params, CP)
if not preference.valid or not preference.enabled:
return False
if policy_for(CP) is None:
policy = policy_for(CP)
if policy is None:
return True
selected = read_selection(params, CP) if selection is None else selection
if policy == 'generic':
return selected.source != 'invalid'
from openpilot.starpilot.lateral.gm_geometry_runtime import read_geometry
geometry = read_geometry(params, CP)
if geometry.force_off:
@@ -0,0 +1,19 @@
"""Shared StarPilot torque shaping using the vehicle's own calibration and conversion."""
from openpilot.common.scalar_clip import clip_scalar
from openpilot.starpilot.lateral.controller_selection import generic_torque_supported
from openpilot.starpilot.lateral.hkg_torque_policy import HKGShapedTorquePolicy
from openpilot.starpilot.lateral.torque_shaping import get_standard_friction_threshold
class GenericTorquePolicy(HKGShapedTorquePolicy):
supported_cp = staticmethod(generic_torque_supported)
def feedforward(self, value, setpoint, jerk, speed):
return value
def friction(self, setpoint, jerk, speed):
return get_standard_friction_threshold(speed), 1.0
def output(self, value, setpoint, speed):
return clip_scalar(value, -self.parent.steer_max, self.parent.steer_max)
@@ -95,10 +95,11 @@ class TestControllerSelection(unittest.TestCase):
wrong = ioniq.as_reader().as_builder()
wrong.notCar = True
self.assertIsNone(policy_for(wrong))
with self.assertRaises(ValueError):
unsupported = cp_for(TOYOTA.TOYOTA_RAV4_TSS2_2022)
LatControlTorque(unsupported.as_reader(), interfaces[TOYOTA.TOYOTA_RAV4_TSS2_2022](unsupported), DT_CTRL,
controller_mode=ControllerMode.STARPILOT)
generic = cp_for(TOYOTA.TOYOTA_RAV4_TSS2_2022)
controller = LatControlTorque(generic.as_reader(), interfaces[TOYOTA.TOYOTA_RAV4_TSS2_2022](generic), DT_CTRL,
controller_mode=ControllerMode.STARPILOT)
self.assertEqual(controller.controller_policy, 'generic')
self.assertIsNotNone(selected_policy(controller))
def test_standard_skips_policy_constructor_and_ioniq_factor_multiplier(self):
for vehicle in (HYUNDAI.HYUNDAI_IONIQ_6, HYUNDAI.GENESIS_G70_2020, TOYOTA.TOYOTA_COROLLA_TSS2):
@@ -76,10 +76,10 @@ def test_exact_admission_and_saved_standard_keeps_upstream_controller():
changed = cp.as_reader().as_builder()
changed.lateralTuning.init('pid')
assert not policy.supported_cp(changed)
for vehicle in (CAR.GENESIS_GV70_1ST_GEN, CAR.GENESIS_GV70_ELECTRIFIED_2ND_GEN):
for vehicle, expected in ((CAR.GENESIS_GV70_1ST_GEN, 'generic'), (CAR.GENESIS_GV70_ELECTRIFIED_2ND_GEN, None)):
sibling = interfaces[vehicle].get_non_essential_params(vehicle)
assert not policy.supported_cp(sibling)
assert policy_for(sibling) is None
assert policy_for(sibling) == expected
def test_stabilization_only_changes_feedforward_feedback_remains_immediate():
@@ -0,0 +1,149 @@
import json
import math
from types import SimpleNamespace
import pytest
from opendbc.car import DT_CTRL, structs
from opendbc.car.car_helpers import interfaces
from opendbc.car.hyundai.values import CAR
from opendbc.car.vehicle_model import VehicleModel
from openpilot.common.params import Params
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
from openpilot.starpilot.lateral.controller_selection import (
DOCUMENT_KEY, ControllerMode, default_selection, learning_allowed,
parse_document, policy_for, read_selection, replace_mode,
)
from openpilot.starpilot.lateral.torque_extension import selected_policy
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner
from openpilot.starpilot.ui.feature_settings_state import row_change
def cp_for(identity):
return interfaces[identity].get_non_essential_params(identity)
def owner_for(params, cp):
return FeatureSettingsOwner(params, lambda group: True, vehicle_fingerprint=lambda: str(cp.carFingerprint),
vehicle_params=lambda: cp)
def test_niro_2023_choice_is_visible_and_saves(tmp_path):
params = Params(str(tmp_path))
cp = cp_for(CAR.KIA_NIRO_EV_2ND_GEN)
assert cp.steerControlType == structs.CarParams.SteerControlType.torque
assert cp.lateralTuning.which() == 'torque'
owner = owner_for(params, cp)
snapshot = owner.snapshot('torque', parked=True, system_long=False, lateral_context=False, metric=False)
row = next((row for row in snapshot.rows if row.key == DOCUMENT_KEY), None)
assert row is not None, '2023 Niro EV must expose the controller selector'
assert row.choices == ('Stock Controller', 'StarPilot Controller')
assert row.value == 'Stock Controller'
request = row_change(row)
assert request is not None and request.value == 'StarPilot Controller'
assert owner.apply(request)
assert read_selection(params, cp).mode == ControllerMode.STARPILOT
assert not owner.apply(request), 'stale expected bytes must remain rejected'
ci = interfaces[CAR.KIA_NIRO_EV_2ND_GEN](cp)
controller = LatControlTorque(cp.as_reader(), ci, DT_CTRL, controller_mode=read_selection(params, cp).mode)
assert selected_policy(controller) is not None
assert controller.controller_policy == 'generic'
assert controller.torque_params.to_dict() == cp.lateralTuning.torque.to_dict()
def test_every_registered_torque_tune_can_save_both_choices():
from openpilot.starpilot.lateral.controller_selection import generic_torque_supported
checked = []
for identity in interfaces:
cp = cp_for(identity)
if not generic_torque_supported(cp):
continue
assert policy_for(cp) is not None, identity
for mode in ControllerMode:
raw = replace_mode(None, cp, mode)
assert parse_document(raw)['vehicles'][str(identity)]['brand'] == str(cp.brand)
from openpilot.starpilot.lateral.controller_selection import selection_from_bytes
selected = selection_from_bytes(cp, raw)
assert selected.mode == mode and selected.source == 'saved', identity
checked.append(str(identity))
assert str(CAR.KIA_NIRO_EV_2ND_GEN) in checked
assert len(checked) > 100
print('Registered torque controller selection coverage:', len(checked))
def test_saved_vehicle_choices_remain_independent():
from opendbc.car.honda.values import CAR as HONDA
niro = cp_for(CAR.KIA_NIRO_EV_2ND_GEN)
honda = cp_for(HONDA.HONDA_FIT_4G)
raw = replace_mode(None, niro, ControllerMode.STARPILOT)
raw = replace_mode(raw, honda, ControllerMode.STANDARD)
from openpilot.starpilot.lateral.controller_selection import selection_from_bytes
assert selection_from_bytes(niro, raw).mode == ControllerMode.STARPILOT
assert selection_from_bytes(honda, raw).mode == ControllerMode.STANDARD
@pytest.mark.parametrize('failure', ['brand', 'unknown', 'angle', 'pid', 'passive', 'dashcam', 'notCar', 'nan', 'factor', 'friction'])
def test_generic_refuses_invalid_controller_configuration(failure):
from openpilot.starpilot.lateral.controller_selection import generic_torque_supported
cp = cp_for(CAR.KIA_NIRO_EV_2ND_GEN)
if failure == 'brand':
cp.brand = 'toyota'
elif failure == 'unknown':
cp.carFingerprint = 'UNREGISTERED'
elif failure == 'angle':
cp.steerControlType = structs.CarParams.SteerControlType.angle
elif failure == 'pid':
cp.lateralTuning.init('pid')
elif failure in ('passive', 'dashcam', 'notCar'):
setattr(cp, 'dashcamOnly' if failure == 'dashcam' else failure, True)
elif failure == 'nan':
cp.lateralTuning.torque.latAccelFactor = float('nan')
elif failure == 'factor':
cp.lateralTuning.torque.latAccelFactor = 0
elif failure == 'friction':
cp.lateralTuning.torque.friction = -1
assert not generic_torque_supported(cp)
assert policy_for(cp) is None
def test_generic_defaults_and_existing_learning_are_preserved(tmp_path):
params = Params(str(tmp_path))
cp = cp_for(CAR.KIA_NIRO_EV_2ND_GEN)
assert default_selection(cp).mode == ControllerMode.STANDARD
assert learning_allowed(params, cp)
params.put_bool('ForceAutoTuneOff', True, block=True)
assert not learning_allowed(params, cp)
params.put(DOCUMENT_KEY, json.loads(replace_mode(None, cp, ControllerMode.STARPILOT)), block=True)
assert not learning_allowed(params, cp)
def test_shared_controller_uses_real_interfaces_and_keeps_output_limits():
checked = set()
for identity in interfaces:
cp = cp_for(identity)
if policy_for(cp) != 'generic' or str(cp.brand) in checked:
continue
checked.add(str(cp.brand))
ci = interfaces[identity](cp)
standard = LatControlTorque(cp.as_reader(), ci, DT_CTRL, controller_mode=ControllerMode.STANDARD)
custom = LatControlTorque(cp.as_reader(), ci, DT_CTRL, controller_mode=ControllerMode.STARPILOT)
assert standard.starpilot_extension is None
assert custom.starpilot_extension is not None
assert custom.torque_params.to_dict() == standard.torque_params.to_dict()
for acceleration in (-1., -.2, 0., .2, 1.):
assert (custom.torque_from_lateral_accel(acceleration, custom.torque_params) ==
standard.torque_from_lateral_accel(acceleration, standard.torque_params))
assert custom.pid.pos_limit == standard.pid.pos_limit
assert custom.pid.neg_limit == standard.pid.neg_limit
state = structs.CarState.new_message()
vm = VehicleModel(cp)
params = SimpleNamespace(angleOffsetDeg=0., roll=0.01)
for active, speed, curvature, pressed, limited in ((False, 0., 0., False, False), (True, 12., .001, False, False),
(True, 25., -.001, False, True), (True, 15., .002, True, False), (False, 15., .002, False, False)):
state.vEgo, state.steeringPressed = speed, pressed
output, _, message = custom.update(active, state, vm, params, limited, curvature, False, .2)
assert math.isfinite(output) and abs(output) <= custom.steer_max
assert message.active == active
if not active:
assert output == 0
assert 'hyundai' in checked and len(checked) >= 3
@@ -92,7 +92,7 @@ def test_generic_partial_manual_tune_honors_scoped_learning_preference(enabled):
from openpilot.starpilot.lateral.tests.test_torque_family import learner
from openpilot.starpilot.lateral.torque_runtime import TorqueHost, read_settings
cp = interfaces['HONDA_FIT_4G'].get_non_essential_params('HONDA_FIT_4G')
assert cp.lateralTuning.which() == 'torque' and policy_for(cp) is None
assert cp.lateralTuning.which() == 'torque' and policy_for(cp) == 'generic'
with OpenpilotPrefix():
params = Params()
params.put(KEY, replace_preference(None, cp, enabled), block=True)
@@ -31,6 +31,9 @@ def create_extension(parent, cp, mode, selected, *, turn_assist=False):
raise ValueError('HKG torque selection does not match CarParams')
policy = HKGVehicleTorquePolicy(parent, cp)
return TorqueExtension(policy, parameter_factor=policy.FACTOR_MULT)
if selected == 'generic':
from openpilot.starpilot.lateral.generic_torque_policy import GenericTorquePolicy
return TorqueExtension(GenericTorquePolicy(parent, cp))
if selected == 'ioniq6':
from openpilot.starpilot.lateral.ioniq6_policy import Ioniq6TorquePolicy
policy = Ioniq6TorquePolicy(parent, cp, turn_assist=turn_assist)
@@ -187,7 +187,7 @@ class TorqueHost:
# existing live manual preference for other optional replay-host vehicles.
if allow_learning is None:
from openpilot.starpilot.lateral.controller_selection import policy_for
if policy_for(CP) is None:
if policy_for(CP) in (None, 'generic'):
from openpilot.starpilot.lateral.learning_preferences import read_preference
preference = read_preference(params, CP)
allow_learning = preference.valid and preference.enabled
@@ -82,7 +82,7 @@ BOOL_DEFAULTS = {
"SubaruStopStartOff": False,
"SubaruSNG": False,
"SubaruRedneckCruise": False,
"ReverseCruise": False, "ToyotaAutoHold": False, "JeepBrakeHold": False, "VoltSNG": False, "GMAutoHold": False, "VoltOnePedalMode": False,
"ReverseCruise": False, "ToyotaAutoHold": False, "SNGHack": False, "JeepBrakeHold": False, "VoltSNG": False, "GMAutoHold": False, "VoltOnePedalMode": False,
"RemapCancelToDistance": False, "LongPitch": True, "DisableOpenpilotLongitudinal": False,
"SpeedLimitController": False, "ShowSpeedLimits": True,
"SLCConfirmation": False,
@@ -427,6 +427,25 @@ class FeatureSettingsOwner:
return commit_exact(self.params, key="JeepBrakeHold", max_bytes=128, raw=b"1" if request.value == "On" else b"0",
expected=request.expected, authorized=authorized, temp_prefix=".jeep-hold-").verified
def _toyota_sng_capability(self):
from opendbc.car.toyota.sng import eligible
cp = self.vehicle_params()
if not eligible(cp):
return None
return (str(cp.carFingerprint), int(cp.flags), bool(cp.autoResumeSng),
tuple((str(config.safetyModel), int(config.safetyParam)) for config in cp.safetyConfigs))
def _apply_toyota_sng(self, request):
if not request.confirmation or request.value not in ("Off", "On") or request.dependencies:
return False
def authorized():
return (self.authority("parked_preferences") and self.authority("vehicle") and
request.capability is not None and request.capability == self._toyota_sng_capability() and
request.vehicle_fingerprint == self.vehicle_fingerprint() and request.expected in (None, b"0", b"1"))
return commit_exact(self.params, key="SNGHack", max_bytes=8, expected=request.expected,
raw=b"1" if request.value == "On" else b"0", authorized=authorized,
temp_prefix=".toyota-sng-").verified
def _auto_hold_capability(self) -> tuple | None:
from opendbc.car.toyota.interface import toyota_auto_hold_supported
cp = self.vehicle_params()
@@ -820,7 +839,7 @@ class FeatureSettingsOwner:
FeatureRow("", "Always On Lateral", "Keep steering assistance active independently of cruise control", page=FeaturePage.AOL, available=True),
FeatureRow("", "Wheel Controls", "Assign steering-wheel buttons and cruise behavior", page=FeaturePage.WHEEL, available=True)]
if (self._long_pitch_capability() is not None or self._bolt_disable_capability() is not None or
self._auto_hold_capability() is not None or self._jeep_hold_capability() is not None or
self._auto_hold_capability() is not None or self._toyota_sng_capability() is not None or self._jeep_hold_capability() is not None or
self._toyota_radar_capability() is not None or self._pedal_setup_capability() is not None or
self.subaru_smoothing.capability() is not None or
self._subaru_avh_capability() is not None or self._subaru_startup_capability() is not None or self._subaru_sng_capability() is not None or
@@ -845,6 +864,12 @@ class FeatureSettingsOwner:
"Hold the brake for over one second at a complete stop with cruise main on and cruise disengaged. " +
"Holds until you press the gas; normal cruise stop-and-go is unchanged. Applies after the next startup.")
rows.extend([replace(row, available=row.available and self._readable("ToyotaAutoHold"), capability=capability)] if capability is not None else [])
sng_capability = self._toyota_sng_capability()
if sng_capability is not None:
row = self._bool_row("SNGHack", "Stop-and-Go Hack",
configurable and parked and self.authority("vehicle") and self.authority("parked_preferences"),
"Automatic resume for Toyota/Lexus vehicles without factory stop-and-go. Applies after the next startup.")
rows.append(replace(row, available=row.available and self._readable("SNGHack"), capability=sng_capability))
radar_capability = self._toyota_radar_capability()
if radar_capability is not None:
from openpilot.starpilot.car.toyota.radar_preferences import CHOICES, KEY
@@ -1380,6 +1405,8 @@ class FeatureSettingsOwner:
return self._apply_jeep_hold(request)
if key == "AlphaLongitudinalEnabled":
return self._apply_alpha_long(request)
if key == "SNGHack":
return self._apply_toyota_sng(request)
if key == "ToyotaAutoHold":
return self._apply_auto_hold(request)
if key == "ToyotaRadarMode":
@@ -71,10 +71,11 @@ class TestControllerFeature(unittest.TestCase):
self.cp.notCar = True
self.assertIsNone(self.row())
def test_registered_stock_torque_uses_scoped_learning_without_controller_choice(self):
def test_registered_stock_torque_has_both_controllers_and_scoped_learning(self):
self.cp = interfaces[HONDA.HONDA_FIT_4G].get_non_essential_params(HONDA.HONDA_FIT_4G)
self.assertEqual(self.cp.lateralTuning.which(), 'torque')
self.assertIsNone(self.row())
self.assertEqual(self.require_row().choices, ('Stock Controller', 'StarPilot Controller'))
self.assertEqual(self.require_row().value, 'Stock Controller')
learning = self.require_row('ForceAutoTuneOff')
self.assertEqual(learning.label, 'Use StarPilot Auto Learn')
self.assertEqual(learning.value, 'On')
@@ -365,7 +365,7 @@ class FeatureSettingsOwnerTests(unittest.TestCase):
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.assertEqual(policy_for(cp), 'generic')
self.owner.vehicle_params = lambda: cp
self.owner.controller.vehicle_params = self.owner.vehicle_params
self.fingerprint = cp.carFingerprint
+3 -1
View File
@@ -1,7 +1,7 @@
"""Confirmation text for parked vehicle preferences."""
VEHICLE_BOOL_KEYS = frozenset(("AlphaLongitudinalEnabled", "SubaruAvhStartup", "SubaruStopStartOff", "SubaruSNG", "SubaruRedneckCruise",
"GMPedalLongitudinal", "DisableOpenpilotLongitudinal", "LongPitch", "VoltSNG", "GMAutoHold", "VoltOnePedalMode",
"GMPedalLongitudinal", "DisableOpenpilotLongitudinal", "LongPitch", "VoltSNG", "SNGHack", "GMAutoHold", "VoltOnePedalMode",
"TeslaAOLScreenTap", "TeslaAOLDisengageOnBrake",
"RemoteStartBootsComma", "HKGRemoteStartBootsComma", "IgnoreIgnitionLine", "TeslaWakeOnCAN"))
@@ -30,6 +30,8 @@ def confirmation_question(request):
return f"{verb} Subaru stop-and-go assistance after the next startup? Factory ACC retains speed control."
if request.key == "SubaruRedneckCruise":
return f"{verb} software cruise after the next startup? Stock ACC keeps pedal control."
if request.key == "SNGHack":
return f"{verb} Toyota stop-and-go assistance after the next startup?"
if request.key == "VoltSNG":
return f"{verb} Volt stop-and-go assistance after the next startup?"
if request.key == "GMAutoHold":
+6 -1
View File
@@ -20,6 +20,7 @@ def bolt_disable_supported(cp) -> bool:
@dataclass(frozen=True)
class VehicleStartupPreferences:
toyota_auto_hold: bool = False
toyota_sng: bool = field(default=False, kw_only=True)
jeep_brake_hold: bool = field(default=False, kw_only=True)
turn_assist: bool = False
gm_long_pitch: bool = True
@@ -116,7 +117,9 @@ class VehicleStartupPreferences:
ford_lateral_mode=ford_mode(params, enabled=enabled),
ford_pinion=bool(enabled and read_saved(params, "SafeMode", 8) in ((None, True), (b"0", True)) and
read_saved(params, "FordPrefSteerAngleCurvature", 8) == (b"1", True)), tesla_screen=screen,
tesla_screen_brake=screen_brake, toyota_auto_hold=toyota, jeep_brake_hold=jeep_hold,
tesla_screen_brake=screen_brake, toyota_auto_hold=toyota,
toyota_sng=bool(enabled and readable and safe in (None, b"0") and
read_saved(params, "SNGHack", 8) == (b"1", True)), jeep_brake_hold=jeep_hold,
gm_longitudinal_tune=selected_tune(params, enabled=enabled),
toyota_lock_doors=bool(enabled and readable and safe in (None, b"0") and
read_saved(params, "LockDoors", 8) == (b"1", True)),
@@ -276,6 +279,8 @@ class VehicleStartupPreferences:
is_volt_one_pedal, camera_acc_pedal_profile, volt_cc_pedal_profile)
cp = ci.CP
if ci.CC is not None and cp.brand == "toyota" and not cp.passive and not cp.dashcamOnly and not cp.notCar:
from opendbc.car.toyota.sng import eligible
ci.CC.sng_hack = self.toyota_sng and eligible(cp)
ci.CC.door_locks.lock = self.toyota_lock_doors
ci.CC.door_locks.unlock = self.toyota_unlock_doors
if ci.CC is not None and is_volt_longitudinal(cp):
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once
extern const uint8_t gitversion[29];
const uint8_t gitversion[29] = "DEV-b3b96ade-Pa5919516-DEBUG";
const uint8_t gitversion[29] = "DEV-0fcf1277-Pa5919516-DEBUG";
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
DEV-b3b96ade-Pa5919516-DEBUG
DEV-0fcf1277-Pa5919516-DEBUG
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "d5495bdc0d7d08ad60b75642bb28e5b88d60766332d0d8af6a0d1af0cfa8c4b4", "schema_version": 1}
{"protected_inventory_sha256": "2c4e52ddf1a87907cb47f05c74f79df2e6762489e5d13d58a53469c7c9664d69", "schema_version": 1}
+2
View File
@@ -142,6 +142,8 @@ AOL_LIFECYCLE_TARGETS = (
"openpilot/starpilot/car/toyota/tests/test_camera_2aa_native.py",
"openpilot/starpilot/car/toyota/tests/test_prius_sdsu_filter.py",
"openpilot/starpilot/car/toyota/tests/test_prius_sdsu_callers.py",
"openpilot/starpilot/car/toyota/tests/test_sng.py",
"openpilot/starpilot/car/toyota/tests/test_sng_callers.py",
"openpilot/starpilot/car/toyota/tests/test_brake_stop_cancel.py",
"openpilot/starpilot/car/toyota/tests/test_prius_stop_engagement_callers.py",
"opendbc_repo/opendbc/safety/tests/test_toyota_prius_sdsu.py",