diff --git a/opendbc_repo/opendbc/safety/tests/test_hyundai_torque_stock_transition.py b/opendbc_repo/opendbc/safety/tests/test_hyundai_torque_stock_transition.py new file mode 100644 index 0000000000..4e156ddd79 --- /dev/null +++ b/opendbc_repo/opendbc/safety/tests/test_hyundai_torque_stock_transition.py @@ -0,0 +1,67 @@ +import pytest + +from opendbc.car.hyundai.canfd_stock_aol import STOCK_AOL_WORDS +from opendbc.safety.tests import test_hyundai_shared_canfd_stock_aol as fixture + + +def rx(case, name, bus, counter, values): + maximum = 16 if name == 'CRUISE_BUTTONS' else 256 + frame = case.packer.make_can_msg(name, bus, dict(values, COUNTER=counter % maximum)) + assert case.safety.safety_rx_hook(case.packet(frame)) + + +def steering(case, word, counter): + name = 'LKAS_ALT' if word & 0x80 else 'LKAS' if word & 0x10 else 'LFA' + frame = case.packer.make_can_msg(name, 0, {'COUNTER': counter, 'StrTqReqVal': 1, + 'ActToiSta': 1, 'LKA_OptUsmSta': 2, 'Damping_Gain': 100}) + return case.packet(frame) + + +@pytest.mark.parametrize('word', sorted(STOCK_AOL_WORDS)) +@pytest.mark.parametrize('gesture', ['cancel', 'brake']) +@pytest.mark.parametrize('veto', ['host_withdrawal', 'bad_source_checksum']) +def test_actual_stock_torque_brake_and_cancel_keep_only_existing_lateral_permission(word, gesture, veto): + case = fixture.TestHyundaiSharedCanfdStockAol(methodName='runTest') + case.setUp() + case.mode(word) + case.healthy(word) + case.safety.set_aol_test_heartbeat(True) + case.safety.aol_set_host_request(1) + assert not case.safety.get_controls_allowed() + assert case.safety.aol_get_permission_mask() == 0 + assert not case.safety.safety_tx_hook(steering(case, word, 0)) + case.gesture(word) + case.safety.aol_set_host_request(1) + assert case.safety.aol_get_permission_mask() == 1 + pt = 1 if word & 0x10 else 0 + buttons = 'CRUISE_BUTTONS_ALT' if word & 0x20 else 'CRUISE_BUTTONS' + for counter, values in ((9, {'ADAPTIVE_CRUISE_MAIN_BTN': 1}), + (10, {}), (11, {'CRUISE_BUTTONS': 2}), (12, {})): + rx(case, buttons, pt, counter, values) + scc = 2 if word & 8 else pt + rx(case, 'SCC_CONTROL', scc, 7, {'MainMode_ACC': 1, 'ACCMode': 1, 'VSetDis': 90}) + assert case.safety.get_controls_allowed() + case.safety.aol_set_host_request(3) + assert case.safety.aol_get_permission_mask() == 1 + case.safety.aol_set_host_request(1) + if gesture == 'cancel': + rx(case, buttons, pt, 13, {'CRUISE_BUTTONS': 4}) + else: + rx(case, 'TCS', pt, 7, {'DriverBraking': 1}) + assert bool(case.safety.get_controls_allowed()) == (gesture == 'cancel') + assert case.safety.aol_get_permission_mask() == 1 + assert case.safety.safety_tx_hook(steering(case, word, 1)) + rx(case, 'SCC_CONTROL', scc, 8, {'MainMode_ACC': 1, 'ACCMode': 0, 'VSetDis': 90}) + assert not case.safety.get_controls_allowed() + assert case.safety.aol_get_permission_mask() == 1 + assert case.safety.safety_tx_hook(steering(case, word, 2)) + assert not case.safety.safety_tx_hook(case.packet(case.packer.make_can_msg('SCC_CONTROL', pt, {'COUNTER': 9}))) + if veto == 'host_withdrawal': + case.safety.aol_set_host_request(0) + else: + address, raw, bus = case.packer.make_can_msg('TCS', pt, {'COUNTER': 8, 'DriverBraking': int(gesture == 'brake')}) + corrupt = bytearray(raw) + corrupt[0] ^= 1 + assert not case.safety.safety_rx_hook(case.packet((address, bytes(corrupt), bus))) + assert case.safety.aol_get_permission_mask() == 0 + assert not case.safety.safety_tx_hook(steering(case, word, 3)) diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index 2c43ec8b5a..5b621af22f 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -1268,8 +1268,11 @@ class SelfdriveD: user_disable = self.events.contains(ET.USER_DISABLE) if user_disable: from opendbc.car.hyundai.canfd_angle_aol import qualified as angle_qualified + from opendbc.car.hyundai.canfd_stock_aol import qualified as stock_canfd_qualified - if angle_qualified(self.CP, marked_only=True): + if (angle_qualified(self.CP, marked_only=True) or + self.CP.brand == 'hyundai' and CS.brakePressed and len(self.CP.safetyConfigs) == 1 and + stock_canfd_qualified(self.CP, marked_only=True)): native = current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id) if stock_lateral_role(self, CS, native, intent, allow_independent_cancel=True, allow_independent_brake=True, now_ns=now_ns): diff --git a/openpilot/starpilot/aol/source_availability.py b/openpilot/starpilot/aol/source_availability.py index 5598fc1020..cc937628e0 100644 --- a/openpilot/starpilot/aol/source_availability.py +++ b/openpilot/starpilot/aol/source_availability.py @@ -1,4 +1,5 @@ """Classify ordinary host source loss without granting commands or engagement.""" +from math import isfinite from opendbc.car.structs import car from openpilot.cereal import log from openpilot.selfdrive.selfdrived.events import ET @@ -26,12 +27,16 @@ def stock_lateral_role(self, CS, native, intent=None, *, allow_independent_cance brake_role = False if allow_independent_brake: from opendbc.car.hyundai.canfd_angle_aol import command_allowed + from opendbc.car.hyundai.canfd_stock_aol import qualified as stock_canfd_qualified settings = getattr(self, 'aol_settings', None) previous = getattr(self, 'aol_axis_decision', None) - brake_role = bool(settings is not None and settings.enabled and CS.vEgo >= settings.pause_brake_mps and - CS.brakePressed and not CS.regenBraking and previous is not None and previous.lateral_active and - previous.native_acknowledged and not previous.longitudinal_active and command_allowed(self.CP, CS)) + brake_role = bool(settings is not None and settings.enabled and CS.brakePressed and not CS.regenBraking and + previous is not None and previous.lateral_active and previous.native_acknowledged and not previous.longitudinal_active and + (CS.vEgo >= settings.pause_brake_mps and command_allowed(self.CP, CS) or + len(self.CP.safetyConfigs) == 1 and stock_canfd_qualified(self.CP, marked_only=True) and + CS.gearShifter == car.CarState.GearShifter.drive and isfinite(CS.vEgoRaw) and isfinite(CS.vEgo) and + (settings.pause_brake_mps <= 0 or CS.vEgo >= settings.pause_brake_mps or CS.standstill))) rules = self.events.event_rules stock_user = stock_lateral_user_event(self.CP) users = [name for name in self.events.names if ET.USER_DISABLE in rules.get(name, {})] diff --git a/openpilot/starpilot/aol/tests/test_torque_stock_brake_guards.py b/openpilot/starpilot/aol/tests/test_torque_stock_brake_guards.py new file mode 100644 index 0000000000..c563c27f50 --- /dev/null +++ b/openpilot/starpilot/aol/tests/test_torque_stock_brake_guards.py @@ -0,0 +1,269 @@ +from contextlib import ExitStack, contextmanager +from pathlib import Path +from types import SimpleNamespace + +import pytest + +from opendbc.car import gen_empty_fingerprint, structs +from opendbc.car.hyundai.canfd_stock_aol import qualified +from opendbc.car.hyundai.hyundaicanfd import CanBus +from opendbc.car.hyundai.interface import CarInterface +from opendbc.car.hyundai.values import CAR, HyundaiFlags +from openpilot.cereal import log +from openpilot.cereal.services import SERVICE_LIST +from openpilot.common.params import Params +from openpilot.common.prefix import OpenpilotPrefix +from openpilot.starpilot.aol.intent import read_settings +from openpilot.starpilot.aol.tests.stock_lateral_test_helpers import car_state, hkg_family_case + +MODELS = [(CAR.GENESIS_GV70_1ST_GEN, False), (CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, True), (CAR.KIA_EV6, True)] + + +@contextmanager +def configured_callers(identity, lka, guard, *, hybrid=False, lkas_action=9): + with ExitStack() as cleanup: + with OpenpilotPrefix(): + saved = Params() + saved.put_bool('AlwaysOnLateral', True, block=True) + saved.put('LKASButtonControl', lkas_action, block=True) + saved.put('MainCruiseButtonControl', 9 if guard == 'mapped_main' else 0, block=True) + for key in ('CancelButtonControl', 'LongCancelButtonControl', 'VeryLongCancelButtonControl'): + saved.put(key, 3 if guard == 'configured_cancel' and key == 'CancelButtonControl' else 0, block=True) + saved.put('AolBrakePauseSpeedMps', 20.0 if guard == 'brake_pause_speed' else 0.0, block=True) + case = hkg_family_case('hkg_canfd', False) + cleanup.callback(case.doCleanups) + fingerprint = gen_empty_fingerprint() + if lka: + fingerprint[CanBus(None, fingerprint).CAM][0x50] = 16 + if hybrid: + fingerprint[CanBus(None, fingerprint, lka_steering=lka).ECAN][0xFA] = 32 + cp = CarInterface.get_params(identity, fingerprint, [], False, False, False) + assert bool(cp.flags & HyundaiFlags.HYBRID) == hybrid + assert qualified(cp) and not cp.openpilotLongitudinalControl and cp.pcmCruise + case.family_cp = cp.as_reader() + sd, _, step, card, receive = case.family_callers(False) + sd.aol_settings = read_settings(saved) + assert sd.aol_settings == card.aol_card_intent.settings + released = car_state() + released.buttonEvents = [structs.CarState.ButtonEvent(type='lkas', pressed=False)] + card.aol_card_intent.update(released, now_ns=10_015_000_000) + assert card.aol_card_intent.allowed_latch + engaged = car_state() + engaged.vEgoRaw = engaged.vEgo + engaged.cruiseState.enabled = True + axis, _, command = step(2, observed_state=engaged, panda_controls_allowed=True) + assert sd.enabled and axis.lateralActive and command.latActive + assert not axis.longitudinalActive and not command.longActive + assert receive(10_020_000_000, state=engaged, axis=sd.pm.messages['aolAxisState'], + event=sd.pm.messages['onroadEvents']) + yield SimpleNamespace(sd=sd, step=step, card=card, receive=receive) + + +@pytest.mark.parametrize('identity,lka', MODELS) +@pytest.mark.parametrize('guard', ['native_denied', 'native_long', 'native_invalid', 'native_no_request', + 'panda_fault', 'relay_fault', 'critical_event', 'uncalibrated', + 'invalid_can', 'reverse', 'brake_pause_speed', 'stale_source', + 'future_source', 'both_pedals', 'configured_cancel', 'mapped_main', + 'mapped_lkas', 'nonfinite_raw', 'regen', 'unmarked_cp', 'aol_disabled', + 'invalid_pause', 'negative_pause', 'excess_pause']) +def test_late_brake_continuity_cannot_override_existing_stock_guards(identity, lka, guard): + with configured_callers(identity, lka, guard) as context: + sd, step, card = context.sd, context.step, context.card + state = car_state(brake=True) + state.vEgoRaw = state.vEgo + state.cruiseState.enabled = True + kwargs = {'panda_controls_allowed': False} + if guard == 'native_denied': + kwargs['allowed'] = False + elif guard == 'native_long': + kwargs['native_request_long'] = True + kwargs['long_allowed'] = False + elif guard == 'native_invalid': + kwargs['native_valid'] = False + elif guard == 'native_no_request': + kwargs['native_request_lat'] = False + kwargs['allowed'] = False + elif guard == 'panda_fault': + kwargs['panda_fault'] = True + elif guard == 'relay_fault': + kwargs['relay_fault'] = True + elif guard == 'critical_event': + kwargs['critical_event'] = log.OnroadEvent.EventName.controlsMismatch + elif guard == 'uncalibrated': + kwargs['calibrated'] = False + elif guard == 'brake_pause_speed': + assert sd.aol_settings.pause_brake_mps == 20.0 + state.vEgoRaw = state.vEgo = 19.0 + assert not state.standstill and state.vEgo < sd.aol_settings.pause_brake_mps + elif guard == 'invalid_can': + state.canValid = False + elif guard == 'reverse': + state.gearShifter = 'reverse' + elif guard == 'stale_source': + kwargs['state_stamp'] = 10_020_000_000 + elif guard == 'future_source': + kwargs['state_stamp'] = 10_080_000_000 + elif guard == 'both_pedals': + kwargs['gas'] = True + elif guard in ('configured_cancel', 'mapped_main', 'mapped_lkas'): + button = {'configured_cancel': 'cancel', 'mapped_main': 'mainCruise', 'mapped_lkas': 'lkas'}[guard] + state.buttonEvents = [structs.CarState.ButtonEvent(type=button, pressed=True)] + card.aol_card_intent.update(state, now_ns=10_070_000_000) + assert not card.aol_card_intent.allowed_latch + elif guard == 'nonfinite_raw': + state.vEgoRaw = float('nan') + elif guard == 'regen': + state.regenBraking = True + elif guard == 'aol_disabled': + sd.params.put_bool('AlwaysOnLateral', False, block=True) + sd.aol_settings = read_settings(sd.params) + card.aol_card_intent.settings = sd.aol_settings + card.aol_card_intent.update(state, now_ns=10_070_000_000) + assert not sd.aol_settings.enabled and not card.aol_card_intent.allowed_latch + elif guard in ('invalid_pause', 'negative_pause', 'excess_pause'): + raw = {'invalid_pause': b'not-a-number', 'negative_pause': b'-1', 'excess_pause': b'101'}[guard] + Path(sd.params.get_param_path('AolBrakePauseSpeedMps')).write_bytes(raw) + sd.aol_settings = read_settings(sd.params) + card.aol_card_intent.settings = sd.aol_settings + card.aol_card_intent.update(state, now_ns=10_070_000_000) + assert not sd.aol_settings.enabled and not card.aol_card_intent.allowed_latch + elif guard == 'unmarked_cp': + cp = sd.CP.as_builder() + cp.safetyConfigs[0].safetyParam &= ~0x0800 + sd.CP = cp.as_reader() + tick = 7 + if guard == 'uncalibrated': + period = round(1e9 / min(SERVICE_LIST['extrinsicsCalibration'].frequency, 100)) + next_due = int(sd.sm.logMonoTime['extrinsicsCalibration']) + period + tick = max(tick, (next_due - 10_000_000_000 + 9_999_999) // 10_000_000) + axis, _, command = step(tick, observed_state=state, **kwargs) + if guard == 'uncalibrated': + assert int(sd.sm.logMonoTime['extrinsicsCalibration']) == 10_000_000_000 + tick * 10_000_000 + assert sd.sm['extrinsicsCalibration'].calStatus == log.ExtrinsicsCalibration.Status.uncalibrated + assert not axis.lateralActive and not command.latActive + assert not axis.longitudinalActive and not command.longActive + + +@pytest.mark.parametrize('identity,lka', MODELS) +@pytest.mark.parametrize('boundary', ['unmarked', 'longitudinal', 'unknown', 'passive', 'second_panda', 'no_brake_role']) +def test_stock_brake_exception_does_not_expand_configuration_admission(identity, lka, boundary): + from openpilot.starpilot.aol.transport_pause import stock_lateral_off + + fingerprint = gen_empty_fingerprint() + if lka: + fingerprint[CanBus(None, fingerprint).CAM][0x50] = 16 + cp = CarInterface.get_params(identity, fingerprint, [], False, False, False) + cp.safetyConfigs[0].safetyParam |= 0x0800 + assert qualified(cp, marked_only=True) + state = car_state(brake=True) + state.vEgoRaw = state.vEgo + state.cruiseState.enabled = True + state.cruiseState.available = True + native = SimpleNamespace(requestedLateral=True, lateralAllowed=True, requestedLongitudinal=False) + assert stock_lateral_off(cp, state, native, allow_independent_brake=True) + if boundary == 'unmarked': + cp.safetyConfigs[0].safetyParam &= ~0x0800 + elif boundary == 'longitudinal': + cp.openpilotLongitudinalControl = True + cp.pcmCruise = False + elif boundary == 'unknown': + cp.carFingerprint = 'UNKNOWN_HYUNDAI' + elif boundary == 'passive': + cp.passive = True + elif boundary == 'second_panda': + word = cp.safetyConfigs[0].safetyParam + configs = cp.init('safetyConfigs', 2) + configs[0].safetyModel = 'noOutput' + configs[1].safetyModel = 'hyundaiCanfd' + configs[1].safetyParam = word + from opendbc.car.hyundai.canfd_owner import config_index + + assert config_index(cp) == (1 if lka else None) + assert qualified(cp, marked_only=True) == lka + assert not stock_lateral_off(cp, state, native, allow_independent_brake=boundary != 'no_brake_role') + + +@pytest.mark.parametrize('tick', [3, 7]) +@pytest.mark.parametrize('gesture', ['cancel', 'brake']) +def test_source_selected_hybrid_stock_brake_continuity(tick, gesture): + with configured_callers(CAR.HYUNDAI_TUCSON_4TH_GEN, False, 'baseline', hybrid=True) as context: + sd, step, card, receive = context.sd, context.step, context.card, context.receive + state = car_state(brake=gesture == 'brake') + state.vEgoRaw = state.vEgo + state.cruiseState.enabled = True + if gesture == 'cancel': + state.buttonEvents = [structs.CarState.ButtonEvent(type='cancel', pressed=True)] + axis, _, command = step(tick, observed_state=state, panda_controls_allowed=gesture == 'cancel') + assert axis.lateralActive and command.latActive + assert not axis.longitudinalActive and not command.longActive + assert receive(10_000_000_000 + tick * 10_000_000, state=state, + axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents']) + settled = car_state(brake=gesture == 'brake') + settled.vEgoRaw = settled.vEgo + axis, _, command = step(tick + 1, observed_state=settled) + assert not sd.enabled and axis.lateralActive and command.latActive + assert not axis.longitudinalActive and not command.longActive + assert receive(10_000_000_000 + (tick + 1) * 10_000_000, state=settled, + axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents']) + assert card.aol_card_intent.allowed_latch + + +@pytest.mark.parametrize('identity,lka', MODELS) +@pytest.mark.parametrize('tick', [3, 7]) +@pytest.mark.parametrize('gesture', ['cancel', 'brake']) +def test_default_button_mapping_keeps_its_existing_cancel_withdrawal(identity, lka, tick, gesture): + with configured_callers(identity, lka, 'baseline', lkas_action=0) as context: + sd, step, card, receive = context.sd, context.step, context.card, context.receive + state = car_state(brake=gesture == 'brake') + state.vEgoRaw = state.vEgo + state.cruiseState.enabled = True + if gesture == 'cancel': + state.buttonEvents = [structs.CarState.ButtonEvent(type='cancel', pressed=True)] + card.aol_card_intent.update(state, now_ns=10_000_000_000 + tick * 10_000_000) + assert card.aol_card_intent.allowed_latch == (gesture == 'brake') + axis, _, command = step(tick, observed_state=state, panda_controls_allowed=gesture == 'cancel') + assert axis.lateralActive and command.latActive + assert not axis.longitudinalActive and not command.longActive + assert receive(10_000_000_000 + tick * 10_000_000, state=state, + axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents']) == (gesture == 'brake') + settled = car_state(brake=gesture == 'brake') + settled.vEgoRaw = settled.vEgo + axis, _, command = step(tick + 1, observed_state=settled) + assert not sd.enabled + assert bool(axis.lateralActive) == bool(command.latActive) == (gesture == 'brake') + assert not axis.longitudinalActive and not command.longActive + assert receive(10_000_000_000 + (tick + 1) * 10_000_000, state=settled, + axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents']) == (gesture == 'brake') + + +@pytest.mark.parametrize('identity,lka', MODELS) +@pytest.mark.parametrize('pause_mps', [0.0, 20.0]) +def test_existing_independent_brake_policy_keeps_standstill_exception(identity, lka, pause_mps): + guard = 'brake_pause_speed' if pause_mps else 'baseline' + with configured_callers(identity, lka, guard) as context: + sd, step, card, receive = context.sd, context.step, context.card, context.receive + assert sd.aol_settings.pause_brake_mps == pause_mps + state = car_state(brake=True) + state.vEgoRaw = state.vEgo = 0.0 + state.standstill = True + state.cruiseState.enabled = True + axis, _, command = step(7, observed_state=state, panda_controls_allowed=False) + assert not sd.enabled and not sd.CP.steerAtStandstill + assert axis.lateralActive and not command.latActive + assert not axis.longitudinalActive and not command.longActive + assert receive(10_070_000_000, state=state, axis=sd.pm.messages['aolAxisState'], + event=sd.pm.messages['onroadEvents']) + assert card.aol_card_intent.allowed_latch + + +@pytest.mark.parametrize('identity,lka', MODELS) +def test_existing_brake_pause_threshold_equality_remains_allowed(identity, lka): + with configured_callers(identity, lka, 'brake_pause_speed') as context: + sd, step = context.sd, context.step + state = car_state(brake=True) + state.vEgoRaw = state.vEgo = sd.aol_settings.pause_brake_mps + state.cruiseState.enabled = True + assert state.vEgo == 20.0 and not state.standstill + axis, _, command = step(7, observed_state=state, panda_controls_allowed=False) + assert not sd.enabled and axis.lateralActive and command.latActive + assert not axis.longitudinalActive and not command.longActive diff --git a/openpilot/starpilot/aol/tests/test_torque_stock_cruise_transition.py b/openpilot/starpilot/aol/tests/test_torque_stock_cruise_transition.py new file mode 100644 index 0000000000..445c9f2929 --- /dev/null +++ b/openpilot/starpilot/aol/tests/test_torque_stock_cruise_transition.py @@ -0,0 +1,72 @@ +from contextlib import ExitStack + +import pytest + +from opendbc.car import gen_empty_fingerprint, structs +from opendbc.car.hyundai.canfd_stock_aol import qualified +from opendbc.car.hyundai.hyundaicanfd import CanBus +from opendbc.car.hyundai.interface import CarInterface +from opendbc.car.hyundai.values import CAR +from openpilot.common.params import Params +from openpilot.common.prefix import OpenpilotPrefix +from openpilot.starpilot.aol.intent import read_settings +from openpilot.starpilot.aol.tests.stock_lateral_test_helpers import car_state, hkg_family_case + + +@pytest.mark.parametrize('identity,lka', [(CAR.GENESIS_GV70_1ST_GEN, False), + (CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, True), + (CAR.KIA_EV6, True)]) +@pytest.mark.parametrize('tick', [3, 7]) +@pytest.mark.parametrize('gesture', ['cancel', 'brake']) +def test_configured_torque_stock_lateral_survives_stock_cruise_transition(identity, lka, tick, gesture): + with ExitStack() as cleanup: + with OpenpilotPrefix(): + saved = Params() + saved.put('LKASButtonControl', 9, block=True) + saved.put('MainCruiseButtonControl', 0, block=True) + for key in ('CancelButtonControl', 'LongCancelButtonControl', 'VeryLongCancelButtonControl'): + saved.put(key, 0, block=True) + case = hkg_family_case('hkg_canfd', False) + cleanup.callback(case.doCleanups) + fingerprint = gen_empty_fingerprint() + if lka: + fingerprint[CanBus(None, fingerprint).CAM][0x50] = 16 + cp = CarInterface.get_params(identity, fingerprint, [], False, False, False) + assert cp.steerControlType == structs.CarParams.SteerControlType.torque + assert qualified(cp) and not cp.openpilotLongitudinalControl and cp.pcmCruise + case.family_cp = cp.as_reader() + sd, _, step, card, receive = case.family_callers(False) + sd.aol_settings = read_settings(saved) + assert sd.aol_settings == card.aol_card_intent.settings + assert sd.aol_settings.pause_brake_mps == 0 + assert card.aol_card_intent.independent_cruise_buttons + engaged = car_state() + engaged.vEgoRaw = engaged.vEgo + engaged.cruiseState.enabled = True + axis, _, command = step(2, observed_state=engaged, panda_controls_allowed=True) + assert sd.enabled and axis.lateralActive and command.latActive + assert not axis.longitudinalActive and not command.longActive + assert receive(10_020_000_000, state=engaged, axis=sd.pm.messages['aolAxisState'], + event=sd.pm.messages['onroadEvents']) + state = car_state() + state.vEgoRaw = state.vEgo + state.cruiseState.enabled = True + if gesture == 'cancel': + state.buttonEvents = [structs.CarState.ButtonEvent(type='cancel', pressed=True)] + else: + state.brakePressed = True + axis, _, command = step(tick, observed_state=state, panda_controls_allowed=gesture == 'cancel') + assert sd.enabled == (gesture == 'cancel') + assert not axis.longitudinalActive and not command.longActive + assert axis.lateralActive and command.latActive + assert receive(10_000_000_000 + tick * 10_000_000, state=state, axis=sd.pm.messages['aolAxisState'], + event=sd.pm.messages['onroadEvents']) + assert card.aol_card_intent.allowed_latch + settled = car_state(brake=gesture == 'brake') + settled.vEgoRaw = settled.vEgo + axis, _, command = step(tick + 1, observed_state=settled) + assert not sd.enabled and not axis.longitudinalActive and not command.longActive + assert axis.lateralActive and command.latActive + assert receive(10_000_000_000 + (tick + 1) * 10_000_000, state=settled, + axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents']) + assert card.aol_card_intent.allowed_latch diff --git a/openpilot/starpilot/aol/transport_pause.py b/openpilot/starpilot/aol/transport_pause.py index ad9f0477ea..4ecaa2a017 100644 --- a/openpilot/starpilot/aol/transport_pause.py +++ b/openpilot/starpilot/aol/transport_pause.py @@ -109,12 +109,16 @@ def stock_lateral_off(cp, cs, native, *, allow_independent_cancel=False, allow_i from openpilot.starpilot.nostalgia import physical_cancel from opendbc.car.hyundai.canfd_angle_aol import qualified as angle_qualified + from opendbc.car.hyundai.canfd_stock_aol import qualified as stock_canfd_qualified + independent_angle = angle_qualified(cp, marked_only=True) + independent_brake = independent_angle or (allow_independent_brake and cp.brand == 'hyundai' and + len(cp.safetyConfigs) == 1 and stock_canfd_qualified(cp, marked_only=True)) return bool(cp.pcmCruise and cs.cruiseState.available and - (not cs.cruiseState.enabled or independent_angle and allow_independent_brake and cs.brakePressed) and + (not cs.cruiseState.enabled or independent_brake and allow_independent_brake and cs.brakePressed) and cs.canValid and not cs.canTimeout and not cs.steerFaultPermanent and not cs.steerFaultTemporary and not cs.accFaulted and not cs.doorOpen and not cs.seatbeltUnlatched and - (not cs.brakePressed or independent_angle and allow_independent_brake) and + (not cs.brakePressed or independent_brake and allow_independent_brake) and not cs.regenBraking and (not cs.gasPressed or independent_angle) and (not physical_cancel(cs, cp) or independent_angle and allow_independent_cancel) and native is not None and native.requestedLateral and native.lateralAllowed and not native.requestedLongitudinal) diff --git a/prebuilt.json b/prebuilt.json index 8ae822b05e..d27aa21587 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "5ac0862226ed39da17f48eedaf31f71216c3ba44557194020d15bb031bf2e284", "schema_version": 1} +{"protected_inventory_sha256": "28c9d821cce63586b434f23ee52d427e27a4357a8b42dd53cecb22288f420032", "schema_version": 1} diff --git a/tools/ci/run_vehicle_tests.py b/tools/ci/run_vehicle_tests.py index 3413c10946..efecb0ee06 100755 --- a/tools/ci/run_vehicle_tests.py +++ b/tools/ci/run_vehicle_tests.py @@ -33,6 +33,9 @@ DEBUG_SAFETY_CLASS_TARGETS = { RELEASE_TARGETS = ( + "openpilot/starpilot/aol/tests/test_torque_stock_cruise_transition.py", + "openpilot/starpilot/aol/tests/test_torque_stock_brake_guards.py", + "opendbc_repo/opendbc/safety/tests/test_hyundai_torque_stock_transition.py", "openpilot/starpilot/aol/tests/test_angle_stock_cruise_transition.py", "opendbc_repo/opendbc/safety/tests/test_hyundai_stock_cruise_transition.py", "opendbc_repo/opendbc/car/hyundai/tests/test_ioniq5pe_replacement_duplicates.py",