mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-03 19:13:45 +08:00
Hyundai AOL Profiles
This commit is contained in:
@@ -1,32 +1,92 @@
|
||||
"""Stock-cruise AOL for the shared EV CAN FD torque/button layout."""
|
||||
"""AOL admission for the ordinary CAN FD torque and physical-button layouts."""
|
||||
|
||||
from opendbc.car.structs import CarParams
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
|
||||
STOCK_AOL_MARKER = 0x0800
|
||||
STOCK_AOL_WORDS = frozenset((0x0811, 0x0891))
|
||||
# Ioniq 6 retains its existing stock/longitudinal owner and settings.
|
||||
STOCK_EV_CARS = frozenset((CAR.HYUNDAI_KONA_EV_2ND_GEN, CAR.HYUNDAI_IONIQ_5,
|
||||
CAR.HYUNDAI_IONIQ_5_N, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KIA_EV6,
|
||||
CAR.GENESIS_GV60_EV_1ST_GEN, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN))
|
||||
STOCK_EV_CARS = frozenset(
|
||||
(
|
||||
CAR.HYUNDAI_KONA_EV_2ND_GEN,
|
||||
CAR.HYUNDAI_IONIQ_5,
|
||||
CAR.HYUNDAI_IONIQ_5_N,
|
||||
CAR.KIA_NIRO_EV_2ND_GEN,
|
||||
CAR.KIA_EV6,
|
||||
CAR.GENESIS_GV60_EV_1ST_GEN,
|
||||
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
|
||||
)
|
||||
)
|
||||
CARNIVAL_CARS = frozenset((CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN))
|
||||
STOCK_AOL_WORDS = frozenset(
|
||||
0x0800 | gas | topology | extra
|
||||
for gas in (0, 1, 2)
|
||||
for topology in (0, 8, 16, 144, 32, 40, 48, 176)
|
||||
for extra in (0, 0x0400, 0x2000, 0x2400)
|
||||
if (topology not in (48, 176) or extra & 0x2000)
|
||||
and (not extra & 0x0400 or topology in (8, 40))
|
||||
and (not extra & 0x2000 or (topology in (32, 40, 48, 176) and gas != 1))
|
||||
)
|
||||
|
||||
|
||||
def _base_word(cp):
|
||||
allowed = (
|
||||
HyundaiFlags.CANFD
|
||||
| HyundaiFlags.EV
|
||||
| HyundaiFlags.HYBRID
|
||||
| HyundaiFlags.CANFD_LKA_STEER_MSG
|
||||
| HyundaiFlags.CANFD_LKA_STEER_MSG_ALT
|
||||
| HyundaiFlags.CANFD_ALT_BUTTONS
|
||||
| HyundaiFlags.CANFD_CAMERA_SCC
|
||||
| HyundaiFlags.CANFD_ALT_GEARS
|
||||
| HyundaiFlags.CANFD_ALT_GEARS_2
|
||||
| HyundaiFlags.CANFD_RADAR_SCC
|
||||
| HyundaiFlags.CANFD_NO_RADAR_DISABLE
|
||||
| HyundaiFlags.CCNC
|
||||
)
|
||||
try:
|
||||
factory = CAR(cp.carFingerprint)
|
||||
except ValueError:
|
||||
return None
|
||||
if (
|
||||
cp.brand != 'hyundai'
|
||||
or factory == CAR.HYUNDAI_IONIQ_6
|
||||
or not factory.config.flags & HyundaiFlags.CANFD
|
||||
or factory.config.flags & (HyundaiFlags.CANFD_ANGLE_STEERING | HyundaiFlags.NON_SCC)
|
||||
or cp.passive
|
||||
or cp.dashcamOnly
|
||||
or cp.notCar
|
||||
or cp.alternativeExperience != 0
|
||||
or cp.steerControlType != CarParams.SteerControlType.torque
|
||||
or not cp.flags & HyundaiFlags.CANFD
|
||||
or int(cp.flags) & ~int(allowed)
|
||||
or len(cp.safetyConfigs) != 1
|
||||
or cp.safetyConfigs[0].safetyModel != CarParams.SafetyModel.hyundaiCanfd
|
||||
):
|
||||
return None
|
||||
# EV identity is static; hybrid fuel is selected by the actual 0xFA fingerprint.
|
||||
if bool(cp.flags & HyundaiFlags.EV) != bool(factory.config.flags & HyundaiFlags.EV):
|
||||
return None
|
||||
ev, hybrid = bool(cp.flags & HyundaiFlags.EV), bool(cp.flags & HyundaiFlags.HYBRID)
|
||||
lka = bool(cp.flags & HyundaiFlags.CANFD_LKA_STEER_MSG)
|
||||
alt_lka = bool(cp.flags & HyundaiFlags.CANFD_LKA_STEER_MSG_ALT)
|
||||
alt_buttons = bool(cp.flags & HyundaiFlags.CANFD_ALT_BUTTONS)
|
||||
camera = bool(cp.flags & HyundaiFlags.CANFD_CAMERA_SCC)
|
||||
if (ev and hybrid) or (alt_lka and not lka) or (lka and camera):
|
||||
return None
|
||||
bus = CanBus(cp)
|
||||
if (bus.ACAN, bus.ECAN, bus.CAM) != ((0, 1, 2) if lka else (1, 0, 2)):
|
||||
return None
|
||||
word = (1 if ev else 2 if hybrid else 0) | (16 if lka else 0) | (128 if alt_lka else 0)
|
||||
word |= (32 if alt_buttons else 0) | (8 if camera else 0)
|
||||
if cp.flags & HyundaiFlags.CCNC and camera:
|
||||
word |= 0x0400
|
||||
if factory in CARNIVAL_CARS and alt_buttons:
|
||||
word |= 0x2000
|
||||
return word if word | STOCK_AOL_MARKER in STOCK_AOL_WORDS else None
|
||||
|
||||
|
||||
def qualified(cp, *, marked_only=False):
|
||||
required = HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEER_MSG
|
||||
allowed = (required | HyundaiFlags.CANFD_LKA_STEER_MSG_ALT | HyundaiFlags.CANFD_ALT_GEARS |
|
||||
HyundaiFlags.CANFD_ALT_GEARS_2 | HyundaiFlags.CANFD_NO_RADAR_DISABLE | HyundaiFlags.CCNC)
|
||||
if (cp.brand != 'hyundai' or cp.carFingerprint not in STOCK_EV_CARS or
|
||||
cp.passive or cp.dashcamOnly or cp.notCar or cp.alternativeExperience != 0 or
|
||||
cp.openpilotLongitudinalControl or not cp.pcmCruise or
|
||||
cp.steerControlType != CarParams.SteerControlType.torque or
|
||||
int(cp.flags) & int(required) != int(required) or int(cp.flags) & ~int(allowed) or
|
||||
len(cp.safetyConfigs) != 1):
|
||||
word = _base_word(cp)
|
||||
if word is None or cp.openpilotLongitudinalControl or not cp.pcmCruise:
|
||||
return False
|
||||
bus = CanBus(cp)
|
||||
if (bus.ACAN, bus.ECAN, bus.CAM) != (0, 1, 2):
|
||||
return False
|
||||
safety = cp.safetyConfigs[0]
|
||||
base = 0x91 if cp.flags & HyundaiFlags.CANFD_LKA_STEER_MSG_ALT else 0x11
|
||||
return (safety.safetyModel == CarParams.SafetyModel.hyundaiCanfd and
|
||||
(safety.safetyParam == (base | STOCK_AOL_MARKER) or
|
||||
(not marked_only and safety.safetyParam == base)))
|
||||
return cp.safetyConfigs[0].safetyParam in ((word | STOCK_AOL_MARKER,) if marked_only else (word, word | STOCK_AOL_MARKER))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from opendbc.car.hyundai.classic_long_aol import qualified as qualified_classic_long
|
||||
from opendbc.car.hyundai.classic_scc_aol import qualified as qualified_classic_scc, ClassicSccLkasSources
|
||||
from opendbc.car.hyundai.ev9_camera_lead import EV9CameraLead
|
||||
from opendbc.car.hyundai.ev9_longitudinal import qualified as ev9_long_qualified
|
||||
@@ -54,7 +55,7 @@ class CarState(CarStateBase):
|
||||
def __init__(self, CP):
|
||||
super().__init__(CP)
|
||||
self.forte_lkas_sources = NonSccLkasSources() if qualified_non_scc(CP) and CP.flags & HyundaiFlags.HAS_LDA_BUTTON else None
|
||||
if qualified_classic_scc(CP) and CP.flags & HyundaiFlags.HAS_LDA_BUTTON:
|
||||
if (qualified_classic_scc(CP) or qualified_classic_long(CP)) and CP.flags & HyundaiFlags.HAS_LDA_BUTTON:
|
||||
self.forte_lkas_sources = ClassicSccLkasSources(CP.carFingerprint)
|
||||
self.ev9_long = ev9_long_qualified(CP)
|
||||
self.ev9_camera_lead = EV9CameraLead(CP) if self.ev9_long else None
|
||||
@@ -538,6 +539,10 @@ class CarState(CarStateBase):
|
||||
]
|
||||
elif not CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CARNIVAL_ALT_RESUME:
|
||||
msgs.append(("CRUISE_BUTTONS_ALT", math.nan))
|
||||
from opendbc.car.hyundai.canfd_stock_aol import qualified as stock_aol_qualified
|
||||
if stock_aol_qualified(CP):
|
||||
msgs = [(name, frequency) for name, frequency in msgs if name != "CRUISE_BUTTONS_ALT"]
|
||||
msgs.append(("CRUISE_BUTTONS_ALT", 50 if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else math.nan))
|
||||
if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING:
|
||||
# These are actual state and safety inputs, not DBC defaults. Keep this
|
||||
# subscription expansion scoped to the new angle profile.
|
||||
@@ -575,7 +580,8 @@ class CarState(CarStateBase):
|
||||
from opendbc.car.hyundai.gv70_startup import eligible as gv70_eligible
|
||||
if ev6_eligible(CP) or gv70_eligible(CP):
|
||||
# Standard 1CF supplies buttons; alternate 1AA only holds optional units.
|
||||
msgs.append(("CRUISE_BUTTONS_ALT", math.nan))
|
||||
if not any(name == "CRUISE_BUTTONS_ALT" for name, _ in msgs):
|
||||
msgs.append(("CRUISE_BUTTONS_ALT", math.nan))
|
||||
existing = {name for name, _ in msgs}
|
||||
for name, frequency in (("ACCELERATOR", 100), ("TCS", 50), ("WHEEL_SPEEDS", 100), ("MDPS", 100),
|
||||
(self.gear_msg_canfd, 100)):
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags, HyundaiSafetyFlags, is_blended
|
||||
from opendbc.car.hyundai.classic_scc_aol import CLASSIC_SCC_IDS, stock_word
|
||||
|
||||
AOL_MARKER = 0x0400
|
||||
AOL_EXPERIENCE = 32
|
||||
_EXCLUDED = int(HyundaiFlags.LEGACY | HyundaiFlags.UNSUPPORTED_LONGITUDINAL | HyundaiFlags.ALT_LIMITS_2 | HyundaiFlags.CAMERA_SCC)
|
||||
CLASSIC_LONG_IDS = frozenset(identity for identity in CLASSIC_SCC_IDS if not int(identity.config.flags) & _EXCLUDED and identity != CAR.GENESIS_G90)
|
||||
LONG_AOL_WORDS = frozenset(AOL_MARKER | 4 | gas | limits | lda for gas in (0, 1, 2) for limits in (0, 64) for lda in (0, 2048))
|
||||
|
||||
|
||||
def ordinary_word(cp):
|
||||
return stock_word(cp) | int(HyundaiSafetyFlags.LONG)
|
||||
|
||||
|
||||
def aol_word(cp):
|
||||
return ordinary_word(cp) | AOL_MARKER | (int(HyundaiSafetyFlags.HAS_LDA_BUTTON) if int(cp.flags) & int(HyundaiFlags.HAS_LDA_BUTTON) else 0)
|
||||
|
||||
|
||||
def qualified(cp, *, marked_only=False):
|
||||
if cp.brand != 'hyundai' or cp.carFingerprint not in CLASSIC_LONG_IDS:
|
||||
return False
|
||||
declared = int(CAR[cp.carFingerprint].config.flags)
|
||||
allowed = declared | int(HyundaiFlags.HAS_LDA_BUTTON | HyundaiFlags.USE_FCA | HyundaiFlags.SEND_LFA)
|
||||
identity_mask = int(
|
||||
HyundaiFlags.CANFD
|
||||
| HyundaiFlags.NON_SCC
|
||||
| HyundaiFlags.FCEV
|
||||
| HyundaiFlags.LEGACY
|
||||
| HyundaiFlags.EV
|
||||
| HyundaiFlags.HYBRID
|
||||
| HyundaiFlags.ALT_LIMITS
|
||||
| HyundaiFlags.ALT_LIMITS_2
|
||||
| HyundaiFlags.CAMERA_SCC
|
||||
)
|
||||
if (
|
||||
int(cp.flags) & identity_mask != declared & identity_mask
|
||||
or int(cp.flags) & ~allowed
|
||||
or is_blended(cp)
|
||||
or cp.passive
|
||||
or cp.notCar
|
||||
or cp.dashcamOnly
|
||||
or not cp.openpilotLongitudinalControl
|
||||
or cp.pcmCruise
|
||||
or cp.steerControlType != structs.CarParams.SteerControlType.torque
|
||||
or not cp.alphaLongitudinalAvailable
|
||||
or cp.alternativeExperience not in (0, AOL_EXPERIENCE)
|
||||
or len(cp.safetyConfigs) != 1
|
||||
):
|
||||
return False
|
||||
safety = cp.safetyConfigs[0]
|
||||
accepted = (aol_word(cp),) if marked_only else (ordinary_word(cp), aol_word(cp))
|
||||
return safety.safetyModel == structs.CarParams.SafetyModel.hyundai and int(safety.safetyParam) in accepted
|
||||
|
||||
|
||||
def native_accepts(cp, model, param):
|
||||
return (
|
||||
qualified(cp, marked_only=True)
|
||||
and cp.alternativeExperience == AOL_EXPERIENCE
|
||||
and model == int(structs.CarParams.SafetyModel.hyundai)
|
||||
and param == aol_word(cp)
|
||||
)
|
||||
@@ -0,0 +1,180 @@
|
||||
"""Typed CAN FD topology and startup ownership contracts for shared AOL."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from opendbc.car import gen_empty_fingerprint, structs
|
||||
from opendbc.car.hyundai.canfd_stock_aol import qualified, STOCK_AOL_WORDS
|
||||
from opendbc.car.hyundai.interface import CarInterface
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
from openpilot.starpilot.car.hyundai.aol import policy_for, native_accepts_cp
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity', [CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_KONA_EV_2ND_GEN])
|
||||
@pytest.mark.parametrize('topology', ['hda1_standard', 'hda1_alternate', 'hda2_standard', 'hda2_alternate'])
|
||||
def test_actual_typed_factory_binds_stock_word(identity, topology):
|
||||
fp = gen_empty_fingerprint()
|
||||
hda2 = topology.startswith('hda2')
|
||||
if hda2:
|
||||
fp[2].update({0x110: 32, 0x362: 32} if topology == 'hda2_alternate' else {0x50: 16, 0x2A4: 24})
|
||||
if topology != 'hda1_alternate':
|
||||
fp[1 if hda2 else 0][0x1CF] = 8
|
||||
else:
|
||||
fp[0][0x1AA] = 16
|
||||
cp = CarInterface.get_params(identity, fp, [], False, False, False)
|
||||
assert qualified(cp)
|
||||
policy = policy_for(cp)
|
||||
assert policy.explicit_latch and policy.safety_param_addition == 0x800
|
||||
cp.safetyConfigs[0].safetyParam |= 0x800
|
||||
assert int(cp.safetyConfigs[0].safetyParam) in STOCK_AOL_WORDS
|
||||
assert native_accepts_cp(cp, cp.safetyConfigs[0].safetyModel.raw, int(cp.safetyConfigs[0].safetyParam))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity', [CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_KONA_EV_2ND_GEN])
|
||||
def test_fuel_identity_mismatch_is_not_a_different_native_profile(identity):
|
||||
fp = gen_empty_fingerprint()
|
||||
fp[0][0x1CF] = 8
|
||||
cp = CarInterface.get_params(identity, fp, [], False, False, False)
|
||||
assert qualified(cp)
|
||||
cp.flags = int(cp.flags) & ~int(HyundaiFlags.EV)
|
||||
cp.safetyConfigs[0].safetyParam = int(cp.safetyConfigs[0].safetyParam) & ~1
|
||||
assert not qualified(cp)
|
||||
|
||||
|
||||
def test_ioniq6_keeps_its_separate_namespace_and_policy():
|
||||
fp = gen_empty_fingerprint()
|
||||
fp[2][0x50] = 16
|
||||
fp[1][0x1CF] = 8
|
||||
fp[0][0x3A5] = 24
|
||||
cp = CarInterface.get_params(CAR.HYUNDAI_IONIQ_6, fp, [], False, False, False)
|
||||
assert not qualified(cp)
|
||||
assert policy_for(cp).safety_param_addition == 0x800
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity,fuel', [(CAR.HYUNDAI_SONATA_2024, 0), (CAR.KIA_SPORTAGE_5TH_GEN, 2), (CAR.HYUNDAI_IONIQ_5, 1)])
|
||||
@pytest.mark.parametrize('alternate', [False, True])
|
||||
def test_stock_hda1_factory_fuel_and_button_source(identity, fuel, alternate):
|
||||
fp = gen_empty_fingerprint()
|
||||
fp[0][0x1AA if alternate else 0x1CF] = 16 if alternate else 8
|
||||
if fuel == 2:
|
||||
fp[0][0xFA] = 8
|
||||
cp = CarInterface.get_params(identity, fp, [], False, False, False)
|
||||
assert qualified(cp)
|
||||
raw = int(cp.safetyConfigs[0].safetyParam)
|
||||
assert raw & 3 == fuel
|
||||
assert bool(cp.flags & HyundaiFlags.CANFD_ALT_BUTTONS) == alternate
|
||||
cp.safetyConfigs[0].safetyParam = raw | 0x800
|
||||
assert qualified(cp, marked_only=True)
|
||||
assert native_accepts_cp(cp, cp.safetyConfigs[0].safetyModel.raw, raw | 0x800)
|
||||
|
||||
|
||||
def test_static_angle_identity_cannot_alias_torque_admission():
|
||||
fp = gen_empty_fingerprint()
|
||||
fp[2].update({0x110: 32, 0x362: 32})
|
||||
fp[1][0x1CF] = 8
|
||||
cp = CarInterface.get_params(CAR.HYUNDAI_IONIQ_5_PE, fp, [], False, False, False)
|
||||
cp.steerControlType = structs.CarParams.SteerControlType.torque
|
||||
cp.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEER_MSG | HyundaiFlags.CANFD_LKA_STEER_MSG_ALT)
|
||||
cp.safetyConfigs[0].safetyParam = 0x891
|
||||
assert not qualified(cp)
|
||||
assert not policy_for(cp).explicit_latch
|
||||
|
||||
|
||||
FACTORY_CASES = tuple(
|
||||
(identity, topology, 1)
|
||||
for identity in (CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_KONA_EV_2ND_GEN)
|
||||
for topology in ('hda1_standard', 'hda1_alternate', 'hda2_standard', 'hda2_alternate')
|
||||
) + tuple(
|
||||
(identity, 'hda1_alternate' if alternate else 'hda1_standard', fuel)
|
||||
for identity, fuel in ((CAR.HYUNDAI_SONATA_2024, 0), (CAR.KIA_SPORTAGE_5TH_GEN, 2), (CAR.HYUNDAI_IONIQ_5, 1))
|
||||
for alternate in (False, True)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity,topology,fuel', FACTORY_CASES)
|
||||
@pytest.mark.parametrize('enabled', [False, True])
|
||||
def test_actual_stock_card_publication_and_controls(identity, topology, fuel, enabled):
|
||||
import gc
|
||||
import os
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.car.card import Car
|
||||
from openpilot.selfdrive.controls.controlsd import Controls
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
|
||||
from opendbc.car.hyundai.radar_interface import RadarInterface
|
||||
|
||||
fp = gen_empty_fingerprint()
|
||||
hda2 = topology.startswith('hda2')
|
||||
if hda2:
|
||||
fp[2].update({0x110: 32, 0x362: 32} if topology == 'hda2_alternate' else {0x50: 16, 0x2A4: 24})
|
||||
fp[1 if hda2 else 0][0x1AA if topology == 'hda1_alternate' else 0x1CF] = 16 if topology == 'hda1_alternate' else 8
|
||||
if fuel == 2:
|
||||
fp[0][0xFA] = 8
|
||||
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1'}):
|
||||
saved = Params()
|
||||
saved.put_bool('OpenpilotEnabledToggle', True, block=True)
|
||||
saved.put_bool('AlwaysOnLateral', enabled, block=True)
|
||||
saved.put('MainCruiseButtonControl', 9, block=True)
|
||||
saved.put('LKASButtonControl', 9, block=True)
|
||||
cp = CarInterface.get_params(identity, fp, [], False, False, False)
|
||||
assert qualified(cp)
|
||||
original_word = int(cp.safetyConfigs[0].safetyParam)
|
||||
original_flags = cp.flags
|
||||
ci = CarInterface(cp)
|
||||
card = Car(CI=ci, RI=RadarInterface(cp))
|
||||
with structs.CarParams.from_bytes(saved.get('CarParams')) as published:
|
||||
assert published.pcmCruise and not published.openpilotLongitudinalControl and not published.passive
|
||||
assert published.safetyConfigs[0].safetyParam == original_word | (0x800 if enabled else 0)
|
||||
assert published.alternativeExperience == 0
|
||||
assert published.flags == original_flags
|
||||
assert qualified(published, marked_only=enabled)
|
||||
assert published.carFingerprint == identity
|
||||
controls = Controls()
|
||||
assert controls.CP.to_dict() == card.CP.to_dict()
|
||||
assert isinstance(controls.LaC, LatControlTorque)
|
||||
assert card.aol_qualified == enabled
|
||||
del controls, card, ci
|
||||
gc.collect()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity', [CAR.KIA_EV6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN])
|
||||
def test_existing_stock_startup_profiles_update_without_duplicate_units_source(identity):
|
||||
from opendbc.can import CANPacker
|
||||
|
||||
fp = gen_empty_fingerprint()
|
||||
fp[2].update({0x50: 16, 0x2A4: 24})
|
||||
fp[1][0x1CF] = 8
|
||||
cp = CarInterface.get_params(identity, fp, [], False, False, False)
|
||||
assert qualified(cp)
|
||||
cp.safetyConfigs[0].safetyParam |= 0x800
|
||||
ci = CarInterface(cp)
|
||||
packer = CANPacker('hyundai_canfd_generated')
|
||||
drive = next(code for code, label in ci.CS.shifter_values.items() if label == 'D')
|
||||
inventory = [
|
||||
('ACCELERATOR', 1),
|
||||
('TCS', 1),
|
||||
('WHEEL_SPEEDS', 1),
|
||||
('MDPS', 1),
|
||||
('STEERING_SENSORS', 1),
|
||||
('DOORS_SEATBELTS', 1),
|
||||
('BLINKERS', 1),
|
||||
('GEAR_ALT_2', 1),
|
||||
('CRUISE_BUTTONS', 1),
|
||||
('SCC_CONTROL', 1),
|
||||
('CAM_0x2a4', 2),
|
||||
('MANUAL_SPEED_LIMIT_ASSIST', 1),
|
||||
]
|
||||
assert all(name != 'CRUISE_BUTTONS_ALT' for name, _ in inventory)
|
||||
for tick in range(40):
|
||||
frames = []
|
||||
for name, bus in inventory:
|
||||
values = {'COUNTER': tick % (16 if name == 'CRUISE_BUTTONS' else 256)}
|
||||
if name == 'GEAR_ALT_2':
|
||||
values['GEAR'] = drive
|
||||
elif name == 'DOORS_SEATBELTS':
|
||||
values['DRIVER_SEATBELT'] = 1
|
||||
elif name == 'WHEEL_SPEEDS':
|
||||
values.update(WHL_SpdFLVal=72, WHL_SpdFRVal=72, WHL_SpdRLVal=72, WHL_SpdRRVal=72)
|
||||
frames.append(packer.make_can_msg(name, bus, values))
|
||||
state = ci.update([(1_000_000_000 + tick * 10_000_000, frames)])
|
||||
assert state.canValid and not state.canTimeout
|
||||
@@ -584,6 +584,10 @@ static safety_config hyundai_init(uint16_t param) {
|
||||
|
||||
hyundai_common_init(param);
|
||||
classic_non_scc_aol_configure(param, false);
|
||||
if (classic_long_aol_param(param) && !classic_long_aol_enabled) {
|
||||
const safety_config denied = {0};
|
||||
return denied;
|
||||
}
|
||||
hyundai_blended_alpha = false;
|
||||
#ifdef ALLOW_DEBUG
|
||||
hyundai_blended_alpha = (param == 0x2004U) || (param == 0x2014U);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "opendbc/safety/declarations.h"
|
||||
#include "opendbc/safety/modes/hyundai_common.h"
|
||||
#include "opendbc/safety/modes/hyundai_canfd_aol.h"
|
||||
|
||||
#define HYUNDAI_CANFD_CRUISE_BUTTON_TX_MSGS(bus) \
|
||||
{0x1CF, bus, 8, .check_relay = false}, /* CRUISE_BUTTON */ \
|
||||
@@ -420,8 +421,8 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *msg) {
|
||||
if (hyundai_canfd_ev9_long && !previous_controls_allowed && controls_allowed) {
|
||||
hyundai_ev9_inactive_accel_count = 0U;
|
||||
}
|
||||
if (aol_ioniq6_long && (msg_matches(msg, 0x1cfU, pt_bus))) {
|
||||
const bool gesture = main_button || GET_BIT(msg, 23U); // LDA/LKAS button
|
||||
if (aol_ioniq6_long && (msg_matches(msg, hyundai_canfd_alt_buttons ? 0x1aaU : 0x1cfU, pt_bus))) {
|
||||
const bool gesture = main_button || (hyundai_canfd_alt_buttons ? GET_BIT(msg, 39U) : GET_BIT(msg, 23U)); // LDA/LKAS button
|
||||
if (cruise_button == HYUNDAI_BTN_CANCEL) {
|
||||
aol_ioniq6_lateral_latch = false;
|
||||
aol_ioniq6_lateral_token_claimed = false;
|
||||
@@ -949,7 +950,7 @@ static safety_config hyundai_canfd_init_validated(uint16_t param) {
|
||||
const uint16_t HYUNDAI_PARAM_ANGLE_MODEL_MASK = 2048U | 4096U | HYUNDAI_PARAM_ANGLE_MODEL_BANK;
|
||||
const uint16_t HYUNDAI_PARAM_ANGLE_TOPOLOGY_MASK = 8U | 16U | 32U | 128U;
|
||||
const bool angle_requested = GET_FLAG(param, HYUNDAI_PARAM_CANFD_ANGLE);
|
||||
const bool ioniq6_stock_aol_requested = (param == 0x0811U) || (param == 0x0891U);
|
||||
const bool ioniq6_stock_aol_requested = hyundai_canfd_stock_torque_aol_param(param);
|
||||
const bool pe_requested = (param == HYUNDAI_IONIQ5_PE_STOCK_PARAM) && ((unsigned int)alternative_experience == 0U);
|
||||
const bool ev9_requested = (param == HYUNDAI_EV9_STOCK_PARAM) && ((unsigned int)alternative_experience == 0U);
|
||||
const bool ordinary_angle_requested = pe_requested || ev9_requested;
|
||||
@@ -1462,6 +1463,108 @@ static safety_config hyundai_canfd_init_validated(uint16_t param) {
|
||||
}
|
||||
}
|
||||
|
||||
if (ioniq6_stock_aol_requested && (param != 0x0811U) && (param != 0x0891U)) {
|
||||
static RxCheck torque_hda2_ev_std[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_STD_RX_CHECKS(1, 0x35)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(1)
|
||||
};
|
||||
static RxCheck torque_hda2_ev_alt[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_ALT_RX_CHECKS(1, 0x35)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(1)
|
||||
};
|
||||
static RxCheck torque_hda2_ice_std[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_STD_RX_CHECKS(1, 0x100)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(1)
|
||||
};
|
||||
static RxCheck torque_hda2_ice_alt[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_ALT_RX_CHECKS(1, 0x100)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(1)
|
||||
};
|
||||
static RxCheck torque_hda2_hybrid_std[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_STD_RX_CHECKS(1, 0x105)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(1)
|
||||
};
|
||||
static RxCheck torque_hda2_hybrid_alt[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_ALT_RX_CHECKS(1, 0x105)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(1)
|
||||
};
|
||||
static RxCheck torque_radar_ev_std[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_STD_RX_CHECKS(0, 0x35)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(0)
|
||||
};
|
||||
static RxCheck torque_radar_ev_alt[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_ALT_RX_CHECKS(0, 0x35)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(0)
|
||||
};
|
||||
static RxCheck torque_radar_ice_std[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_STD_RX_CHECKS(0, 0x100)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(0)
|
||||
};
|
||||
static RxCheck torque_radar_ice_alt[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_ALT_RX_CHECKS(0, 0x100)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(0)
|
||||
};
|
||||
static RxCheck torque_radar_hybrid_std[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_STD_RX_CHECKS(0, 0x105)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(0)
|
||||
};
|
||||
static RxCheck torque_radar_hybrid_alt[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_ALT_RX_CHECKS(0, 0x105)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(0)
|
||||
};
|
||||
static RxCheck torque_camera_ev_std[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_STD_RX_CHECKS(0, 0x35)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(2)
|
||||
};
|
||||
static RxCheck torque_camera_ev_alt[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_ALT_RX_CHECKS(0, 0x35)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(2)
|
||||
};
|
||||
static RxCheck torque_camera_ice_std[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_STD_RX_CHECKS(0, 0x100)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(2)
|
||||
};
|
||||
static RxCheck torque_camera_ice_alt[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_ALT_RX_CHECKS(0, 0x100)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(2)
|
||||
};
|
||||
static RxCheck torque_camera_hybrid_std[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_STD_RX_CHECKS(0, 0x105)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(2)
|
||||
};
|
||||
static RxCheck torque_camera_hybrid_alt[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_ALT_RX_CHECKS(0, 0x105)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(2)
|
||||
};
|
||||
RxCheck *selected = NULL;
|
||||
if (hyundai_canfd_lka_steer_msg) {
|
||||
if (hyundai_ev_gas_signal) {
|
||||
selected = hyundai_canfd_alt_buttons ? torque_hda2_ev_alt : torque_hda2_ev_std;
|
||||
} else if (hyundai_hybrid_gas_signal) {
|
||||
selected = hyundai_canfd_alt_buttons ? torque_hda2_hybrid_alt : torque_hda2_hybrid_std;
|
||||
} else {
|
||||
selected = hyundai_canfd_alt_buttons ? torque_hda2_ice_alt : torque_hda2_ice_std;
|
||||
}
|
||||
} else if (hyundai_camera_scc) {
|
||||
if (hyundai_ev_gas_signal) {
|
||||
selected = hyundai_canfd_alt_buttons ? torque_camera_ev_alt : torque_camera_ev_std;
|
||||
} else if (hyundai_hybrid_gas_signal) {
|
||||
selected = hyundai_canfd_alt_buttons ? torque_camera_hybrid_alt : torque_camera_hybrid_std;
|
||||
} else {
|
||||
selected = hyundai_canfd_alt_buttons ? torque_camera_ice_alt : torque_camera_ice_std;
|
||||
}
|
||||
} else {
|
||||
if (hyundai_ev_gas_signal) {
|
||||
selected = hyundai_canfd_alt_buttons ? torque_radar_ev_alt : torque_radar_ev_std;
|
||||
} else if (hyundai_hybrid_gas_signal) {
|
||||
selected = hyundai_canfd_alt_buttons ? torque_radar_hybrid_alt : torque_radar_hybrid_std;
|
||||
} else {
|
||||
selected = hyundai_canfd_alt_buttons ? torque_radar_ice_alt : torque_radar_ice_std;
|
||||
}
|
||||
}
|
||||
ret.rx_checks = selected;
|
||||
ret.rx_checks_len = 6;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1475,7 +1578,10 @@ static safety_config hyundai_canfd_init(uint16_t param) {
|
||||
const bool valid = !GET_FLAG(param, 32768U);
|
||||
#endif
|
||||
safety_config ret = {0};
|
||||
if (valid) {
|
||||
const bool marker_valid = GET_FLAG(param, 16384U) || !GET_FLAG(param, 2048U) ||
|
||||
hyundai_canfd_stock_torque_aol_param(param) ||
|
||||
(param == 0x8815U) || (param == 0x8895U);
|
||||
if (valid && marker_valid) {
|
||||
ret = hyundai_canfd_init_validated(param);
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
// Bit 11 selects the shared axis lease only for these ordinary torque layouts.
|
||||
// Angle model selectors and the separate Ioniq 6 actuator profile stay separate.
|
||||
static inline bool hyundai_canfd_stock_torque_aol_param(uint16_t param) {
|
||||
const uint16_t base = param & (uint16_t)~0x0800U;
|
||||
const uint16_t gas = base & 3U;
|
||||
const uint16_t topology = base & 0x00B8U;
|
||||
const bool carnival = (base & 0x2000U) != 0U;
|
||||
const bool ccnc = (base & 0x0400U) != 0U;
|
||||
const bool standard = (topology == 0U) || (topology == 8U) || (topology == 16U) || (topology == 144U);
|
||||
const bool alternate = (topology == 32U) || (topology == 40U) ||
|
||||
(carnival && ((topology == 48U) || (topology == 176U)));
|
||||
return ((param & 0x0800U) != 0U) && ((base & (uint16_t)~0x24BBU) == 0U) && (gas != 3U) &&
|
||||
(standard || alternate) && (!ccnc || ((topology == 8U) || (topology == 40U))) &&
|
||||
(!carnival || (alternate && (gas != 1U)));
|
||||
}
|
||||
@@ -15,6 +15,13 @@
|
||||
|
||||
static bool classic_scc_aol_enabled = false;
|
||||
static bool classic_scc_aol_camera = false;
|
||||
static bool classic_long_aol_enabled = false;
|
||||
static bool classic_long_main_neutral = false;
|
||||
static bool classic_long_main_pressed = false;
|
||||
|
||||
static bool classic_long_aol_param(uint16_t param) {
|
||||
return ((param & 0x0404U) == 0x0404U) && ((param & 0xF3B8U) == 0U) && ((param & 3U) != 3U);
|
||||
}
|
||||
|
||||
static bool classic_scc_aol_param(uint16_t param) {
|
||||
// Stock SCC gas, torque limits and camera routing only. No LONG or NON_SCC.
|
||||
@@ -56,6 +63,8 @@ static void classic_non_scc_aol_clear_authorization(void) {
|
||||
classic_non_scc_aol_source_neutral[i] = false;
|
||||
classic_non_scc_aol_source_ts[i] = 0U;
|
||||
}
|
||||
classic_long_main_neutral = false;
|
||||
classic_long_main_pressed = false;
|
||||
aol_host_axis_mask = 0U;
|
||||
}
|
||||
|
||||
@@ -64,6 +73,7 @@ static void classic_non_scc_aol_reset(void) {
|
||||
classic_non_scc_aol_enabled = false;
|
||||
classic_scc_aol_enabled = false;
|
||||
classic_scc_aol_camera = false;
|
||||
classic_long_aol_enabled = false;
|
||||
classic_non_scc_aol_ev = false;
|
||||
classic_non_scc_aol_hev = false;
|
||||
classic_non_scc_aol_lda = false;
|
||||
@@ -94,8 +104,9 @@ static uint8_t classic_non_scc_aol_request_mask(void) {
|
||||
classic_non_scc_aol_clear_authorization();
|
||||
} else if (heartbeat_engaged) {
|
||||
classic_non_scc_aol_session = true;
|
||||
if (classic_scc_aol_enabled && classic_non_scc_aol_lda && controls_allowed &&
|
||||
classic_non_scc_aol_main_seen && aol_rx_healthy() && ((aol_host_axis_mask & 1U) != 0U)) {
|
||||
if (((classic_scc_aol_enabled && classic_non_scc_aol_lda) || classic_long_aol_enabled) && controls_allowed &&
|
||||
classic_non_scc_aol_main_seen && (!classic_long_aol_enabled || classic_non_scc_aol_main) &&
|
||||
aol_rx_healthy() && ((aol_host_axis_mask & 1U) != 0U)) {
|
||||
// Actual stock ACC activation can seed the host's LKAS-on-engage latch.
|
||||
classic_non_scc_aol_token = true;
|
||||
classic_non_scc_aol_token_ts = now;
|
||||
@@ -103,7 +114,7 @@ static uint8_t classic_non_scc_aol_request_mask(void) {
|
||||
if (((aol_host_axis_mask & 1U) != 0U) && classic_non_scc_aol_token) {
|
||||
classic_non_scc_aol_claimed = true;
|
||||
}
|
||||
result = aol_host_axis_mask & 1U; // NON-SCC never grants an independent longitudinal axis.
|
||||
result = aol_host_axis_mask & (classic_long_aol_enabled ? 3U : 1U);
|
||||
} else {
|
||||
// Physical evidence may precede heartbeat but grants no permission yet.
|
||||
}
|
||||
@@ -116,10 +127,14 @@ static uint8_t classic_non_scc_aol_permission_mask(void) {
|
||||
classic_non_scc_aol_clear_authorization();
|
||||
} else {
|
||||
const uint8_t request = classic_non_scc_aol_request_mask();
|
||||
const bool physical_authorized = (classic_scc_aol_enabled && classic_non_scc_aol_lda) ?
|
||||
const bool physical_authorized = (classic_long_aol_enabled || (classic_scc_aol_enabled && classic_non_scc_aol_lda)) ?
|
||||
(classic_non_scc_aol_token || controls_allowed) :
|
||||
(classic_non_scc_aol_main || classic_non_scc_aol_token || controls_allowed);
|
||||
result = (((request & 1U) != 0U) && classic_non_scc_aol_main_seen && physical_authorized) ? 1U : 0U;
|
||||
result = (((request & 1U) != 0U) && classic_non_scc_aol_main_seen && physical_authorized &&
|
||||
(!classic_long_aol_enabled || classic_non_scc_aol_main)) ? 1U : 0U;
|
||||
if (classic_long_aol_enabled && classic_non_scc_aol_main && controls_allowed && ((request & 2U) != 0U)) {
|
||||
result |= 2U;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -137,11 +152,31 @@ static void classic_non_scc_aol_rx(const CANPacket_t *msg) {
|
||||
if (classic_non_scc_aol_enabled && ((msg->bus == 0U) || (classic_scc_aol_enabled && classic_scc_aol_camera && (msg->bus == 2U)))) {
|
||||
const uint32_t now = microsecond_timer_get();
|
||||
const uint8_t scc_bus = classic_scc_aol_camera ? 2U : 0U;
|
||||
if (classic_long_aol_enabled && (msg->bus == 0U) && (msg->addr == 0x394U) && (GET_LEN(msg) == 8U)) {
|
||||
classic_non_scc_aol_main = ((msg->data[5] >> 3U) & 3U) == 0U;
|
||||
classic_non_scc_aol_main_seen = true;
|
||||
if (!classic_non_scc_aol_main) {
|
||||
classic_non_scc_aol_token = false;
|
||||
classic_non_scc_aol_claimed = false;
|
||||
}
|
||||
}
|
||||
if (classic_long_aol_enabled && (msg->bus == 0U) && (msg->addr == 0x4F1U) && (GET_LEN(msg) == 4U)) {
|
||||
const bool main_pressed = GET_BIT(msg, 3U);
|
||||
if (!main_pressed) {
|
||||
classic_long_main_neutral = true;
|
||||
} else if (classic_long_main_neutral && !classic_long_main_pressed && (aol_host_axis_mask == 0U)) {
|
||||
classic_non_scc_aol_token = true;
|
||||
classic_non_scc_aol_claimed = false;
|
||||
classic_non_scc_aol_token_ts = now;
|
||||
} else {
|
||||
}
|
||||
classic_long_main_pressed = main_pressed;
|
||||
}
|
||||
if (classic_scc_aol_enabled && (msg->bus == scc_bus) && (msg->addr == 0x420U) && (GET_LEN(msg) == 8U)) {
|
||||
classic_non_scc_aol_main = GET_BIT(msg, 0U);
|
||||
classic_non_scc_aol_main_seen = true;
|
||||
}
|
||||
if (!classic_scc_aol_enabled && (GET_LEN(msg) == 8U) &&
|
||||
if (!classic_scc_aol_enabled && !classic_long_aol_enabled && (GET_LEN(msg) == 8U) &&
|
||||
((!classic_non_scc_aol_ev && !classic_non_scc_aol_hev && (msg->addr == 0x260U)) ||
|
||||
(classic_non_scc_aol_ev && (msg->addr == 0x592U)) ||
|
||||
(classic_non_scc_aol_hev && (msg->addr == 0x595U)))) {
|
||||
@@ -192,13 +227,16 @@ static void classic_non_scc_aol_configure(uint16_t param, bool legacy) {
|
||||
classic_non_scc_aol_reset();
|
||||
classic_scc_aol_enabled = classic_scc_aol_param(param) && (!legacy || ((param & 8U) == 0U)) &&
|
||||
((unsigned int)alternative_experience == 32U);
|
||||
#ifdef ALLOW_DEBUG
|
||||
classic_long_aol_enabled = !legacy && classic_long_aol_param(param) && ((unsigned int)alternative_experience == 32U);
|
||||
#endif
|
||||
classic_scc_aol_camera = classic_scc_aol_enabled && ((param & 8U) != 0U);
|
||||
classic_non_scc_aol_enabled = ((!legacy && ((param == HYUNDAI_FORTE_AOL_MAIN) || (param == HYUNDAI_FORTE_AOL_LDA) ||
|
||||
(param == HYUNDAI_KONA_AOL_MAIN) || (param == HYUNDAI_KONA_AOL_LDA) ||
|
||||
(param == HYUNDAI_NON_SCC_HEV_AOL_MAIN) || (param == HYUNDAI_NON_SCC_HEV_AOL_LDA) ||
|
||||
(param == HYUNDAI_NON_SCC_EV_ALT_AOL_MAIN) || (param == HYUNDAI_NON_SCC_EV_ALT_AOL_LDA))) || classic_scc_aol_enabled) &&
|
||||
(param == HYUNDAI_NON_SCC_EV_ALT_AOL_MAIN) || (param == HYUNDAI_NON_SCC_EV_ALT_AOL_LDA))) || classic_scc_aol_enabled || classic_long_aol_enabled) &&
|
||||
((unsigned int)alternative_experience == 32U);
|
||||
classic_non_scc_aol_lda = classic_non_scc_aol_enabled && ((classic_scc_aol_enabled && ((param & 0x0800U) != 0U)) || (param == HYUNDAI_FORTE_AOL_LDA) || (param == HYUNDAI_KONA_AOL_LDA) ||
|
||||
classic_non_scc_aol_lda = classic_non_scc_aol_enabled && (((classic_scc_aol_enabled || classic_long_aol_enabled) && ((param & 0x0800U) != 0U)) || (param == HYUNDAI_FORTE_AOL_LDA) || (param == HYUNDAI_KONA_AOL_LDA) ||
|
||||
(param == HYUNDAI_NON_SCC_HEV_AOL_LDA) ||
|
||||
(param == HYUNDAI_NON_SCC_EV_ALT_AOL_LDA));
|
||||
classic_non_scc_aol_ev = classic_non_scc_aol_enabled && ((param == HYUNDAI_NON_SCC_EV_ALT_AOL_MAIN) || (param == HYUNDAI_NON_SCC_EV_ALT_AOL_LDA));
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
from opendbc.car.structs import CarParams
|
||||
import unittest
|
||||
from opendbc.safety.tests import test_hyundai_kona_lda_aol as fixture
|
||||
|
||||
|
||||
class TestHyundaiClassicLongAol(unittest.TestCase):
|
||||
def setUp(self):
|
||||
fixture.TestHyundaiKonaLdaAol.setUp(self)
|
||||
self.release = self.safety.set_safety_hooks(CarParams.SafetyModel.allOutput, 0) != 0
|
||||
|
||||
tearDown = fixture.TestHyundaiKonaLdaAol.tearDown
|
||||
reset = fixture.TestHyundaiKonaLdaAol.reset
|
||||
rx = fixture.TestHyundaiKonaLdaAol.rx
|
||||
request = fixture.TestHyundaiKonaLdaAol.request
|
||||
|
||||
def feed(self, word=0x404, button=0, main=False, available=True, tcs=True, clu=True, wrong_bus=None, corrupt_tcs=False):
|
||||
self.safety.set_timer(self.now)
|
||||
c = self.counter
|
||||
self.counter += 1
|
||||
if word & 3:
|
||||
self.rx('E_EMS11', {'Accel_Pedal_Pos': 0, 'CR_Vcu_AccPedDep_Pos': 0})
|
||||
else:
|
||||
self.rx('EMS16', {'AliveCounter': c % 4}, integrity=True)
|
||||
self.rx('WHL_SPD11', {'WHL_SPD_AliveCounter_LSB': c % 4, 'WHL_SPD_AliveCounter_MSB': (c // 4) % 4}, integrity=True)
|
||||
if tcs:
|
||||
self.rx('TCS13', {'AliveCounterTCS': c % 8, 'ACCEnable': 0 if available else 1}, integrity=not corrupt_tcs, bus=0 if wrong_bus is None else wrong_bus)
|
||||
self.rx('MDPS12', {'CR_Mdps_StrColTq': 0})
|
||||
if clu:
|
||||
self.rx('CLU11', {'CF_Clu_AliveCnt1': c % 16, 'CF_Clu_CruiseSwState': button, 'CF_Clu_CruiseSwMain': int(main)})
|
||||
self.now += 10000
|
||||
|
||||
def test_availability_and_held_main_cannot_authorize(self):
|
||||
self.reset(0x404)
|
||||
for _ in range(6):
|
||||
self.feed(main=True)
|
||||
self.assertEqual(self.request(3), 0)
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
def test_physical_main_grants_lateral_only_without_stock_scc(self):
|
||||
self.reset(0x404)
|
||||
for _ in range(6):
|
||||
self.feed()
|
||||
self.feed(main=True)
|
||||
expected = 0 if self.release else 1
|
||||
self.assertEqual(self.request(3), expected)
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
def test_set_resume_capture_and_cancel_keep_longitudinal_physical(self):
|
||||
if self.release:
|
||||
self.skipTest('Current classic OP-long is DEBUG-only')
|
||||
for button in (1, 2):
|
||||
self.reset(0xC04)
|
||||
for _ in range(6):
|
||||
self.feed(0xC04)
|
||||
self.feed(0xC04, button=button)
|
||||
self.feed(0xC04)
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
self.assertEqual(self.request(3), 3)
|
||||
self.feed(0xC04, button=4)
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
self.assertEqual(self.request(3), 1)
|
||||
self.safety.set_aol_test_heartbeat(False)
|
||||
self.assertEqual(self.request(3), 0)
|
||||
|
||||
def test_tcs_fault_and_expiry_revoke_without_disabled_scc_requirement(self):
|
||||
if self.release:
|
||||
self.skipTest('Current classic OP-long is DEBUG-only')
|
||||
for cause in ('fault', 'expiry'):
|
||||
self.reset(0x404)
|
||||
for _ in range(6):
|
||||
self.feed()
|
||||
self.feed(main=True)
|
||||
self.assertEqual(self.request(1), 1)
|
||||
if cause == 'fault':
|
||||
self.feed(available=False)
|
||||
else:
|
||||
for _ in range(120):
|
||||
self.feed(tcs=False)
|
||||
self.request(1)
|
||||
self.assertEqual(self.request(3), 0)
|
||||
|
||||
def test_release_marked_long_has_zero_tx_and_existing_unmarked_is_unchanged(self):
|
||||
if not self.release:
|
||||
self.skipTest('Explicit release marked-owner boundary')
|
||||
for word in (0x404, 0xC04, 0x405, 0xC05, 0x406, 0xC06, 0x444, 0xC44, 0x445, 0xC45, 0x446, 0xC46):
|
||||
self.reset(word)
|
||||
self.assertEqual(self.request(3), 0)
|
||||
for name, values in (('LKAS11', {'CR_Lkas_StrToqReq': 0, 'CF_Lkas_ActToi': 0}), ('SCC12', {'ACCMode': 0, 'aReqRaw': 0, 'aReqValue': 0})):
|
||||
self.assertFalse(self.safety.safety_tx_hook(self.packer.make_can_msg_safety(name, 0, values)))
|
||||
self.reset(4)
|
||||
self.assertTrue(self.safety.safety_tx_hook(self.packer.make_can_msg_safety('LKAS11', 0, {'CR_Lkas_StrToqReq': 0, 'CF_Lkas_ActToi': 0})))
|
||||
self.assertFalse(self.safety.safety_tx_hook(self.packer.make_can_msg_safety('SCC12', 0, {'ACCMode': 1, 'aReqRaw': 1, 'aReqValue': 1})))
|
||||
|
||||
def test_required_clu_expiry_clears_prior_main_and_requires_fresh_gesture(self):
|
||||
if self.release:
|
||||
self.skipTest('Current classic OP-long is DEBUG-only')
|
||||
self.reset(0x404)
|
||||
for _ in range(6):
|
||||
self.feed()
|
||||
self.feed(main=True)
|
||||
self.assertEqual(self.request(1), 1)
|
||||
for _ in range(120):
|
||||
self.feed(clu=False)
|
||||
self.request(1)
|
||||
self.assertEqual(self.request(3), 0)
|
||||
for _ in range(6):
|
||||
self.feed(main=True)
|
||||
self.assertEqual(self.request(1), 0)
|
||||
self.assertEqual(self.request(0), 0)
|
||||
self.feed()
|
||||
self.feed(main=True)
|
||||
self.assertEqual(self.request(1), 1)
|
||||
|
||||
def test_wrong_bus_tcs_does_not_renew_ready_health(self):
|
||||
if self.release:
|
||||
self.skipTest('Current classic OP-long is DEBUG-only')
|
||||
self.reset(0x404)
|
||||
for _ in range(6):
|
||||
self.feed()
|
||||
self.feed(main=True)
|
||||
self.assertEqual(self.request(1), 1)
|
||||
for _ in range(120):
|
||||
self.feed(wrong_bus=1)
|
||||
self.request(1)
|
||||
self.assertEqual(self.request(3), 0)
|
||||
@@ -0,0 +1,95 @@
|
||||
import unittest
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.hyundai.canfd_stock_aol import STOCK_AOL_WORDS
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
|
||||
class TestHyundaiSharedCanfdStockAol(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.packer = CANPacker('hyundai_canfd_generated')
|
||||
|
||||
def mode(self, word):
|
||||
self.safety.set_alternative_experience(0)
|
||||
self.assertEqual(self.safety.set_safety_hooks(structs.CarParams.SafetyModel.hyundaiCanfd, word), 0)
|
||||
self.safety.init_tests()
|
||||
self.safety.set_timer(1_000_000)
|
||||
|
||||
@staticmethod
|
||||
def packet(frame):
|
||||
return libsafety_py.make_CANPacket(frame[0], frame[2], frame[1])
|
||||
|
||||
def healthy(self, word, *, omit=None, wrong_bus=None):
|
||||
pt = 1 if word & 0x10 else 0
|
||||
fuel = {0: 'ACCELERATOR_BRAKE_ALT', 1: 'ACCELERATOR', 2: 'ACCELERATOR_ALT'}[word & 3]
|
||||
buttons = 'CRUISE_BUTTONS_ALT' if word & 0x20 else 'CRUISE_BUTTONS'
|
||||
for counter in range(1, 7):
|
||||
for name in (fuel, 'TCS', 'WHEEL_SPEEDS', 'MDPS', buttons):
|
||||
if name == omit:
|
||||
continue
|
||||
bus = 1 - pt if name == wrong_bus else pt
|
||||
values = {'COUNTER': counter % (16 if name == 'CRUISE_BUTTONS' else 256)}
|
||||
if name == 'MDPS':
|
||||
values['MDPS_StrTqSnsrVal'] = 0
|
||||
frame = self.packer.make_can_msg(name, bus, values)
|
||||
self.assertTrue(self.safety.safety_rx_hook(self.packet(frame)), name)
|
||||
if not word & 4 and omit != 'SCC_CONTROL':
|
||||
frame = self.packer.make_can_msg('SCC_CONTROL', 2 if word & 8 else pt, {'COUNTER': counter})
|
||||
self.assertTrue(self.safety.safety_rx_hook(self.packet(frame)))
|
||||
|
||||
def gesture(self, word):
|
||||
pt = 1 if word & 0x10 else 0
|
||||
name = 'CRUISE_BUTTONS_ALT' if word & 0x20 else 'CRUISE_BUTTONS'
|
||||
for count, pressed in ((7, 1), (8, 0)):
|
||||
frame = self.packer.make_can_msg(name, pt, {'COUNTER': count, 'LDA_BTN': pressed})
|
||||
self.assertTrue(self.safety.safety_rx_hook(self.packet(frame)))
|
||||
self.safety.set_aol_test_heartbeat(True)
|
||||
self.safety.aol_set_host_request(3)
|
||||
|
||||
def test_selected_fuel_button_and_scc_sources_are_required(self):
|
||||
for word in (0x800 | fuel | topology for fuel in (0, 1, 2) for topology in (0, 8, 16, 32, 40, 144)):
|
||||
fuel = {0: 'ACCELERATOR_BRAKE_ALT', 1: 'ACCELERATOR', 2: 'ACCELERATOR_ALT'}[word & 3]
|
||||
buttons = 'CRUISE_BUTTONS_ALT' if word & 0x20 else 'CRUISE_BUTTONS'
|
||||
for omitted in (fuel, buttons, 'SCC_CONTROL'):
|
||||
self.mode(word)
|
||||
self.healthy(word, omit=omitted)
|
||||
if omitted != buttons:
|
||||
self.gesture(word)
|
||||
else:
|
||||
self.safety.set_aol_test_heartbeat(True)
|
||||
self.safety.aol_set_host_request(3)
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
|
||||
self.mode(word)
|
||||
self.healthy(word)
|
||||
self.gesture(word)
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 1)
|
||||
|
||||
def test_full_stock_word_inventory_and_unknown_marker_rejection(self):
|
||||
for word in STOCK_AOL_WORDS:
|
||||
with self.subTest(word=hex(word)):
|
||||
self.mode(word)
|
||||
self.healthy(word)
|
||||
self.gesture(word)
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 1)
|
||||
pt = 1 if word & 0x10 else 0
|
||||
self.assertFalse(self.safety.safety_tx_hook(self.packet(self.packer.make_can_msg('SCC_CONTROL', pt, {}))))
|
||||
for word in (0x803, 0x804, 0x815, 0x895, 0x830, 0x880, 0xC00):
|
||||
self.mode(word)
|
||||
self.safety.set_aol_test_heartbeat(True)
|
||||
self.safety.aol_set_host_request(3)
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
|
||||
|
||||
def test_source_expiry_and_wrong_bus_withdraw_permission(self):
|
||||
for word in (0x800 | fuel | topology for fuel in (0, 1, 2) for topology in (0, 8, 16, 32, 40, 144)):
|
||||
self.mode(word)
|
||||
self.healthy(word)
|
||||
self.gesture(word)
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 1)
|
||||
self.safety.set_timer(2_000_000)
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
|
||||
self.mode(word)
|
||||
self.healthy(word, wrong_bus='WHEEL_SPEEDS')
|
||||
self.gesture(word)
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
|
||||
@@ -103,7 +103,15 @@ int main() {
|
||||
legacy.safety_mode = HYUNDAI_LEGACY_AOL_PROFILE.mode;
|
||||
assert(aol_capable(legacy, HYUNDAI_LEGACY_AOL_PROFILE.mode, VEHICLE_REGISTRY) ==
|
||||
(scc_expected && ((word & 8U) == 0U)));
|
||||
const bool expected = scc_expected || word == 0x1400U || word == 0x1C00U || word == 0x1440U || word == 0x1C40U ||
|
||||
bool long_expected = false;
|
||||
for (const uint16_t gas : {0U, 1U, 2U}) {
|
||||
for (const uint16_t limits : {0U, 64U}) {
|
||||
for (const uint16_t lda : {0U, 2048U}) {
|
||||
long_expected |= word == (0x0404U | gas | limits | lda);
|
||||
}
|
||||
}
|
||||
}
|
||||
const bool expected = long_expected || scc_expected || word == 0x1400U || word == 0x1C00U || word == 0x1440U || word == 0x1C40U ||
|
||||
word == 0x1402U || word == 0x1C02U || word == 0x1441U || word == 0x1C41U;
|
||||
assert(aol_capable(forte, HYUNDAI_CLASSIC_AOL_PROFILE.mode, VEHICLE_REGISTRY) == expected);
|
||||
assert(aol_runtime_enabled(false, forte, VEHICLE_REGISTRY) == expected);
|
||||
@@ -164,10 +172,19 @@ int main() {
|
||||
assert(!aol_capable(ev9, HYUNDAI_AOL_PROFILE.mode, VEHICLE_REGISTRY));
|
||||
}
|
||||
|
||||
// Transport namespace is exactly the retained siblings plus PE and EV9. No other
|
||||
// model2/3 or LONG word gets access through this mode28 registry predicate.
|
||||
constexpr uint16_t stock_canfd_words[] = {
|
||||
0x0800U, 0x0801U, 0x0802U, 0x0808U, 0x0809U, 0x080AU,
|
||||
0x0810U, 0x0811U, 0x0812U, 0x0820U, 0x0821U, 0x0822U,
|
||||
0x0828U, 0x0829U, 0x082AU, 0x0890U, 0x0891U, 0x0892U,
|
||||
0x0C08U, 0x0C09U, 0x0C0AU, 0x0C28U, 0x0C29U, 0x0C2AU,
|
||||
0x2820U, 0x2822U, 0x2828U, 0x282AU, 0x2830U, 0x2832U,
|
||||
0x28B0U, 0x28B2U, 0x2C28U, 0x2C2AU,
|
||||
};
|
||||
for (uint32_t word = 0U; word <= 0xFFFFU; ++word) {
|
||||
const bool expected = word == 0x0811U || word == 0x0891U || word == 0x8815U || word == 0x8895U || word == 0x5491U || word == 0x5C91U;
|
||||
bool expected = word == 0x8815U || word == 0x8895U || word == 0x5491U || word == 0x5C91U;
|
||||
for (uint16_t stock_word : stock_canfd_words) {
|
||||
expected = expected || word == stock_word;
|
||||
}
|
||||
assert(hyundai_aol_param(static_cast<uint16_t>(word)) == expected);
|
||||
}
|
||||
|
||||
@@ -242,7 +259,7 @@ int main() {
|
||||
HYUNDAI_AOL_PROFILE.mode);
|
||||
assert(stock_transport.last_write == 0U && stock_result.outcome.compatible);
|
||||
}
|
||||
for (uint16_t raw : {0x0011U, 0x0091U, 0x0810U, 0x0890U, 0x0815U, 0x0895U,
|
||||
for (uint16_t raw : {0x0011U, 0x0091U, 0x0815U, 0x0895U,
|
||||
0x0813U, 0x0831U, 0x0911U, 0x8015U, 0x8095U, 0x8814U, 0x8894U, 0x8817U}) {
|
||||
ioniq.safety_param = raw;
|
||||
assert(!aol_capable(ioniq, HYUNDAI_AOL_PROFILE.mode, VEHICLE_REGISTRY));
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import gc
|
||||
import itertools
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.selfdrive.car.card import alpha_long_requested
|
||||
from opendbc.car import gen_empty_fingerprint, structs
|
||||
from opendbc.car.hyundai.interface import CarInterface
|
||||
from opendbc.car.hyundai.classic_long_aol import CLASSIC_LONG_IDS, qualified, aol_word, native_accepts
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
from openpilot.starpilot.car.hyundai.aol import policy_for, create_intent
|
||||
from openpilot.starpilot.aol.intent import AolSettings, AOL_TOGGLE
|
||||
|
||||
|
||||
def params(identity=CAR.HYUNDAI_SONATA, *, release=False, lda=False):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
if lda:
|
||||
fingerprint[0][0x391] = 8
|
||||
request = alpha_long_requested(SimpleNamespace(get_bool=lambda key: True), is_release=release)
|
||||
return CarInterface.get_params(identity, fingerprint, [], request, release, False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity', sorted(CLASSIC_LONG_IDS, key=str))
|
||||
@pytest.mark.parametrize('lda', (False, True))
|
||||
def test_actual_final_factory_state_and_exact_marked_configuration(identity, lda):
|
||||
cp = params(identity, lda=lda)
|
||||
if cp.dashcamOnly or cp.lateralTuning.which() != 'torque' or cp.steerControlType != structs.CarParams.SteerControlType.torque:
|
||||
assert not qualified(cp)
|
||||
return
|
||||
assert qualified(cp)
|
||||
before = cp.to_dict()
|
||||
policy = policy_for(cp)
|
||||
cp.safetyConfigs[0].safetyParam |= policy.safety_param_addition
|
||||
cp.alternativeExperience |= policy.alternative_experience_addition
|
||||
expected = dict(before)
|
||||
expected['safetyConfigs'] = [{**before['safetyConfigs'][0], 'safetyParam': aol_word(cp)}]
|
||||
expected['alternativeExperience'] = 32
|
||||
assert cp.to_dict() == expected
|
||||
assert native_accepts(cp, int(cp.safetyConfigs[0].safetyModel.raw), aol_word(cp))
|
||||
release = params(identity, release=True, lda=lda)
|
||||
assert not release.openpilotLongitudinalControl
|
||||
assert release.pcmCruise
|
||||
assert not qualified(release)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity', (CAR.GENESIS_G80, CAR.KIA_XCEED_PHEV, CAR.GENESIS_G90, CAR.HYUNDAI_ELANTRA_2024, CAR.HYUNDAI_ELANTRA_HEV_2024))
|
||||
def test_unqualified_legacy_prepared_owner_and_camera_topologies_excluded(identity):
|
||||
assert not qualified(params(identity))
|
||||
|
||||
|
||||
def test_malformed_final_contract_never_marks_aol():
|
||||
cp = params()
|
||||
for field, value in (
|
||||
('flags', int(cp.flags) | int(HyundaiFlags.EV)),
|
||||
('pcmCruise', True),
|
||||
('openpilotLongitudinalControl', False),
|
||||
('passive', True),
|
||||
('notCar', True),
|
||||
('dashcamOnly', True),
|
||||
('alternativeExperience', 1),
|
||||
('alphaLongitudinalAvailable', False),
|
||||
):
|
||||
bad = cp.as_reader().as_builder()
|
||||
setattr(bad, field, value)
|
||||
assert not qualified(bad)
|
||||
for word in (0x404 | 8, 0x404 | 256, 0x404 | 512, 0x404 | 4096, 0x404 | 32768, 0x407):
|
||||
bad = cp.as_reader().as_builder()
|
||||
bad.safetyConfigs[0].safetyParam = word
|
||||
assert not qualified(bad)
|
||||
bad = cp.as_reader().as_builder()
|
||||
bad.safetyConfigs[0].safetyModel = structs.CarParams.SafetyModel.hyundaiLegacy
|
||||
assert not qualified(bad)
|
||||
|
||||
|
||||
def test_tcs_availability_without_physical_or_standard_engagement_is_not_intent():
|
||||
cp = params()
|
||||
intent = create_intent(cp, AolSettings(True, 0.0, AOL_TOGGLE, AOL_TOGGLE, (0, 0, 0), (0, 0, 0)))
|
||||
cs = structs.CarState.new_message(canValid=True, canTimeout=False, gearShifter='drive')
|
||||
cs.cruiseState.available = True
|
||||
for tick in range(20):
|
||||
intent.update(cs, fault_active=False, now_ns=1_000_000_000 + tick * 10_000_000, standard_enabled=False)
|
||||
assert not intent.allowed_latch
|
||||
|
||||
|
||||
CARD_IDENTITIES = (CAR.HYUNDAI_SONATA, CAR.HYUNDAI_IONIQ, CAR.HYUNDAI_KONA_EV)
|
||||
CARD_CASES = [
|
||||
(identity, alpha, disable, aol, False) for identity, alpha, disable, aol in itertools.product(CARD_IDENTITIES, (False, True), (False, True), (False, True))
|
||||
]
|
||||
CARD_CASES += [(identity, True, False, True, True) for identity in CARD_IDENTITIES]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity,alpha,disable,aol,release', CARD_CASES)
|
||||
def test_actual_card_published_cp_controls_and_disabled_loop(identity, alpha, disable, aol, release, monkeypatch):
|
||||
from opendbc.car.hyundai.classic_scc_aol import qualified as stock_qualified, aol_word as stock_word
|
||||
from opendbc.car.hyundai.radar_interface import RadarInterface
|
||||
from opendbc.car.hyundai.values import HyundaiSafetyFlags
|
||||
from openpilot.cereal import messaging
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.car.card import Car
|
||||
from openpilot.selfdrive.controls.controlsd import Controls
|
||||
from openpilot.starpilot.lateral.tests.test_lane_runtime import feed
|
||||
|
||||
monkeypatch.setenv('SIMULATION', '1')
|
||||
monkeypatch.setenv('REPLAY', '1')
|
||||
monkeypatch.setenv('AOL_REPLAY_RUNTIME', '0')
|
||||
with OpenpilotPrefix():
|
||||
saved = Params()
|
||||
for key, value in (
|
||||
('OpenpilotEnabledToggle', True),
|
||||
('AlphaLongitudinalEnabled', alpha),
|
||||
('IsReleaseBranch', release),
|
||||
('DisableOpenpilotLongitudinal', disable),
|
||||
('AlwaysOnLateral', aol),
|
||||
):
|
||||
saved.put_bool(key, value, block=True)
|
||||
saved.put('LKASButtonControl', AOL_TOGGLE, block=True)
|
||||
saved.put('MainCruiseButtonControl', AOL_TOGGLE, block=True)
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[0][0x391] = 8
|
||||
requested = alpha_long_requested(saved, is_release=saved.get_bool('IsReleaseBranch'))
|
||||
cp = CarInterface.get_params(identity, fingerprint, [], requested, release, False)
|
||||
assert cp.openpilotLongitudinalControl == requested
|
||||
assert cp.pcmCruise != requested
|
||||
assert not CarInterface.startup_required(cp)
|
||||
assert qualified(cp) if requested else stock_qualified(cp)
|
||||
before = cp.to_dict()
|
||||
subscriber = messaging.sub_sock('carParams', timeout=100, conflate=True)
|
||||
ci = card = controls = None
|
||||
try:
|
||||
ci = CarInterface(cp)
|
||||
card = Car(CI=ci, RI=RadarInterface(cp))
|
||||
expected = dict(before)
|
||||
if aol:
|
||||
expected['safetyConfigs'] = [{**before['safetyConfigs'][0], 'safetyParam': aol_word(card.CP) if requested else stock_word(card.CP)}]
|
||||
expected['alternativeExperience'] = 32
|
||||
assert card.CP.to_dict() == expected
|
||||
assert bool(card.CP.safetyConfigs[0].safetyParam & HyundaiSafetyFlags.LONG) == requested
|
||||
assert card.aol_qualified == aol
|
||||
assert card.vehicle_startup.owner is None
|
||||
with structs.CarParams.from_bytes(saved.get('CarParams')) as published:
|
||||
assert published.to_dict() == card.CP.to_dict()
|
||||
parsed = ci.update([])
|
||||
event = None
|
||||
for _ in range(10):
|
||||
card.car_params_published = False
|
||||
card.state_publish(parsed, None)
|
||||
event = messaging.recv_one(subscriber)
|
||||
if event is not None:
|
||||
break
|
||||
time.sleep(0.01)
|
||||
assert event is not None and event.which() == 'carParams' and event.valid
|
||||
assert event.carParams.to_dict() == card.CP.to_dict()
|
||||
controls = Controls()
|
||||
assert controls.CP.to_dict() == card.CP.to_dict()
|
||||
feed(controls, 1_000_000_000, 0, active=False, enabled=False, can_valid=False, can_timeout=True)
|
||||
command, lateral_log = controls.state_control()
|
||||
assert not command.enabled and not command.latActive and not command.longActive
|
||||
controls.publish(command, lateral_log)
|
||||
finally:
|
||||
del controls, card, ci, subscriber
|
||||
gc.collect()
|
||||
@@ -1,10 +1,16 @@
|
||||
from opendbc.car.hyundai.classic_long_aol import (
|
||||
qualified as qualified_classic_long,
|
||||
aol_word as classic_long_word,
|
||||
native_accepts as classic_long_accepts,
|
||||
LONG_AOL_WORDS,
|
||||
)
|
||||
from opendbc.car.hyundai.classic_scc_aol import (
|
||||
qualified as qualified_classic_scc,
|
||||
aol_word as classic_aol_word,
|
||||
native_accepts as classic_native_accepts,
|
||||
native_profile_supported as classic_native_profile_supported,
|
||||
)
|
||||
from opendbc.car.hyundai.canfd_stock_aol import qualified as qualified_canfd_stock, STOCK_AOL_MARKER
|
||||
from opendbc.car.hyundai.canfd_stock_aol import qualified as qualified_canfd_stock, STOCK_AOL_MARKER, STOCK_AOL_WORDS
|
||||
from opendbc.car.hyundai.non_scc_aol import qualified as qualified_non_scc, aol_word, AOL_MARKER, AOL_EXPERIENCE, AOL_WORDS
|
||||
from opendbc.car.hyundai.kona_aol import allow_lateral_onset as kona_lateral_onset
|
||||
from opendbc.car.structs import car
|
||||
@@ -62,6 +68,16 @@ def ioniq6_settings_capable(CP) -> bool:
|
||||
|
||||
|
||||
def policy_for(CP) -> AolVehiclePolicy:
|
||||
if qualified_classic_long(CP):
|
||||
return AolVehiclePolicy(
|
||||
intent_supported=True,
|
||||
settings_supported=True,
|
||||
runtime_supported=True,
|
||||
normal_runtime_supported=True,
|
||||
explicit_latch=True,
|
||||
safety_param_addition=classic_long_word(CP) ^ int(CP.safetyConfigs[0].safetyParam),
|
||||
alternative_experience_addition=AOL_EXPERIENCE,
|
||||
)
|
||||
if qualified_classic_scc(CP):
|
||||
return AolVehiclePolicy(
|
||||
intent_supported=True,
|
||||
@@ -113,13 +129,16 @@ def policy_for(CP) -> AolVehiclePolicy:
|
||||
|
||||
def native_profile_supported(model: int, param: int) -> bool:
|
||||
return (
|
||||
classic_native_profile_supported(model, param)
|
||||
or (model == int(car.CarParams.SafetyModel.hyundaiCanfd) and param in IONIQ6_AOL_SAFETY_PARAMS | set(STOCK_SAFETY_PARAMS.values()))
|
||||
(model == int(car.CarParams.SafetyModel.hyundai) and param in LONG_AOL_WORDS)
|
||||
or classic_native_profile_supported(model, param)
|
||||
or (model == int(car.CarParams.SafetyModel.hyundaiCanfd) and param in IONIQ6_AOL_SAFETY_PARAMS | STOCK_AOL_WORDS | set(STOCK_SAFETY_PARAMS.values()))
|
||||
or (model == int(car.CarParams.SafetyModel.hyundai) and param in AOL_WORDS)
|
||||
)
|
||||
|
||||
|
||||
def native_accepts_cp(CP, model: int, param: int) -> bool:
|
||||
if qualified_classic_long(CP, marked_only=True):
|
||||
return classic_long_accepts(CP, model, param)
|
||||
if qualified_classic_scc(CP, marked_only=True):
|
||||
return classic_native_accepts(CP, model, param)
|
||||
if qualified_canfd_stock(CP, marked_only=True):
|
||||
@@ -135,13 +154,23 @@ def native_latch_rejected(CP, native) -> bool:
|
||||
# In this exact native policy a requested lateral axis remains permitted
|
||||
# through pedal override; denial means the physical authorization was lost.
|
||||
return (
|
||||
(qualified_ioniq6(CP) or qualified_non_scc(CP) or qualified_canfd_stock(CP, marked_only=True) or qualified_classic_scc(CP, marked_only=True))
|
||||
(
|
||||
qualified_classic_long(CP, marked_only=True)
|
||||
or qualified_ioniq6(CP)
|
||||
or qualified_non_scc(CP)
|
||||
or qualified_canfd_stock(CP, marked_only=True)
|
||||
or qualified_classic_scc(CP, marked_only=True)
|
||||
)
|
||||
and native.requestedLateral
|
||||
and not native.lateralAllowed
|
||||
)
|
||||
|
||||
|
||||
def create_intent(CP, settings):
|
||||
if qualified_classic_long(CP):
|
||||
from openpilot.starpilot.car.hyundai.classic_long_intent import ClassicLongCardIntent
|
||||
|
||||
return ClassicLongCardIntent(CP, settings)
|
||||
if qualified_classic_scc(CP):
|
||||
from openpilot.starpilot.car.hyundai.classic_scc_intent import ClassicSccCardIntent
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/pandad/aol_protocol.h"
|
||||
#include "opendbc/safety/modes/hyundai_canfd_aol.h"
|
||||
|
||||
inline bool hyundai_aol_param(uint16_t param) {
|
||||
// Existing mode28 entry also transports exact ordinary PE/EV9 requests.
|
||||
return param == 0x0811U || param == 0x0891U || param == 0x8815U || param == 0x8895U || param == 0x5491U || param == 0x5C91U;
|
||||
return hyundai_canfd_stock_torque_aol_param(param) || param == 0x8815U || param == 0x8895U || param == 0x5491U || param == 0x5C91U;
|
||||
}
|
||||
|
||||
inline constexpr AolSafetyProfile HYUNDAI_AOL_PROFILE{28U, hyundai_aol_param, true};
|
||||
@@ -21,8 +22,12 @@ inline bool hyundai_legacy_aol_param(uint16_t param) {
|
||||
|
||||
inline constexpr AolSafetyProfile HYUNDAI_LEGACY_AOL_PROFILE{23U, hyundai_legacy_aol_param, true};
|
||||
|
||||
inline bool hyundai_classic_long_aol_param(uint16_t param) {
|
||||
return ((param & 0x0404U) == 0x0404U) && ((param & 0xF3B8U) == 0U) && ((param & 3U) != 3U);
|
||||
}
|
||||
|
||||
inline bool hyundai_classic_aol_param(uint16_t param) {
|
||||
return hyundai_classic_scc_aol_param(param) || param == 0x1400U || param == 0x1C00U || param == 0x1440U || param == 0x1C40U ||
|
||||
return hyundai_classic_long_aol_param(param) || hyundai_classic_scc_aol_param(param) || param == 0x1400U || param == 0x1C00U || param == 0x1440U || param == 0x1C40U ||
|
||||
param == 0x1402U || param == 0x1C02U || param == 0x1441U || param == 0x1C41U;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
from opendbc.car.hyundai.values import HyundaiFlags
|
||||
from openpilot.starpilot.aol.intent import AolCardIntent, AOL_TOGGLE
|
||||
|
||||
|
||||
class ClassicLongCardIntent(AolCardIntent):
|
||||
def __init__(self, cp, settings):
|
||||
super().__init__(settings, explicit_latch=True)
|
||||
self.requires_fault_observation = True
|
||||
self.observe_stock_engagement = True
|
||||
self.has_lkas = bool(cp.flags & HyundaiFlags.HAS_LDA_BUTTON)
|
||||
self._normal_previous = False
|
||||
|
||||
def update(self, CS, *, consumed_buttons=frozenset(), fault_active=None, now_ns=0, native_rejection_ns=0, standard_enabled=False):
|
||||
rejected = self._native_rejection_ns < native_rejection_ns <= now_ns
|
||||
super().update(
|
||||
CS,
|
||||
consumed_buttons=consumed_buttons,
|
||||
fault_active=fault_active,
|
||||
now_ns=now_ns,
|
||||
native_rejection_ns=native_rejection_ns,
|
||||
standard_enabled=standard_enabled,
|
||||
)
|
||||
normal = bool(standard_enabled and CS.canValid and not CS.canTimeout and CS.cruiseState.available)
|
||||
deliberate = any(event.pressed for event in CS.buttonEvents)
|
||||
if (
|
||||
normal
|
||||
and not self._normal_previous
|
||||
and self.has_lkas
|
||||
and self.settings.enabled
|
||||
and self.settings.lkas_action == AOL_TOGGLE
|
||||
and not deliberate
|
||||
and not rejected
|
||||
and not fault_active
|
||||
and not CS.steerFaultPermanent
|
||||
and not CS.steerFaultTemporary
|
||||
and not CS.gasPressed
|
||||
and not CS.brakePressed
|
||||
and not CS.accFaulted
|
||||
):
|
||||
self.allowed_latch = True
|
||||
self.pause_lateral = False
|
||||
self._last_latch_edge_ns = now_ns
|
||||
self._normal_previous = normal
|
||||
@@ -21,6 +21,10 @@ sys.path.insert(0, str(ROOT))
|
||||
from tools import test_runner
|
||||
|
||||
RELEASE_TARGETS = (
|
||||
"opendbc_repo/opendbc/safety/tests/test_hyundai_classic_long_aol.py",
|
||||
"openpilot/starpilot/aol/tests/test_classic_long_profiles.py",
|
||||
"opendbc_repo/opendbc/safety/tests/test_hyundai_shared_canfd_stock_aol.py",
|
||||
"opendbc_repo/opendbc/car/hyundai/tests/test_shared_canfd_stock_aol.py",
|
||||
"opendbc_repo/opendbc/safety/tests/test_hyundai_classic_scc_aol.py",
|
||||
"openpilot/starpilot/aol/tests/test_classic_scc_profiles.py",
|
||||
"opendbc_repo/opendbc/safety/tests/test_hyundai_blended.py",
|
||||
|
||||
Reference in New Issue
Block a user