mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-10 22:43:42 +08:00
Restore Sportage AOL and show effective wheel controls
This commit is contained in:
@@ -95,9 +95,10 @@ def qualified(cp, *, marked_only=False):
|
||||
return cp.safetyConfigs[config_index(cp)].safetyParam in ((word | STOCK_AOL_MARKER,) if marked_only else (word, word | STOCK_AOL_MARKER))
|
||||
|
||||
|
||||
LONG_AOL_WORDS = frozenset((0x0815, 0x0895, 0x2815, 0x2895))
|
||||
SPORTAGE_LONG_AOL_WORDS = frozenset(0x0804 | fuel | topology for fuel in (0, 2) for topology in (0, 8, 16, 144, 32, 40))
|
||||
LONG_AOL_WORDS = frozenset((0x0815, 0x0895, 0x2815, 0x2895)) | SPORTAGE_LONG_AOL_WORDS
|
||||
LONG_EV_CARS = frozenset((CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_KONA_EV_2ND_GEN))
|
||||
INDEPENDENT_LONG_BUTTON_CARS = LONG_EV_CARS | {CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN}
|
||||
INDEPENDENT_LONG_BUTTON_CARS = LONG_EV_CARS | {CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN}
|
||||
|
||||
|
||||
def qualified_long(cp, *, marked_only=False):
|
||||
@@ -111,7 +112,10 @@ def qualified_long(cp, *, marked_only=False):
|
||||
if not eligible(cp):
|
||||
return False
|
||||
word = _base_word(cp, experiences=(0, 32))
|
||||
if word not in (0x11, 0x91):
|
||||
if cp.carFingerprint == CAR.KIA_SPORTAGE_5TH_GEN:
|
||||
if word is None or word | 0x804 not in SPORTAGE_LONG_AOL_WORDS:
|
||||
return False
|
||||
elif word not in (0x11, 0x91):
|
||||
return False
|
||||
raw = int(cp.safetyConfigs[config_index(cp)].safetyParam)
|
||||
if cp.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN:
|
||||
|
||||
@@ -27,7 +27,7 @@ from opendbc.car.hyundai.ioniq6_longitudinal import Ioniq6LongitudinalPolicy
|
||||
from opendbc.car.hyundai.canfd_dash_icons import CanFDDashIcons
|
||||
from opendbc.car.hyundai.classic_scc_aol import qualified as qualified_classic_stock_aol
|
||||
from opendbc.car.hyundai.classic_long_aol import independent_buttons_qualified as qualified_classic_long_buttons
|
||||
from opendbc.car.hyundai.canfd_stock_aol import qualified as qualified_canfd_stock_aol
|
||||
from opendbc.car.hyundai.canfd_stock_aol import qualified as qualified_canfd_stock_aol, qualified_long as qualified_canfd_long_aol
|
||||
from opendbc.car.hyundai.ev6_aol import qualified as qualified_ev6_aol
|
||||
from opendbc.car.hyundai.g90_longitudinal import G90LongitudinalPolicy
|
||||
from opendbc.car.hyundai.gv70_longitudinal import is_gv70, scc_request, suppress_stock_cancel
|
||||
@@ -157,7 +157,9 @@ class CarController(CarControllerBase):
|
||||
ioniq6_status = (self.ioniq6_longitudinal is not None and
|
||||
int(CP.safetyConfigs[-1].safetyParam) in (0x8015, 0x8095, 0x8815, 0x8895))
|
||||
self.ioniq6_bsm_enabled = ioniq6_status
|
||||
self.ev6_aol_status = CP.carFingerprint == CAR.KIA_EV6 and (qualified_canfd_stock_aol(CP) or qualified_ev6_aol(CP))
|
||||
self.ev6_aol_status = ((CP.carFingerprint == CAR.KIA_EV6 and (qualified_canfd_stock_aol(CP) or qualified_ev6_aol(CP))) or
|
||||
(CP.carFingerprint == CAR.KIA_SPORTAGE_5TH_GEN and
|
||||
(qualified_canfd_stock_aol(CP) or qualified_canfd_long_aol(CP))))
|
||||
self.canfd_dash_icons = CanFDDashIcons() if ioniq6_status or self.torque_ev_scc_enabled or self.ev6_aol_status else None
|
||||
self.classic_dash_icons = CanFDDashIcons() if classic_aol_dash_enabled(CP) else None
|
||||
self.classic_lfa_icon = None
|
||||
|
||||
@@ -330,7 +330,7 @@ static uint8_t aol_ioniq6_request_mask(void) {
|
||||
} else if (heartbeat_engaged) {
|
||||
aol_ioniq6_session_started = true;
|
||||
if ((hyundai_canfd_ioniq6_long || hyundai_ev9_long_aol ||
|
||||
(hyundai_canfd_torque_ev_long && !aol_optional_release_policy)) && controls_allowed && aol_ioniq6_enable_release &&
|
||||
(aol_optional_release_capable && !aol_optional_release_policy)) && controls_allowed && aol_ioniq6_enable_release &&
|
||||
((aol_host_axis_mask & 0x3U) == 0x3U) &&
|
||||
(safety_get_ts_elapsed(now, aol_ioniq6_enable_release_ts) <= AOL_HOST_REQUEST_TIMEOUT_US)) {
|
||||
aol_ioniq6_lateral_latch = true;
|
||||
@@ -583,21 +583,22 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *msg) {
|
||||
cruise_button = (msg->data[4] >> 4) & 0x7U;
|
||||
main_button = GET_BIT(msg, 34U);
|
||||
}
|
||||
const bool lkas_button = hyundai_canfd_alt_buttons ? GET_BIT(msg, 39U) : GET_BIT(msg, 23U);
|
||||
const bool previous_controls_allowed = controls_allowed;
|
||||
const int previous_cruise_button = cruise_button_prev;
|
||||
const int main_policy = hyundai_canfd_ccnc_ev_long ? (hyundai_canfd_pe_long ? HYUNDAI_MAIN_WITHDRAW : HYUNDAI_MAIN_TRACK) : HYUNDAI_MAIN_NONE;
|
||||
const bool normal_main_button = hyundai_canfd_ccnc_ev_long && GET_BIT(msg, 21U);
|
||||
hyundai_common_cruise_buttons_check(cruise_button, main_button, normal_main_button, main_policy);
|
||||
if ((hyundai_canfd_ioniq6_long || hyundai_ev9_long_aol ||
|
||||
(hyundai_canfd_torque_ev_long && !aol_optional_release_policy)) && aol_ioniq6_long) {
|
||||
(aol_optional_release_capable && !aol_optional_release_policy)) && aol_ioniq6_long) {
|
||||
static uint8_t aol_ioniq6_enable_counter = 0U;
|
||||
static uint32_t aol_ioniq6_enable_ts = 0U;
|
||||
const uint8_t counter = hyundai_canfd_get_counter(msg);
|
||||
const uint32_t now = microsecond_timer_get();
|
||||
const uint32_t elapsed = safety_get_ts_elapsed(now, aol_ioniq6_enable_ts);
|
||||
const bool fresh = aol_ioniq6_enable_counter_seen &&
|
||||
hyundai_canfd_button_counter_follows(aol_ioniq6_enable_counter, counter, 0xFU) && (elapsed > 0U) && (elapsed <= 100000U);
|
||||
if (!fresh || main_button || GET_BIT(msg, 23U)) {
|
||||
hyundai_canfd_button_counter_follows(aol_ioniq6_enable_counter, counter, hyundai_canfd_alt_buttons ? 0xFFU : 0xFU) && (elapsed > 0U) && (elapsed <= 100000U);
|
||||
if (!fresh || main_button || lkas_button) {
|
||||
aol_ioniq6_enable_pending = false;
|
||||
aol_ioniq6_enable_release = false;
|
||||
} else if (aol_ioniq6_enable_neutral && (previous_cruise_button == HYUNDAI_BTN_NONE) &&
|
||||
@@ -616,7 +617,7 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *msg) {
|
||||
} else {
|
||||
// A held enable button does not renew the heartbeat grace.
|
||||
}
|
||||
aol_ioniq6_enable_neutral = !main_button && !GET_BIT(msg, 23U) && (cruise_button == HYUNDAI_BTN_NONE);
|
||||
aol_ioniq6_enable_neutral = !main_button && !lkas_button && (cruise_button == HYUNDAI_BTN_NONE);
|
||||
aol_ioniq6_enable_counter_seen = true;
|
||||
aol_ioniq6_enable_counter = counter;
|
||||
aol_ioniq6_enable_ts = now;
|
||||
@@ -657,7 +658,7 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *msg) {
|
||||
}
|
||||
aol_optional_release_previous = (uint8_t)cruise_button;
|
||||
}
|
||||
const bool gesture = main_button || (hyundai_canfd_alt_buttons ? GET_BIT(msg, 39U) : GET_BIT(msg, 23U)); // LDA/LKAS button
|
||||
const bool gesture = main_button || lkas_button;
|
||||
if (cruise_button == HYUNDAI_BTN_CANCEL) {
|
||||
aol_ioniq6_enable_reset();
|
||||
aol_optional_release_pending = false;
|
||||
@@ -1495,7 +1496,7 @@ static safety_config hyundai_canfd_init_validated(uint16_t supplied_param) {
|
||||
hyundai_canfd_angle_model = 0U;
|
||||
#ifdef ALLOW_DEBUG
|
||||
hyundai_canfd_ioniq6_long = ioniq6_long_requested;
|
||||
hyundai_canfd_torque_ev_long = (param == 0x0015U) || (param == 0x0095U) || torque_long_aol_requested || gv60_long_requested;
|
||||
hyundai_canfd_torque_ev_long = (param == 0x0015U) || (param == 0x0095U) || (torque_long_aol_requested && GET_FLAG(param, 16U)) || gv60_long_requested;
|
||||
#endif
|
||||
hyundai_torque_aol_cancel_preserves_lateral = ioniq6_stock_aol_requested || torque_long_aol_requested ||
|
||||
(param == 0x8815U) || (param == 0x8895U);
|
||||
|
||||
@@ -18,6 +18,13 @@ static inline bool hyundai_canfd_stock_torque_aol_param(uint16_t param) {
|
||||
|
||||
#if defined(ALLOW_DEBUG) || defined(__cplusplus)
|
||||
static inline bool hyundai_canfd_torque_long_aol_param(uint16_t param) {
|
||||
return (param == 0x0815U) || (param == 0x0895U) || (param == 0x2815U) || (param == 0x2895U);
|
||||
const uint16_t base = param & (uint16_t)~0x0800U;
|
||||
const uint16_t fuel = base & 3U;
|
||||
const uint16_t topology = base & 0x00B8U;
|
||||
const bool sportage = ((param & 0x0800U) != 0U) && ((base & (uint16_t)~0x00BEU) == 0U) &&
|
||||
((base & 4U) != 0U) && ((fuel == 0U) || (fuel == 2U)) &&
|
||||
((topology == 0U) || (topology == 8U) || (topology == 16U) ||
|
||||
(topology == 144U) || (topology == 32U) || (topology == 40U));
|
||||
return sportage || (param == 0x0815U) || (param == 0x0895U) || (param == 0x2815U) || (param == 0x2895U);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.hyundai.hyundaicanfd import create_steering_messages
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
from opendbc.car.hyundai.canfd_stock_aol import SPORTAGE_LONG_AOL_WORDS
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
from opendbc.safety.tests import test_hyundai_shared_canfd_stock_aol as fixtures
|
||||
|
||||
|
||||
class TestSportageLongAol(unittest.TestCase):
|
||||
packet = staticmethod(fixtures.TestHyundaiSharedCanfdStockAol.packet)
|
||||
healthy = fixtures.TestHyundaiSharedCanfdStockAol.healthy
|
||||
gesture = fixtures.TestHyundaiSharedCanfdStockAol.gesture
|
||||
|
||||
def setUp(self):
|
||||
self.safety = libsafety_py.libsafety
|
||||
self.packer = CANPacker('hyundai_canfd_generated')
|
||||
self.release = self.safety.set_safety_hooks(structs.CarParams.SafetyModel.allOutput, 0) != 0
|
||||
|
||||
def tearDown(self):
|
||||
self.safety.set_alternative_experience(0)
|
||||
self.safety.set_safety_hooks(structs.CarParams.SafetyModel.noOutput, 0)
|
||||
|
||||
def mode(self, word, experience=32):
|
||||
self.safety.set_alternative_experience(experience)
|
||||
assert self.safety.set_safety_hooks(structs.CarParams.SafetyModel.hyundaiCanfd, word) == 0
|
||||
self.safety.init_tests()
|
||||
self.safety.set_timer(1_000_000)
|
||||
|
||||
def test_physical_arm_cancel_pedals_and_ungranted_longitudinal(self):
|
||||
for word in SPORTAGE_LONG_AOL_WORDS:
|
||||
with self.subTest(word=hex(word)):
|
||||
self.mode(word)
|
||||
if self.release:
|
||||
assert not self.safety.safety_config_valid()
|
||||
continue
|
||||
self.healthy(word)
|
||||
self.safety.set_aol_test_heartbeat(True)
|
||||
self.safety.aol_set_host_request(3)
|
||||
assert self.safety.aol_get_permission_mask() == 0
|
||||
self.gesture(word)
|
||||
assert self.safety.aol_get_permission_mask() == 1
|
||||
bus = 1 if word & 16 else 0
|
||||
flags = HyundaiFlags.CANFD
|
||||
if word & 16:
|
||||
flags |= HyundaiFlags.CANFD_LKA_STEER_MSG
|
||||
if word & 128:
|
||||
flags |= HyundaiFlags.CANFD_LKA_STEER_MSG_ALT
|
||||
cp = SimpleNamespace(carFingerprint=CAR.KIA_SPORTAGE_5TH_GEN, flags=int(flags), openpilotLongitudinalControl=True)
|
||||
frames = create_steering_messages(self.packer, cp, SimpleNamespace(ECAN=bus, ACAN=1-bus), True, True, 1)
|
||||
for frame in frames:
|
||||
assert self.safety.safety_tx_hook(self.packet(frame))
|
||||
excessive = create_steering_messages(self.packer, cp, SimpleNamespace(ECAN=bus, ACAN=1-bus), True, True, 410)
|
||||
assert not self.safety.safety_tx_hook(self.packet(excessive[0]))
|
||||
name = 'CRUISE_BUTTONS_ALT' if word & 32 else 'CRUISE_BUTTONS'
|
||||
frame = self.packer.make_can_msg(name, bus, {'COUNTER': 9, 'CRUISE_BUTTONS': 4})
|
||||
assert self.safety.safety_rx_hook(self.packet(frame))
|
||||
assert self.safety.aol_get_permission_mask() == 1
|
||||
for counter, brake in ((7, True), (8, False)):
|
||||
frame = self.packer.make_can_msg('TCS', bus, {'COUNTER': counter, 'DriverBraking': brake})
|
||||
assert self.safety.safety_rx_hook(self.packet(frame))
|
||||
assert self.safety.aol_get_permission_mask() == 1
|
||||
accel = self.packet(self.packer.make_can_msg('SCC_CONTROL', bus, {'ACCMode': 1, 'aReqRaw': .1, 'aReqValue': .1}))
|
||||
assert not self.safety.safety_tx_hook(accel)
|
||||
self.safety.set_timer(1_500_001)
|
||||
assert self.safety.aol_get_permission_mask() == 0
|
||||
|
||||
def test_wrong_experience_missing_source_and_wrong_bus_deny(self):
|
||||
for word in SPORTAGE_LONG_AOL_WORDS:
|
||||
for experience in (0, 1, 33):
|
||||
self.mode(word, experience)
|
||||
assert not self.safety.safety_config_valid()
|
||||
if self.release:
|
||||
continue
|
||||
fuel = 'ACCELERATOR_ALT' if word & 2 else 'ACCELERATOR_BRAKE_ALT'
|
||||
buttons = 'CRUISE_BUTTONS_ALT' if word & 32 else 'CRUISE_BUTTONS'
|
||||
for omitted in (fuel, buttons, 'TCS', 'WHEEL_SPEEDS', 'MDPS'):
|
||||
self.mode(word)
|
||||
self.healthy(word, omit=omitted)
|
||||
if omitted != buttons:
|
||||
self.gesture(word)
|
||||
assert self.safety.aol_get_permission_mask() == 0
|
||||
|
||||
def test_fresh_set_release_arms_lateral_through_cancel_and_brake(self):
|
||||
for word in SPORTAGE_LONG_AOL_WORDS:
|
||||
self.mode(word)
|
||||
if self.release:
|
||||
assert not self.safety.safety_config_valid()
|
||||
continue
|
||||
name = 'CRUISE_BUTTONS_ALT' if word & 32 else 'CRUISE_BUTTONS'
|
||||
maximum = 255 if word & 32 else 14
|
||||
bus = 1 if word & 16 else 0
|
||||
self.healthy(word, omit=name)
|
||||
self.safety.set_aol_test_heartbeat(True)
|
||||
self.safety.aol_set_host_request(3)
|
||||
for tick, counter, button in ((0, maximum - 1, 0), (1, maximum, 2), (2, 0, 0)):
|
||||
self.safety.set_timer(1_000_000 + tick * 20_000)
|
||||
frame = self.packer.make_can_msg(name, bus, {'COUNTER': counter, 'CRUISE_BUTTONS': button})
|
||||
assert self.safety.safety_rx_hook(self.packet(frame))
|
||||
self.safety.aol_set_host_request(3)
|
||||
assert self.safety.get_controls_allowed()
|
||||
assert self.safety.aol_get_permission_mask() == 3
|
||||
self.safety.set_timer(1_060_000)
|
||||
frame = self.packer.make_can_msg(name, bus, {'COUNTER': 1, 'CRUISE_BUTTONS': 4})
|
||||
assert self.safety.safety_rx_hook(self.packet(frame))
|
||||
assert not self.safety.get_controls_allowed()
|
||||
assert self.safety.aol_get_permission_mask() == 1, hex(word)
|
||||
frame = self.packer.make_can_msg('TCS', bus, {'COUNTER': 7, 'DriverBraking': True})
|
||||
assert self.safety.safety_rx_hook(self.packet(frame))
|
||||
assert self.safety.aol_get_permission_mask() == 1
|
||||
@@ -512,6 +512,8 @@ int main() {
|
||||
0x6A28U, 0x6A2AU, 0x7008U, 0x700AU, 0x7010U, 0x7012U, 0x7028U, 0x702AU,
|
||||
0x7090U, 0x7092U, 0x7208U, 0x720AU, 0x7228U, 0x722AU, 0x7809U, 0x7829U,
|
||||
};
|
||||
constexpr uint16_t sportage_long_words[] = {0x0804U, 0x0806U, 0x080CU, 0x080EU, 0x0814U, 0x0816U,
|
||||
0x0894U, 0x0896U, 0x0824U, 0x0826U, 0x082CU, 0x082EU};
|
||||
for (uint32_t word = 0U; word <= 0xFFFFU; ++word) {
|
||||
bool expected = word == 0x2815U || word == 0x2895U || word == 0x0815U || word == 0x0895U || word == 0x8815U || word == 0x8895U || word == 0x5491U || word == 0x5C91U || word == 0x5C95U || word == 0x5495U;
|
||||
for (uint16_t stock_word : stock_canfd_words) {
|
||||
@@ -520,6 +522,9 @@ int main() {
|
||||
for (uint16_t stock_word : stock_angle_words) {
|
||||
expected = expected || word == stock_word;
|
||||
}
|
||||
for (uint16_t sportage_word : sportage_long_words) {
|
||||
expected = expected || word == sportage_word;
|
||||
}
|
||||
assert(hyundai_aol_param(static_cast<uint16_t>(word)) == expected);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car import gen_empty_fingerprint, structs
|
||||
from opendbc.car.hyundai.canfd_stock_aol import qualified_long
|
||||
from opendbc.car.hyundai.interface import CarInterface
|
||||
from opendbc.car.hyundai.values import CAR
|
||||
from openpilot.starpilot.aol.intent import AolSettings
|
||||
from openpilot.starpilot.car.hyundai.aol import create_intent, native_accepts_cp, policy_for
|
||||
from openpilot.starpilot.ui.wheel_feature import WheelFeature
|
||||
|
||||
|
||||
def sportage(*, hybrid=False, alternate=False, lka=0, alpha=True):
|
||||
fp = gen_empty_fingerprint()
|
||||
bus = 1 if lka else 0
|
||||
if lka:
|
||||
fp[2][0x110 if lka == 2 else 0x50] = 32 if lka == 2 else 16
|
||||
fp[bus].update({0x1aa if alternate else 0x1cf: 16 if alternate else 8, 0x130: 32, 0x35: 32})
|
||||
if hybrid:
|
||||
fp[bus][0xFA] = 8
|
||||
fw = [structs.CarParams.CarFw(ecu='adas')] if lka else []
|
||||
return CarInterface.get_params(CAR.KIA_SPORTAGE_5TH_GEN, fp, fw, alpha, False, False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('hybrid', [False, True])
|
||||
@pytest.mark.parametrize('layout', [(0, False), (0, True), (1, False), (2, False)])
|
||||
@pytest.mark.parametrize('alpha', [False, True])
|
||||
def test_sportage_factory_wheel_choices_and_physical_latch(hybrid, layout, alpha):
|
||||
lka, alternate = layout
|
||||
cp = sportage(hybrid=hybrid, alternate=alternate, lka=lka, alpha=alpha)
|
||||
assert cp.openpilotLongitudinalControl == alpha
|
||||
policy = policy_for(cp)
|
||||
assert policy.settings_supported and policy.runtime_supported
|
||||
owner = SimpleNamespace(vehicle_params=lambda: cp, configuration_vehicle=lambda: False,
|
||||
configuration_longitudinal=lambda: False, longitudinal_available=lambda: alpha)
|
||||
for key in ('LKASButtonControl', 'MainCruiseButtonControl'):
|
||||
assert 'Toggle AOL' in WheelFeature(owner).choices(key)
|
||||
cp.safetyConfigs[-1].safetyParam |= policy.safety_param_addition
|
||||
cp.alternativeExperience |= policy.alternative_experience_addition
|
||||
assert native_accepts_cp(cp, int(cp.safetyConfigs[-1].safetyModel.raw), cp.safetyConfigs[-1].safetyParam)
|
||||
if alpha:
|
||||
assert qualified_long(cp, marked_only=True)
|
||||
for button in ('lkas', 'mainCruise'):
|
||||
intent = create_intent(cp, AolSettings(True, 0., 9, 9, (0, 0, 0), (0, 0, 0)))
|
||||
cs = structs.CarState(canValid=True, gearShifter='drive')
|
||||
cs.cruiseState.available = True
|
||||
intent.update(cs, now_ns=1, fault_active=False)
|
||||
cs.buttonEvents = [structs.CarState.ButtonEvent(type=button, pressed=True)]
|
||||
intent.update(cs, now_ns=2, fault_active=False)
|
||||
assert intent.allowed_latch
|
||||
cs.buttonEvents = [structs.CarState.ButtonEvent(type='cancel', pressed=True)]
|
||||
intent.update(cs, now_ns=3, fault_active=False)
|
||||
assert intent.allowed_latch
|
||||
cs.buttonEvents = []
|
||||
cs.brakePressed = True
|
||||
intent.update(cs, now_ns=4, fault_active=False)
|
||||
assert intent.allowed_latch
|
||||
cs.canValid = False
|
||||
intent.update(cs, now_ns=5, fault_active=False)
|
||||
assert not intent.output(cs)[0]
|
||||
|
||||
|
||||
def test_sportage_alpha_rejects_wrong_identity_word_and_experience():
|
||||
cp = sportage()
|
||||
assert qualified_long(cp)
|
||||
for field, value in (('carFingerprint', CAR.KIA_SELTOS), ('passive', True), ('dashcamOnly', True),
|
||||
('notCar', True), ('pcmCruise', True), ('alternativeExperience', 32)):
|
||||
bad = cp.as_reader().as_builder()
|
||||
setattr(bad, field, value)
|
||||
assert not qualified_long(bad)
|
||||
for word in (0x80d, 0x80f, 0x830, 0x180c, 0x480c):
|
||||
bad = cp.as_reader().as_builder()
|
||||
bad.safetyConfigs[-1].safetyParam = word
|
||||
bad.alternativeExperience = 32
|
||||
assert not qualified_long(bad)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('hybrid', [False, True])
|
||||
@pytest.mark.parametrize('alternate', [False, True])
|
||||
@pytest.mark.parametrize('alpha', [False, True])
|
||||
def test_sportage_lateral_only_uses_active_status_without_changing_normal_commands(hybrid, alternate, alpha):
|
||||
from opendbc.can import CANParser
|
||||
from opendbc.car import Bus
|
||||
from opendbc.car.hyundai.values import DBC
|
||||
|
||||
cp = sportage(hybrid=hybrid, alternate=alternate, alpha=alpha)
|
||||
policy = policy_for(cp)
|
||||
cp.safetyConfigs[-1].safetyParam |= policy.safety_param_addition
|
||||
cp.alternativeExperience |= policy.alternative_experience_addition
|
||||
ci = CarInterface(cp)
|
||||
ci.update([])
|
||||
parser = CANParser(DBC[CAR.KIA_SPORTAGE_5TH_GEN][Bus.pt], [('LFA', 0)], 0)
|
||||
before = cp.to_dict()
|
||||
for enabled, lateral, expected in ((False, False, 1), (False, True, 2), (True, True, 2), (True, False, 2)):
|
||||
cc = structs.CarControl(enabled=enabled, latActive=lateral)
|
||||
stamp = 10_000_000_000 + ci.CC.frame * 10_000_000
|
||||
_, frames = ci.CC.update(cc.as_reader(), ci.CS, stamp)
|
||||
parser.update((stamp, frames))
|
||||
assert parser.vl['LFA']['LKA_SysIndReq'] == expected
|
||||
assert parser.vl['LFA']['Damping_Gain'] == 100
|
||||
assert cp.to_dict() == before
|
||||
@@ -183,7 +183,6 @@ def _policy_for(CP) -> AolVehiclePolicy:
|
||||
explicit_latch=qualified,
|
||||
distance_personality=distance_personality,
|
||||
safety_param_addition=0x0800 if stock else 0,
|
||||
fixed_cruise_buttons=settings,
|
||||
distance_pause_only=settings,
|
||||
paddle_pause=settings,
|
||||
)
|
||||
|
||||
@@ -6,12 +6,12 @@ from opendbc.car.hyundai.values import CAR
|
||||
@dataclass(frozen=True)
|
||||
class WheelSettingsPolicy:
|
||||
settings_supported: bool = True
|
||||
fixed_cruise_buttons: bool = True
|
||||
fixed_cruise_buttons: bool = False
|
||||
explicit_latch: bool = True
|
||||
distance_pause_only: bool = True
|
||||
paddle_pause: bool = True
|
||||
runtime_supported: bool = False
|
||||
lkas_button_supported: bool = False
|
||||
lkas_button_supported: bool = True
|
||||
auxiliary_lkas_supported: bool = False
|
||||
|
||||
|
||||
|
||||
@@ -114,18 +114,18 @@ class TestOffroadConfigurationPages(unittest.TestCase):
|
||||
self.assertTrue(rows[label]['choices'] or rows[label]['step'], (name, label))
|
||||
self.assert_no_car_cache()
|
||||
|
||||
def test_fixed_ioniq6_cruise_buttons_remain_outside_remapping_catalog(self):
|
||||
def test_ioniq6_cruise_buttons_are_in_remapping_catalog(self):
|
||||
from openpilot.starpilot.car.hyundai.settings import configuration_wheel_policy
|
||||
cp = self.context.sample().cp
|
||||
policy = configuration_wheel_policy(cp)
|
||||
assert policy is not None
|
||||
self.assertTrue(policy.fixed_cruise_buttons)
|
||||
self.assertFalse(policy.lkas_button_supported)
|
||||
self.assertFalse(policy.fixed_cruise_buttons)
|
||||
self.assertTrue(policy.lkas_button_supported)
|
||||
self.params.put('LKASButtonControl', 9, block=True)
|
||||
self.params.put('MainCruiseButtonControl', 10, block=True)
|
||||
labels = {row['label'] for row in self.page('wheel')['rows']}
|
||||
self.assertNotIn('LKAS press', labels)
|
||||
self.assertNotIn('Main cruise press', labels)
|
||||
self.assertIn('LKAS press', labels)
|
||||
self.assertIn('Main cruise press', labels)
|
||||
self.assertIn('Distance long press', labels)
|
||||
self.assertEqual(self.params.get('LKASButtonControl'), 9)
|
||||
self.assertEqual(self.params.get('MainCruiseButtonControl'), 10)
|
||||
|
||||
@@ -300,8 +300,7 @@ class LateralFeatureSettingsTests(unittest.TestCase):
|
||||
master = self.row("aol", "AlwaysOnLateral")
|
||||
self.assertTrue(master.available)
|
||||
self.assertIn("independently of cruise control", master.reason)
|
||||
self.assertFalse(any(row.key == "wheel:LKASButtonControl" for row in self.owner.snapshot(
|
||||
"wheel", parked=True, system_long=True, lateral_context=True, metric=False).rows))
|
||||
self.assertIn("Toggle AOL", self.row("wheel", "wheel:LKASButtonControl").choices)
|
||||
nostalgia = self.row("vehicle", "NostalgiaMode")
|
||||
self.assertTrue(nostalgia.available)
|
||||
self.assertTrue(self.owner.apply(required_change(nostalgia)))
|
||||
@@ -316,8 +315,7 @@ class LateralFeatureSettingsTests(unittest.TestCase):
|
||||
tagged.safetyConfigs[0].safetyParam |= 0x800
|
||||
self.cp = tagged
|
||||
self.assertTrue(self.row("aol", "AlwaysOnLateral").available)
|
||||
self.assertFalse(any(row.key == "wheel:LKASButtonControl" for row in self.owner.snapshot(
|
||||
"wheel", parked=True, system_long=True, lateral_context=True, metric=False).rows))
|
||||
self.assertIn("Toggle AOL", self.row("wheel", "wheel:LKASButtonControl").choices)
|
||||
|
||||
def test_ioniq5_left_paddle_setting_uses_existing_long_configuration(self):
|
||||
from openpilot.starpilot.tests.test_nostalgia import _ioniq5_params
|
||||
@@ -429,10 +427,8 @@ class LateralFeatureSettingsTests(unittest.TestCase):
|
||||
stock, long_cp = ioniq6_candidate(False)
|
||||
self.cp = stock
|
||||
self.params.put_bool("AlwaysOnLateral", True, block=True)
|
||||
self.assertFalse(any(row.key == "wheel:LKASButtonControl" for row in self.owner.snapshot(
|
||||
"wheel", parked=True, system_long=True, lateral_context=True, metric=False).rows))
|
||||
self.assertFalse(any(row.key == "wheel:MainCruiseButtonControl" for row in self.owner.snapshot(
|
||||
"wheel", parked=True, system_long=True, lateral_context=True, metric=False).rows))
|
||||
self.assertIn("Toggle AOL", self.row("wheel", "wheel:LKASButtonControl").choices)
|
||||
self.assertIn("Toggle AOL", self.row("wheel", "wheel:MainCruiseButtonControl").choices)
|
||||
cases = (("DistanceButtonControl", "Pause steering", 1, (False, True), True),
|
||||
("LongDistanceButtonControl", "Pause longitudinal", CRUISE_LONG_PRESS, (True, False), True),
|
||||
("VeryLongDistanceButtonControl", "Pause steering", CRUISE_LONG_PRESS * 5, (False, True), True),
|
||||
@@ -491,7 +487,7 @@ class LateralFeatureSettingsTests(unittest.TestCase):
|
||||
def test_ioniq_distance_canonical_repair_and_fixed_gestures(self):
|
||||
self.cp, _ = ioniq6_candidate(False)
|
||||
state = self.owner.snapshot("wheel", parked=True, system_long=True, lateral_context=True, metric=False)
|
||||
self.assertFalse(any(row.key in ("wheel:LKASButtonControl", "wheel:MainCruiseButtonControl") for row in state.rows))
|
||||
self.assertTrue({"wheel:LKASButtonControl", "wheel:MainCruiseButtonControl"} <= {row.key for row in state.rows})
|
||||
key = "DistanceButtonControl"
|
||||
path = Path(self.params.get_param_path(key))
|
||||
for raw in (b"03", b"99", b"bad"):
|
||||
|
||||
@@ -17,7 +17,7 @@ class HyundaiConfigurationSettingsTests(unittest.TestCase):
|
||||
self.assertTrue(configuration_media_supported(cp))
|
||||
policy = configuration_wheel_policy(cp)
|
||||
assert policy is not None
|
||||
self.assertTrue(policy.fixed_cruise_buttons)
|
||||
self.assertFalse(policy.fixed_cruise_buttons)
|
||||
self.assertTrue(policy.distance_pause_only)
|
||||
self.assertFalse(policy.runtime_supported)
|
||||
self.assertEqual(vars(cp), before)
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
from opendbc.car.ford.values import CAR as FORD
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.car.tests.test_hyundai_aol import candidate
|
||||
from openpilot.starpilot.aol.intent import AolSettings
|
||||
from openpilot.starpilot.aol.tests.test_sportage_alpha_aol import sportage
|
||||
from openpilot.starpilot.car.ford.aol import create_intent as ford_intent
|
||||
from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner
|
||||
from openpilot.starpilot.ui.wheel_feature import WheelFeature
|
||||
|
||||
|
||||
def owner(params, cp, *, configuration=False):
|
||||
return SimpleNamespace(params=params, vehicle_params=lambda: cp, configuration_vehicle=lambda: configuration,
|
||||
configuration_longitudinal=lambda: False, longitudinal_available=lambda: cp.openpilotLongitudinalControl,
|
||||
authority=lambda _: True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('alpha', [False, True])
|
||||
@pytest.mark.parametrize('configuration', [False, True])
|
||||
def test_ioniq6_lkas_and_main_are_visible_and_configurable_without_changing_saved_settings(tmp_path, alpha, configuration):
|
||||
params = Params(str(tmp_path))
|
||||
stock, long_cp = candidate(False)
|
||||
cp = long_cp if alpha else stock
|
||||
wheel = WheelFeature(owner(params, cp, configuration=configuration))
|
||||
for key in ('LKASButtonControl', 'MainCruiseButtonControl'):
|
||||
assert 'Toggle AOL' in wheel.choices(key)
|
||||
assert params.get(key) is None
|
||||
assert any(row.key == 'wheel:' + key for row in wheel.rows())
|
||||
|
||||
|
||||
@pytest.mark.parametrize('alpha', [False, True])
|
||||
def test_hkg_default_toggle_and_explicit_mapping_are_displayed_truthfully(tmp_path, alpha):
|
||||
params = Params(str(tmp_path))
|
||||
cp = sportage(alpha=alpha)
|
||||
wheel = WheelFeature(owner(params, cp))
|
||||
rows = {row.key: row for row in wheel.rows()}
|
||||
for key in ('LKASButtonControl', 'MainCruiseButtonControl'):
|
||||
assert rows['wheel:' + key].value == 'Toggle AOL'
|
||||
assert params.get(key) is None
|
||||
params.put('LKASButtonControl', 9, block=True)
|
||||
params.put('MainCruiseButtonControl', 0, block=True)
|
||||
rows = {row.key: row for row in wheel.rows()}
|
||||
assert rows['wheel:LKASButtonControl'].value == 'Toggle AOL'
|
||||
assert rows['wheel:MainCruiseButtonControl'].value == 'Off'
|
||||
assert params.get('LKASButtonControl') == 9
|
||||
assert params.get('MainCruiseButtonControl') == 0
|
||||
|
||||
|
||||
def test_mach_e_fixed_lkas_role_matches_actual_intent_and_cannot_claim_off(tmp_path):
|
||||
params = Params(str(tmp_path))
|
||||
cp = interfaces[FORD.FORD_MUSTANG_MACH_E_MK1].get_non_essential_params(FORD.FORD_MUSTANG_MACH_E_MK1)
|
||||
wheel = WheelFeature(owner(params, cp))
|
||||
rows = {row.key: row for row in wheel.rows()}
|
||||
lkas = rows['wheel:LKASButtonControl']
|
||||
assert lkas.value == 'Toggle AOL' and 'Toggle AOL' in lkas.choices
|
||||
assert lkas.available and 'also toggles AOL' in lkas.reason
|
||||
main = rows['wheel:MainCruiseButtonControl']
|
||||
assert main.value == 'Factory cruise / AOL'
|
||||
assert not main.available and not main.choices
|
||||
for key in ('LKASButtonControl', 'MainCruiseButtonControl'):
|
||||
assert params.get(key) is None
|
||||
intent = ford_intent(cp, AolSettings(True, 0., 0, 0, (0, 0, 0), (0, 0, 0)))
|
||||
state = structs.CarState(canValid=True, gearShifter='drive')
|
||||
state.cruiseState.available = True
|
||||
intent.update(state, now_ns=1, fault_active=False)
|
||||
state.buttonEvents = [structs.CarState.ButtonEvent(type='lkas', pressed=True)]
|
||||
intent.update(state, now_ns=2, fault_active=False)
|
||||
assert intent.allowed_latch
|
||||
|
||||
|
||||
def test_ioniq6_actual_settings_owner_exposes_both_rows(tmp_path):
|
||||
params = Params(str(tmp_path))
|
||||
cp, _ = candidate(False)
|
||||
settings = FeatureSettingsOwner(params, lambda _: True, vehicle_fingerprint=lambda: str(cp.carFingerprint), vehicle_params=lambda: cp)
|
||||
page = settings.snapshot('wheel', parked=True, system_long=True, lateral_context=True, metric=False)
|
||||
rows = {row.key: row for row in page.rows}
|
||||
for key in ('LKASButtonControl', 'MainCruiseButtonControl'):
|
||||
assert 'Toggle AOL' in rows['wheel:' + key].choices
|
||||
@@ -29,13 +29,35 @@ class WheelFeature:
|
||||
return base + (self.owner.longitudinal_available(), "configuration")
|
||||
return base + (True,) if self.owner.configuration_longitudinal() else base
|
||||
|
||||
def _policy(self, cp):
|
||||
policy = policy_for(cp)
|
||||
if self.owner.configuration_vehicle():
|
||||
from openpilot.starpilot.car.hyundai.settings import configuration_wheel_policy
|
||||
policy = configuration_wheel_policy(cp) or policy
|
||||
return policy
|
||||
|
||||
def fixed_role(self, cp, key, policy):
|
||||
if key not in KEYS[:2] or not policy.settings_supported:
|
||||
return None
|
||||
if key == KEYS[0] and (not button_reported(cp, key) or
|
||||
not (policy.lkas_button_supported or policy.auxiliary_lkas_supported)):
|
||||
return None
|
||||
if policy.fixed_cruise_buttons and not (key == KEYS[0] and policy.auxiliary_lkas_supported):
|
||||
return "Toggle AOL" if cp.brand == 'hyundai' else "Factory cruise / AOL"
|
||||
if cp.brand == 'ford' and policy.explicit_latch and policy.runtime_supported and key == KEYS[1]:
|
||||
sources = [read_saved(self.owner.params, name, 8) for name in KEYS[:2]]
|
||||
return "Factory cruise / AOL" if all(ok and value != b'9' for value, ok in sources) else "Factory cruise"
|
||||
return None
|
||||
|
||||
def choices(self, key):
|
||||
cp = self.owner.vehicle_params()
|
||||
if self.capability() is None:
|
||||
return ()
|
||||
if key == PADDLE_KEY:
|
||||
return ("Off", "Bookmark") if paddle_bookmark_available(self.owner.params, cp) else ()
|
||||
policy = policy_for(cp)
|
||||
policy = self._policy(cp)
|
||||
if self.fixed_role(cp, key, policy) is not None:
|
||||
return ()
|
||||
media = ioniq6_media_eligible(cp)
|
||||
if self.owner.configuration_vehicle():
|
||||
from openpilot.starpilot.car.hyundai.settings import configuration_wheel_policy, configuration_media_supported
|
||||
@@ -76,18 +98,39 @@ class WheelFeature:
|
||||
if capability is None:
|
||||
return []
|
||||
rows = []
|
||||
cp = self.owner.vehicle_params()
|
||||
policy = self._policy(cp)
|
||||
for key, label in zip(KEYS, LABELS, strict=True):
|
||||
fixed = self.fixed_role(cp, key, policy)
|
||||
if fixed is not None:
|
||||
raw, _ = read_saved(self.owner.params, key, 8)
|
||||
rows.append(FeatureRow(PREFIX + key, label, fixed, raw, (), available=False,
|
||||
reason="Vehicle-controlled AOL role; this button cannot be reassigned. Saved preferences are unchanged.",
|
||||
capability=capability))
|
||||
continue
|
||||
choices = self.choices(key)
|
||||
if not choices:
|
||||
continue
|
||||
raw, readable = read_saved(self.owner.params, key, 8)
|
||||
number = DEFAULTS.get(key, 0) if raw is None else int(raw) if raw in tuple(str(i).encode() for i in range(15)) else -1
|
||||
selected = next((name for name, code in ACTIONS.items() if code == number), None)
|
||||
implicit_toggle = False
|
||||
if cp.brand == 'hyundai' and policy.explicit_latch and key in KEYS[:2]:
|
||||
from openpilot.starpilot.car.hyundai.aol import independent_cruise_buttons_supported
|
||||
sources = [read_saved(self.owner.params, name, 8) for name in KEYS[:2]]
|
||||
ordinary_actions = (None, *(str(code).encode() for code in ACTIONS.values() if code != 9))
|
||||
implicit_toggle = independent_cruise_buttons_supported(cp) and all(ok and value in ordinary_actions
|
||||
for value, ok in sources)
|
||||
ford_role = cp.brand == 'ford' and policy.explicit_latch and policy.runtime_supported and key == KEYS[0]
|
||||
if implicit_toggle or ford_role and selected == "Off":
|
||||
selected = "Toggle AOL"
|
||||
default = next(label for label, code in ACTIONS.items() if code == DEFAULTS.get(key, 0))
|
||||
rows.append(FeatureRow(PREFIX + key, label, selected or "Invalid saved action", raw, choices,
|
||||
default_value=default if default in choices else None,
|
||||
available=readable and self.owner.authority("preferences"),
|
||||
reason=("Bookmark on release. Available while Left Paddle Longitudinal Pause is Off." if key == PADDLE_KEY else
|
||||
reason=("LKAS also toggles AOL independently of its saved extra action." if ford_role else
|
||||
"Neither LKAS nor MAIN is assigned Toggle AOL, so both retain the default AOL toggle." if implicit_toggle else
|
||||
"Bookmark on release. Available while Left Paddle Longitudinal Pause is Off." if key == PADDLE_KEY else
|
||||
"Axis pause and AOL assignments apply after restarting. Other actions can apply this drive. " +
|
||||
"Long actions fire while held."),
|
||||
capability=capability, repair_value="Off" if selected is None else ""))
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"protected_inventory_sha256": "7a8c76528995c0eadbec1aa432c4c324fad71b1048b8b0e39182d47e6dc99a65", "schema_version": 1}
|
||||
Reference in New Issue
Block a user