Compare commits

...

3 Commits

Author SHA1 Message Date
firestar5683 ac3fb6a4d8 Hyundai: hotfix platform-specific control status 2026-09-10 20:45:47 -05:00
firestar5683 79b791a26b EV6 Hotfix 2026-09-10 11:30:48 -05:00
firestar5683 1c35e376e9 uno mas lil dip 2026-09-08 21:15:28 -05:00
14 changed files with 223 additions and 92 deletions
@@ -10,8 +10,9 @@ from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_steer_an
from opendbc.car.common.conversions import Conversions as CV from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.hyundai import hyundaicanfd, hyundaican from opendbc.car.hyundai import hyundaicanfd, hyundaican
from opendbc.car.hyundai.hyundaicanfd import CanBus from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \ from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, \
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \ CANFD_ANGLE_LONGITUDINAL_CAR, CANFD_RADAR_ECU_KEEPALIVE_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, \
kia_ev6_gt_line_longitudinal_tuning, \
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID
from opendbc.car.interfaces import CarControllerBase from opendbc.car.interfaces import CarControllerBase
from opendbc.car.vehicle_model import VehicleModel from opendbc.car.vehicle_model import VehicleModel
@@ -859,10 +860,9 @@ class CarController(CarControllerBase):
can_sends = [] can_sends = []
lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING lka_steering = self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING
longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False)) persistent_lfa_status_cars = (CAR.HYUNDAI_IONIQ_6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.KIA_EV6)
lfa_status_cars = (CAR.HYUNDAI_IONIQ_6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN)
lfa_longitudinal_active = self.CP.openpilotLongitudinalControl \ lfa_longitudinal_active = self.CP.openpilotLongitudinalControl \
if self.CP.carFingerprint in lfa_status_cars else longitudinal_active if self.CP.carFingerprint in persistent_lfa_status_cars else self.long_active_ecu
lka_steering_long = lka_steering and lfa_longitudinal_active lka_steering_long = lka_steering and lfa_longitudinal_active
ccnc_non_hda2 = self.CP.flags & HyundaiFlags.CCNC and not lka_steering ccnc_non_hda2 = self.CP.flags & HyundaiFlags.CCNC and not lka_steering
use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \ use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \
@@ -889,11 +889,10 @@ class CarController(CarControllerBase):
gear = getattr(getattr(CS, "out", None), "gearShifter", None) gear = getattr(getattr(CS, "out", None), "gearShifter", None)
drive_gear = gear == structs.CarState.GearShifter.drive drive_gear = gear == structs.CarState.GearShifter.drive
if angle_lkas_alt: if angle_lkas_alt and self.CP.carFingerprint != CAR.KIA_SPORTAGE_HEV_2026:
steering_msg_active = bool(steering_msg_active and drive_gear) steering_msg_active = bool(steering_msg_active and drive_gear)
angle_lkas_alt_standstill_handoff = bool(getattr(CS.out, "standstill", False) and not CC.latActive) angle_lkas_alt_standstill_handoff = bool(getattr(CS.out, "standstill", False) and not CC.latActive)
forward_stock_lkas = (self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR or forward_stock_lkas = self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR and angle_lkas_alt and (
self.CP.carFingerprint == CAR.KIA_SPORTAGE_HEV_2026) and angle_lkas_alt and (
angle_lkas_alt_standstill_handoff or not (drive_gear and (CC.latActive or CC.enabled)) angle_lkas_alt_standstill_handoff or not (drive_gear and (CC.latActive or CC.enabled))
) )
preserve_stock_lfa_status = preserve_stock_canfd_lfa_status(self.CP.carFingerprint) preserve_stock_lfa_status = preserve_stock_canfd_lfa_status(self.CP.carFingerprint)
@@ -920,7 +919,7 @@ class CarController(CarControllerBase):
# prevent LFA from activating on LKA steering cars by sending "no lane lines detected" to ADAS ECU # prevent LFA from activating on LKA steering cars by sending "no lane lines detected" to ADAS ECU
suppress_lfa = bool(lka_steering) suppress_lfa = bool(lka_steering)
if angle_lkas_alt: if angle_lkas_alt and self.CP.carFingerprint != CAR.KIA_SPORTAGE_HEV_2026:
suppress_lfa = bool(lka_steering and drive_gear and (CC.latActive or (ccnc_angle_long and CC.enabled))) suppress_lfa = bool(lka_steering and drive_gear and (CC.latActive or (ccnc_angle_long and CC.enabled)))
if self.frame % 5 == 0 and suppress_lfa: if self.frame % 5 == 0 and suppress_lfa:
can_sends.append(hyundaicanfd.create_suppress_lfa(self.packer, self.CAN, CS.lfa_block_msg, can_sends.append(hyundaicanfd.create_suppress_lfa(self.packer, self.CAN, CS.lfa_block_msg,
@@ -995,7 +994,7 @@ class CarController(CarControllerBase):
# The front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat # The front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat
# and stops publishing object tracks when it disappears. # and stops publishing object tracks when it disappears.
radar_heartbeat_step = 1 if ccnc_angle_long else 4 radar_heartbeat_step = 1 if ccnc_angle_long else 4
if self.CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR and self.frame % radar_heartbeat_step == 0: if self.CP.carFingerprint in CANFD_RADAR_ECU_KEEPALIVE_CAR and self.frame % radar_heartbeat_step == 0:
can_sends.append(hyundaicanfd.create_accelerator_brake_alt_spoof(0, self.frame // radar_heartbeat_step, can_sends.append(hyundaicanfd.create_accelerator_brake_alt_spoof(0, self.frame // radar_heartbeat_step,
CS.out.brakePressed, CS.out.gasPressed, CS.out.brakePressed, CS.out.gasPressed,
self.CP.carFingerprint)) self.CP.carFingerprint))
@@ -142,7 +142,21 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
lkas_values["LKAS_ANGLE_ACTIVE"] = 2 if lat_active else 1 lkas_values["LKAS_ANGLE_ACTIVE"] = 2 if lat_active else 1
lkas_values["ADAS_ACIAnglTqRedcGainVal"] = apply_torque if lat_active else 0.0 lkas_values["ADAS_ACIAnglTqRedcGainVal"] = apply_torque if lat_active else 0.0
if angle_lkas_alt: if angle_lkas_alt:
if lat_active: if CP.carFingerprint == CAR.KIA_SPORTAGE_HEV_2026:
lkas_values = {
"LKA_OptUsmSta": 0,
"LKA_SysIndReq": 2 if enabled else 1,
"StrTqReqVal": 0,
"LKA_SysWrn": 0,
"ActToiSta": 0,
"LKA_UsmMod": 0,
"LKA_RcgSta": 3 if lat_active else 0,
"Damping_Gain": 100,
"ADAS_StrAnglReqVal": apply_angle,
"LKAS_ANGLE_ACTIVE": 2 if lat_active else 1,
"ADAS_ACIAnglTqRedcGainVal": apply_torque if lat_active else 0.0,
}
elif lat_active:
lkas_values = { lkas_values = {
"LKA_OptUsmSta": 0, "LKA_OptUsmSta": 0,
"LKA_RcgSta": 3, "LKA_RcgSta": 3,
+11 -9
View File
@@ -7,7 +7,7 @@ from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
CANFD_UNSUPPORTED_LONGITUDINAL_CAR, \ CANFD_UNSUPPORTED_LONGITUDINAL_CAR, \
CANFD_SECURITYACCESS_CAR, \ CANFD_SECURITYACCESS_CAR, \
CANFD_ANGLE_LONGITUDINAL_CAR, \ CANFD_ANGLE_LONGITUDINAL_CAR, \
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \ CANFD_RADAR_ECU_KEEPALIVE_CAR, \
RADAR_LIVE_LONGITUDINAL_CAR, \ RADAR_LIVE_LONGITUDINAL_CAR, \
UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \ UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \
LEGACY_LONGITUDINAL_CAR, \ LEGACY_LONGITUDINAL_CAR, \
@@ -27,6 +27,15 @@ from openpilot.starpilot.common.testing_grounds import testing_ground
ButtonType = structs.CarState.ButtonEvent.Type ButtonType = structs.CarState.ButtonEvent.Type
Ecu = structs.CarParams.Ecu Ecu = structs.CarParams.Ecu
def get_communication_control_request(car_fingerprint):
if car_fingerprint in CANFD_RADAR_ECU_KEEPALIVE_CAR:
return bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX,
uds.MESSAGE_TYPE.NORMAL])
return bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x80 | uds.CONTROL_TYPE.DISABLE_RX_DISABLE_TX,
uds.MESSAGE_TYPE.NORMAL])
# Cancel button can sometimes be ACC pause/resume button, main button can also enable on some cars # Cancel button can sometimes be ACC pause/resume button, main button can also enable on some cars
ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.cancel, ButtonType.mainCruise) ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.cancel, ButtonType.mainCruise)
@@ -353,14 +362,7 @@ class CarInterface(CarInterfaceBase):
params = Params() params = Params()
if communication_control is None: if communication_control is None:
if CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR: communication_control = get_communication_control_request(CP.carFingerprint)
# Don't use 0x80 suppress bit so we can read the ECU response.
# Use ENABLE_RX_DISABLE_TX (0x01) so the ECU can still receive from rear radars for BSM
# while blocking SCC TX.
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL])
else:
# 0x80 silences response for other cars (original behavior)
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x80 | uds.CONTROL_TYPE.DISABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL])
ecu_log(f"=== init() called: opLong={CP.openpilotLongitudinalControl}, flags=0x{CP.flags:x}, safetyParam={CP.safetyConfigs[-1].safetyParam} ===") ecu_log(f"=== init() called: opLong={CP.openpilotLongitudinalControl}, flags=0x{CP.flags:x}, safetyParam={CP.safetyConfigs[-1].safetyParam} ===")
@@ -4,7 +4,7 @@ from types import SimpleNamespace
import pytest import pytest
from opendbc.can import CANPacker, CANParser from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs, uds
from opendbc.car.structs import CarControl, CarParams from opendbc.car.structs import CarControl, CarParams
from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car from opendbc.car.fw_versions import build_fw_dict, match_fw_to_car
from opendbc.car.hyundai.carcontroller import CarController, CANCEL_BUTTON_DELAY_FRAMES, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \ from opendbc.car.hyundai.carcontroller import CarController, CANCEL_BUTTON_DELAY_FRAMES, Ioniq6LongitudinalTuningState, GenesisG90LongitudinalTuningState, \
@@ -24,7 +24,7 @@ from opendbc.car.hyundai.carcontroller import CarController, CANCEL_BUTTON_DELAY
clear_ioniq_6_torque_when_request_inactive clear_ioniq_6_torque_when_request_inactive
from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \ from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \
get_canfd_cruise_available get_canfd_cruise_available
from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX, get_communication_control_request
from opendbc.car.hyundai import hyundaican, hyundaicanfd from opendbc.car.hyundai import hyundaican, hyundaicanfd
from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum
from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \ from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \
@@ -129,6 +129,19 @@ def get_test_toggles() -> SimpleNamespace:
class TestHyundaiFingerprint: class TestHyundaiFingerprint:
@pytest.mark.parametrize("car", [
CAR.HYUNDAI_IONIQ_5,
CAR.KIA_EV6,
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
])
def test_hda2_cars_without_radar_keepalive_use_stock_communication_control(self, car):
stock_request = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL, 0x83, uds.MESSAGE_TYPE.NORMAL])
radar_keepalive_request = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL,
uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX, uds.MESSAGE_TYPE.NORMAL])
assert get_communication_control_request(car) == stock_request
assert get_communication_control_request(CAR.HYUNDAI_IONIQ_6) == radar_keepalive_request
def test_carnival_hev_low_speed_torque_rate_limits(self): def test_carnival_hev_low_speed_torque_rate_limits(self):
CP = CarInterface.get_params(CAR.KIA_CARNIVAL_HEV_4TH_GEN, gen_empty_fingerprint(), [], CP = CarInterface.get_params(CAR.KIA_CARNIVAL_HEV_4TH_GEN, gen_empty_fingerprint(), [],
False, False, False, None) False, False, False, None)
@@ -2552,9 +2565,10 @@ class TestHyundaiFingerprint:
if controller.packer.dbc.addr_to_msg[addr].name in ("LFA", "LKAS")] if controller.packer.dbc.addr_to_msg[addr].name in ("LFA", "LKAS")]
assert steering_names == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)] assert steering_names == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)]
def test_ioniq_6_keeps_lfa_status_when_longitudinal_is_inactive(self): @pytest.mark.parametrize("car", [CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN])
def test_egmp_keeps_lfa_status_when_longitudinal_is_inactive(self, car):
CP = CarParams.new_message() CP = CarParams.new_message()
CP.carFingerprint = CAR.HYUNDAI_IONIQ_6 CP.carFingerprint = car
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING) CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING)
CP.openpilotLongitudinalControl = True CP.openpilotLongitudinalControl = True
@@ -2575,6 +2589,38 @@ class TestHyundaiFingerprint:
cc.hudControl, cs, cc, get_test_toggles(), lka_icon=1, lfa_icon=1) cc.hudControl, cs, cc, get_test_toggles(), lka_icon=1, lfa_icon=1)
assert any(addr == 0x12A for addr, _, _ in msgs) assert any(addr == 0x12A for addr, _, _ in msgs)
@pytest.mark.parametrize(("car", "powertrain_flag"), [
(CAR.HYUNDAI_IONIQ_5, HyundaiFlags.EV),
(CAR.KIA_CARNIVAL_2025, 0),
(CAR.KIA_CARNIVAL_HEV_4TH_GEN, HyundaiFlags.HYBRID),
])
def test_hda2_keeps_lfa_status_while_longitudinal_ecu_is_disabled(self, car, powertrain_flag):
CP = CarParams.new_message()
CP.carFingerprint = car
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.CANFD_LKA_STEERING | powertrain_flag)
CP.openpilotLongitudinalControl = True
controller = CarController(DBC[CP.carFingerprint], CP)
controller.frame = 1
controller.long_active_ecu = True
cc = SimpleNamespace(
enabled=False, latActive=False, longActive=False,
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace(),
)
cs = SimpleNamespace(
stock_lfa_msg=None, stock_lkas_msg=None,
left_blindspot_from_radar=False, right_blindspot_from_radar=False,
out=SimpleNamespace(
brakePressed=False, gasPressed=False,
gearShifter=structs.CarState.GearShifter.park,
),
)
msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False,
cc.hudControl, cs, cc, get_test_toggles(), lka_icon=1, lfa_icon=1)
assert any(addr == 0x12A for addr, _, _ in msgs)
def test_gv70_electrified_longitudinal_uses_hda2_scc_contract(self): def test_gv70_electrified_longitudinal_uses_hda2_scc_contract(self):
CP = CarParams.new_message() CP = CarParams.new_message()
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
@@ -2707,7 +2753,7 @@ class TestHyundaiFingerprint:
assert len([msg for msg in msgs if msg[0] == 0x110]) == expected_lkas_msgs assert len([msg for msg in msgs if msg[0] == 0x110]) == expected_lkas_msgs
@pytest.mark.parametrize("standstill", [False, True]) @pytest.mark.parametrize("standstill", [False, True])
def test_sportage_angle_lkas_alt_forwards_stock_status_when_inactive(self, standstill): def test_sportage_angle_lkas_alt_keeps_status_and_suppression_alive(self, standstill):
CP = CarParams.new_message() CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_SPORTAGE_HEV_2026 CP.carFingerprint = CAR.KIA_SPORTAGE_HEV_2026
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.HYBRID | HyundaiFlags.CANFD_ANGLE_STEERING | CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.HYBRID | HyundaiFlags.CANFD_ANGLE_STEERING |
@@ -2715,16 +2761,67 @@ class TestHyundaiFingerprint:
CP.openpilotLongitudinalControl = False CP.openpilotLongitudinalControl = False
controller = CarController(DBC[CP.carFingerprint], CP) controller = CarController(DBC[CP.carFingerprint], CP)
controller.frame = 5
can_bus = CanBus(CP)
cc = SimpleNamespace(enabled=False, latActive=False, cc = SimpleNamespace(enabled=False, latActive=False,
actuators=SimpleNamespace(longControlState=LongCtrlState.off), actuators=SimpleNamespace(longControlState=LongCtrlState.off),
leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace()) leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace())
cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg={}, lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 32) if i != 7}
lfa_block_msg["COUNTER"] = 0
cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg={}, lfa_block_msg=lfa_block_msg,
out=SimpleNamespace(standstill=standstill, steeringAngleDeg=0.0, out=SimpleNamespace(standstill=standstill, steeringAngleDeg=0.0,
gearShifter=structs.CarState.GearShifter.drive)) gearShifter=structs.CarState.GearShifter.drive))
msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False, cc.hudControl, cs, cc, msgs = controller.create_canfd_msgs(0, False, 0.0, 0.0, 0.0, 0.0, False, cc.hudControl, cs, cc,
get_test_toggles(), lka_icon=1, lfa_icon=1) get_test_toggles(), lka_icon=1, lfa_icon=1)
assert not [msg for msg in msgs if msg[0] in (0x110, 0x12A)] lkas_msgs = [msg for msg in msgs if msg[0] == 0x110]
assert len(lkas_msgs) == 1
assert len([msg for msg in msgs if msg[0] == 0x362]) == 1
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS_ALT", 0)], can_bus.ACAN)
parser.update([(1, lkas_msgs)])
assert parser.can_valid
assert parser.vl["LKAS_ALT"]["LKA_OptUsmSta"] == 0
assert parser.vl["LKAS_ALT"]["LKA_SysIndReq"] == 1
assert parser.vl["LKAS_ALT"]["LKA_RcgSta"] == 0
assert parser.vl["LKAS_ALT"]["Damping_Gain"] == 100
assert parser.vl["LKAS_ALT"]["LKAS_ANGLE_ACTIVE"] == 1
assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == 0.0
def test_sportage_angle_lkas_alt_active_status_matches_vehicle_contract(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_SPORTAGE_HEV_2026
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.HYBRID | HyundaiFlags.CANFD_ANGLE_STEERING |
HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.CANFD_LKA_STEERING_ALT)
CP.openpilotLongitudinalControl = False
controller = CarController(DBC[CP.carFingerprint], CP)
controller.frame = 5
can_bus = CanBus(CP)
cc = SimpleNamespace(enabled=True, latActive=True,
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
leftBlinker=False, rightBlinker=False, hudControl=SimpleNamespace())
lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 32) if i != 7}
lfa_block_msg["COUNTER"] = 0
cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg={}, lfa_block_msg=lfa_block_msg,
out=SimpleNamespace(standstill=False, steeringAngleDeg=10.0,
gearShifter=structs.CarState.GearShifter.drive))
msgs = controller.create_canfd_msgs(0, True, 0.4, 12.0, 0.0, 0.0, False, cc.hudControl, cs, cc,
get_test_toggles(), lka_icon=2, lfa_icon=2)
lkas_msgs = [msg for msg in msgs if msg[0] == 0x110]
assert len(lkas_msgs) == 1
assert len([msg for msg in msgs if msg[0] == 0x362]) == 1
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS_ALT", 0)], can_bus.ACAN)
parser.update([(1, lkas_msgs)])
assert parser.can_valid
assert parser.vl["LKAS_ALT"]["LKA_OptUsmSta"] == 0
assert parser.vl["LKAS_ALT"]["LKA_SysIndReq"] == 2
assert parser.vl["LKAS_ALT"]["LKA_RcgSta"] == 3
assert parser.vl["LKAS_ALT"]["Damping_Gain"] == 100
assert parser.vl["LKAS_ALT"]["LKAS_ANGLE_ACTIVE"] == 2
assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == pytest.approx(0.4)
def test_ev9_inactive_angle_steering_does_not_suppress_stock_lfa(self): def test_ev9_inactive_angle_steering_does_not_suppress_stock_lfa(self):
CP = CarParams.new_message() CP = CarParams.new_message()
@@ -1218,6 +1218,13 @@ CANFD_ALT_BUTTONS_RESUME_CAR = {CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_
CANFD_CORNER_RADAR_BSM_CAR = {CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9} CANFD_CORNER_RADAR_BSM_CAR = {CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9}
CANFD_RADAR_LIVE_LONGITUDINAL_CAR = { CANFD_RADAR_LIVE_LONGITUDINAL_CAR = {
CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_5_PE, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.KIA_EV9, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_IONIQ_5_PE, CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.KIA_EV9, CAR.GENESIS_GV60_EV_1ST_GEN,
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
}
CANFD_RADAR_ECU_KEEPALIVE_CAR = {
CAR.HYUNDAI_IONIQ_5_PE,
CAR.HYUNDAI_IONIQ_6,
CAR.KIA_EV9,
CAR.GENESIS_GV60_EV_1ST_GEN,
} }
RADAR_LIVE_LONGITUDINAL_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR | { RADAR_LIVE_LONGITUDINAL_CAR = CANFD_RADAR_LIVE_LONGITUDINAL_CAR | {
CAR.HYUNDAI_IONIQ, CAR.HYUNDAI_IONIQ,
-7
View File
@@ -404,7 +404,6 @@ class Controls:
self.turn_blinker_swept = 0.0 self.turn_blinker_swept = 0.0
self.twitch_guard_remaining = 0.0 self.twitch_guard_remaining = 0.0
self.kona_non_scc_lateral_active = False self.kona_non_scc_lateral_active = False
self.kona_non_scc_lateral_faulted = False
self.elantra_hev_2024_lateral_faulted = False self.elantra_hev_2024_lateral_faulted = False
self.elantra_hev_2024_previous_cruise_enabled = False self.elantra_hev_2024_previous_cruise_enabled = False
@@ -504,11 +503,6 @@ class Controls:
standstill = abs(CS.vEgo) <= max(self.CP.minSteerSpeed, 0.3) or CS.standstill standstill = abs(CS.vEgo) <= max(self.CP.minSteerSpeed, 0.3) or CS.standstill
if self.CP.carFingerprint == HYUNDAI_CAR.HYUNDAI_KONA_NON_SCC: if self.CP.carFingerprint == HYUNDAI_CAR.HYUNDAI_KONA_NON_SCC:
always_on_lateral_enabled = self.sm['starpilotCarState'].alwaysOnLateralEnabled always_on_lateral_enabled = self.sm['starpilotCarState'].alwaysOnLateralEnabled
lateral_requested = (CC.enabled and self.sm['selfdriveState'].active) or always_on_lateral_enabled
if not lateral_requested:
self.kona_non_scc_lateral_faulted = False
elif CS.steerFaultTemporary:
self.kona_non_scc_lateral_faulted = True
CC.latActive = get_kona_non_scc_lateral_active( CC.latActive = get_kona_non_scc_lateral_active(
CC.enabled, self.sm['selfdriveState'].active, CC.enabled, self.sm['selfdriveState'].active,
always_on_lateral_enabled, always_on_lateral_enabled,
@@ -516,7 +510,6 @@ class Controls:
standstill, self.CP.steerAtStandstill, standstill, self.CP.steerAtStandstill,
self.sm['starpilotPlan'].lateralCheck, self.sm['starpilotPlan'].lateralCheck,
CS.steeringPressed, self.kona_non_scc_lateral_active, CS.steeringPressed, self.kona_non_scc_lateral_active,
self.kona_non_scc_lateral_faulted,
) )
self.kona_non_scc_lateral_active = CC.latActive self.kona_non_scc_lateral_active = CC.latActive
elif self.CP.carFingerprint == HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024: elif self.CP.carFingerprint == HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024:
+2 -3
View File
@@ -89,13 +89,12 @@ def update_lateral_fault_latch(previous_latched: bool, lateral_requested: bool,
def get_kona_non_scc_lateral_active(enabled: bool, active: bool, always_on_lateral_enabled: bool, def get_kona_non_scc_lateral_active(enabled: bool, active: bool, always_on_lateral_enabled: bool,
steer_fault_temporary: bool, steer_fault_permanent: bool, steer_fault_temporary: bool, steer_fault_permanent: bool,
standstill: bool, steer_at_standstill: bool, lateral_check: bool, standstill: bool, steer_at_standstill: bool, lateral_check: bool,
steering_pressed: bool, previous_lateral_active: bool, steering_pressed: bool, previous_lateral_active: bool) -> bool:
steer_fault_latched: bool = False) -> bool:
"""Avoid the Kona EPS torque fault when AOL is enabled over driver steering input.""" """Avoid the Kona EPS torque fault when AOL is enabled over driver steering input."""
lateral_active = get_lateral_active(enabled, active, always_on_lateral_enabled, lateral_active = get_lateral_active(enabled, active, always_on_lateral_enabled,
steer_fault_temporary, steer_fault_permanent, steer_fault_temporary, steer_fault_permanent,
standstill, steer_at_standstill, lateral_check) standstill, steer_at_standstill, lateral_check)
if not lateral_active or steer_fault_latched: if not lateral_active:
return False return False
aol_rising_edge = always_on_lateral_enabled and not enabled and not previous_lateral_active aol_rising_edge = always_on_lateral_enabled and not enabled and not previous_lateral_active
@@ -35,9 +35,15 @@ def test_kona_non_scc_aol_gate_does_not_change_fault_or_normal_lateral_gates():
) )
def test_kona_non_scc_does_not_retry_after_a_latched_temporary_fault(): def test_kona_non_scc_recovers_after_temporary_fault_clears():
assert not get_kona_non_scc_lateral_active( assert not get_kona_non_scc_lateral_active(
False, False, True, False, False, False, False, True, False, False, True, False, False, True, True, False, False, False, True, False, True,
)
assert not get_kona_non_scc_lateral_active(
False, False, True, False, False, False, False, True, True, False,
)
assert get_kona_non_scc_lateral_active(
False, False, True, False, False, False, False, True, False, False,
) )
@@ -1,15 +1,35 @@
import { BluetoothPanel } from "../components/BluetoothPanel.js" import { BluetoothPanel } from "../components/BluetoothPanel.js"
import { WheelControls } from "../components/WheelControls.js"
import { GalaxySection } from "../components/GalaxySection.js" import { GalaxySection } from "../components/GalaxySection.js"
import { GalaxyTabs } from "../components/GalaxyTabs.js"
import { useTabRouting } from "../composables.js"
const TABS = {
bluetooth: "Bluetooth",
controllers: "Controllers",
}
export const Bluetooth = { export const Bluetooth = {
name: "Bluetooth", name: "Bluetooth",
components: { BluetoothPanel, GalaxySection }, components: { BluetoothPanel, WheelControls, GalaxySection, GalaxyTabs },
setup() {
return useTabRouting("/bluetooth", { bluetooth: "bluetooth", controllers: "controllers" })
},
data() { return { TABS } },
template: ` template: `
<div class="gx-view"> <div class="gx-view">
<h2 style="margin-top:0;">Bluetooth</h2> <h2 style="margin-top:0;">Bluetooth</h2>
<GalaxySection title="Bluetooth Devices" icon="bi-bluetooth" :collapsible="false"> <GalaxyTabs :items="TABS" :active="tab" @select="selectTab" />
<BluetoothPanel />
</GalaxySection> <template v-if="tab === 'bluetooth'">
<GalaxySection title="Bluetooth Devices" icon="bi-bluetooth" :collapsible="false">
<BluetoothPanel />
</GalaxySection>
</template>
<template v-else>
<WheelControls />
</template>
</div> </div>
`, `,
} }
@@ -4,11 +4,11 @@ import { PwaInstallSection, isFirestarOrigin } from "../components/PwaInstallSec
const isTunnel = () => isFirestarOrigin() const isTunnel = () => isFirestarOrigin()
function localDeviceUrl(ip) { function localDeviceUrl(ip, route = "/") {
const raw = String(ip || "").trim() const raw = String(ip || "").trim()
if (!raw || raw === "unknown") return "" if (!raw || raw === "unknown") return ""
const host = raw.includes(":") && !raw.startsWith("[") ? `[${raw}]` : raw const host = raw.includes(":") && !raw.startsWith("[") ? `[${raw}]` : raw
return `http://${host}:8082` return `http://${host}:8082/#${route}`
} }
export const Galaxy = { export const Galaxy = {
@@ -28,7 +28,7 @@ export const Galaxy = {
if (this.isTunnel) { if (this.isTunnel) {
try { try {
const status = await api.getDeviceStatus() const status = await api.getDeviceStatus()
this.localUrl = localDeviceUrl(status?.lanIp) this.localUrl = localDeviceUrl(status?.lanIp, "/galaxy")
} catch (e) {} } catch (e) {}
return return
} }
@@ -92,7 +92,10 @@ export const Galaxy = {
<strong>Galaxy Pairing Unavailable via Galaxy</strong> <strong>Galaxy Pairing Unavailable via Galaxy</strong>
<span> <span>
Galaxy pairing requires a direct connection. If you are on the same local network, connect here: Galaxy pairing requires a direct connection. If you are on the same local network, connect here:
<a v-if="localUrl" :href="localUrl" style="color:inherit; font-weight:var(--fw-bold); overflow-wrap:anywhere;">{{ localUrl }}</a> <br />
<a v-if="localUrl" class="gx-btn gx-btn--tonal" :href="localUrl" style="margin-top:var(--sp-3);">
<i class="bi bi-box-arrow-up-right"></i> Open Galaxy Locally
</a>
<span v-else>your device's local IP on port 8082.</span> <span v-else>your device's local IP on port 8082.</span>
</span> </span>
</div> </div>
@@ -52,11 +52,11 @@ function normalizeRoute(r) {
} }
} }
function localDeviceUrl(ip) { function localDeviceUrl(ip, route = "/") {
const raw = String(ip || "").trim() const raw = String(ip || "").trim()
if (!raw || raw === "unknown") return "" if (!raw || raw === "unknown") return ""
const host = raw.includes(":") && !raw.startsWith("[") ? `[${raw}]` : raw const host = raw.includes(":") && !raw.startsWith("[") ? `[${raw}]` : raw
return `http://${host}:8082` return `http://${host}:8082/#${route}`
} }
export const Recordings = { export const Recordings = {
@@ -346,7 +346,7 @@ export const Recordings = {
if (this.onFirestar) { if (this.onFirestar) {
try { try {
const status = await api.getDeviceStatus() const status = await api.getDeviceStatus()
this.localUrl = localDeviceUrl(status?.lanIp) this.localUrl = localDeviceUrl(status?.lanIp, "/recordings")
} catch (e) {} } catch (e) {}
return return
} }
@@ -520,7 +520,10 @@ export const Recordings = {
<GxNotice v-else tone="info" icon="bi-satellite" title="Recordings unavailable via Galaxy"> <GxNotice v-else tone="info" icon="bi-satellite" title="Recordings unavailable via Galaxy">
Recordings are unavailable via Galaxy for bandwidth reasons. If you are on the same local network, connect here: Recordings are unavailable via Galaxy for bandwidth reasons. If you are on the same local network, connect here:
<a v-if="localUrl" :href="localUrl" style="color:inherit; font-weight:var(--fw-bold); overflow-wrap:anywhere;">{{ localUrl }}</a> <br />
<a v-if="localUrl" class="gx-btn gx-btn--tonal" :href="localUrl" style="margin-top:var(--sp-3);">
<i class="bi bi-box-arrow-up-right"></i> Open Recordings Locally
</a>
<span v-else>your device's local IP on port 8082.</span> <span v-else>your device's local IP on port 8082.</span>
</GxNotice> </GxNotice>
</div> </div>
@@ -11,7 +11,7 @@ const TOOLS = [
{ name: "System Tools", link: "/system", icon: "bi-arrow-repeat", desc: "Backup, restore, updates" }, { name: "System Tools", link: "/system", icon: "bi-arrow-repeat", desc: "Backup, restore, updates" },
{ name: "Theme Maker", link: "/theme_maker", icon: "bi-palette-fill", desc: "Customize the look" }, { name: "Theme Maker", link: "/theme_maker", icon: "bi-palette-fill", desc: "Customize the look" },
{ name: "Tuning, Plots & Testing", link: "/tuning", icon: "bi-sign-turn-right", desc: "Steering & speed tuning, live plots, testing grounds" }, { name: "Tuning, Plots & Testing", link: "/tuning", icon: "bi-sign-turn-right", desc: "Steering & speed tuning, live plots, testing grounds" },
{ name: "Vehicle Controls", link: "/vehicle", icon: "bi-car-front", desc: "Controllers & vehicle features" }, { name: "Vehicle Controls", link: "/vehicle", icon: "bi-car-front", desc: "Vehicle features" },
].sort((a, b) => a.name.localeCompare(b.name)) ].sort((a, b) => a.name.localeCompare(b.name))
export const Tools = { export const Tools = {
@@ -1,34 +1,22 @@
import { api, showSnackbar } from "../api.js" import { api, showSnackbar } from "../api.js"
import { navigate, toolHref } from "../store.js" import { navigate, toolHref } from "../store.js"
import { WheelControls } from "../components/WheelControls.js"
import { GalaxySection } from "../components/GalaxySection.js" import { GalaxySection } from "../components/GalaxySection.js"
import { GalaxyTabs } from "../components/GalaxyTabs.js"
import { useTabRouting } from "../composables.js"
const FEATURES = [ const FEATURES = [
{ key: "doors", name: "Lock/Unlock Doors", icon: "bi-door-closed", desc: "Send lock or unlock commands remotely to your vehicle.", embed: "/manage_doors" }, { key: "doors", name: "Lock/Unlock Doors", icon: "bi-door-closed", desc: "Send lock or unlock commands remotely to your vehicle.", embed: "/manage_doors" },
{ key: "tsk", name: "Toyota Security Keys", icon: "bi-key-fill", desc: "Manage and apply security keys for secOC protected devices.", embed: "/manage_tsk" }, { key: "tsk", name: "Toyota Security Keys", icon: "bi-key-fill", desc: "Manage and apply security keys for secOC protected devices.", embed: "/manage_tsk" },
] ]
const TABS = {
controllers: "Controllers",
features: "Vehicle Features",
}
export const Vehicle = { export const Vehicle = {
name: "Vehicle", name: "Vehicle",
components: { WheelControls, GalaxySection, GalaxyTabs }, components: { GalaxySection },
data() { data() {
return { return {
TABS,
features: FEATURES, features: FEATURES,
featureStatus: {}, featureStatus: {},
busy: "", busy: "",
} }
}, },
setup() {
return useTabRouting("/vehicle", { controllers: "controllers", features: "features" })
},
computed: { computed: {
featureList() { return this.features }, featureList() { return this.features },
}, },
@@ -63,30 +51,22 @@ export const Vehicle = {
<div class="gx-view"> <div class="gx-view">
<h2 style="margin-top:0;">Vehicle Controls</h2> <h2 style="margin-top:0;">Vehicle Controls</h2>
<GalaxyTabs :items="TABS" :active="tab" @select="selectTab" /> <GalaxySection title="Vehicle Features" icon="bi-check2-square">
<div style="padding: var(--sp-3); display:grid; gap:8px;">
<template v-if="tab === 'controllers'"> <button v-for="f in featureList" :key="f.key" type="button"
<WheelControls /> class="gx-row" style="width:100%; border:none; background:transparent; color:inherit; cursor:pointer; text-align:left;"
</template> @click="openFeature(f)">
<div class="gx-row__info">
<template v-else> <span class="gx-row__label"><i class="bi" :class="f.icon" style="margin-right:6px; color:var(--primary);"></i>{{ f.name }}</span>
<GalaxySection title="Vehicle Features" icon="bi-check2-square"> <span class="gx-row__desc">{{ f.desc }}</span>
<div style="padding: var(--sp-3); display:grid; gap:8px;"> </div>
<button v-for="f in featureList" :key="f.key" type="button" <span v-if="busy === f.key" class="gx-chip" style="background:var(--surface-variant);">Checking...</span>
class="gx-row" style="width:100%; border:none; background:transparent; color:inherit; cursor:pointer; text-align:left;" <span v-else-if="statusOf(f.key) === 'denied'" class="gx-chip" style="background:var(--error);">Not supported</span>
@click="openFeature(f)"> <i v-else class="bi bi-chevron-right" style="color:var(--text-muted);"></i>
<div class="gx-row__info"> </button>
<span class="gx-row__label"><i class="bi" :class="f.icon" style="margin-right:6px; color:var(--primary);"></i>{{ f.name }}</span> <p style="color:var(--text-muted); margin:0;">These features verify vehicle compatibility when launched.</p>
<span class="gx-row__desc">{{ f.desc }}</span> </div>
</div> </GalaxySection>
<span v-if="busy === f.key" class="gx-chip" style="background:var(--surface-variant);">Checking...</span>
<span v-else-if="statusOf(f.key) === 'denied'" class="gx-chip" style="background:var(--error);">Not supported</span>
<i v-else class="bi bi-chevron-right" style="color:var(--text-muted);"></i>
</button>
<p style="color:var(--text-muted); margin:0;">These features verify vehicle compatibility when launched.</p>
</div>
</GalaxySection>
</template>
</div> </div>
`, `,
} }
@@ -126,8 +126,8 @@ def test_ui_ports_all_tool_views():
assert ep in src, f"{rel} should use api.{ep}" assert ep in src, f"{rel} should use api.{ep}"
vehicle = _read("js/views/Vehicle.js") vehicle = _read("js/views/Vehicle.js")
bluetooth = _read("js/views/Bluetooth.js") bluetooth = _read("js/views/Bluetooth.js")
assert "WheelControls" in vehicle and "BluetoothPanel" not in vehicle and "carFeaturesCheck" in vehicle assert "WheelControls" not in vehicle and "BluetoothPanel" not in vehicle and "carFeaturesCheck" in vehicle
assert "BluetoothPanel" in bluetooth assert "BluetoothPanel" in bluetooth and "WheelControls" in bluetooth
def test_ui_routes_ported_views_natively_no_classic_fallback(): def test_ui_routes_ported_views_natively_no_classic_fallback():
@@ -233,9 +233,13 @@ def test_ui_schema_driven_param_engine_reused():
assert "GalaxyEmbed" not in tuning and 'src="/tuning"' not in tuning, "Tuning must be native, not a classic embed" assert "GalaxyEmbed" not in tuning and 'src="/tuning"' not in tuning, "Tuning must be native, not a classic embed"
assert "LateralTuningPanel" in tuning and "LongitudinalManeuvers" not in tuning assert "LateralTuningPanel" in tuning and "LongitudinalManeuvers" not in tuning
vehicle = _read("js/views/Vehicle.js") vehicle = _read("js/views/Vehicle.js")
bluetooth = _read("js/views/Bluetooth.js")
assert "ParamSections" not in vehicle, "Vehicle must not render redundant toggles" assert "ParamSections" not in vehicle, "Vehicle must not render redundant toggles"
assert "WheelControls" in vehicle and "BluetoothPanel" not in vehicle assert "WheelControls" not in vehicle and "BluetoothPanel" not in vehicle
assert "GalaxySection" in vehicle assert "GalaxySection" in vehicle
assert "WheelControls" in bluetooth and "BluetoothPanel" in bluetooth
assert bluetooth.index('bluetooth: "Bluetooth"') < bluetooth.index('controllers: "Controllers"')
assert 'useTabRouting("/bluetooth"' in bluetooth
engine = _read("js/components/ParamSections.js") engine = _read("js/components/ParamSections.js")
assert "SettingTree" in engine assert "SettingTree" in engine
assert "isSettingVisible" in engine assert "isSettingVisible" in engine
@@ -517,6 +521,10 @@ def test_ui_mobile_polish_regressions():
assert "bandwidth reasons" in recordings and "status?.lanIp" in recordings assert "bandwidth reasons" in recordings and "status?.lanIp" in recordings
assert "status?.lanIp" in galaxy assert "status?.lanIp" in galaxy
assert ':href="localUrl"' in recordings and ':href="localUrl"' in galaxy assert ':href="localUrl"' in recordings and ':href="localUrl"' in galaxy
assert 'localDeviceUrl(status?.lanIp, "/recordings")' in recordings
assert 'localDeviceUrl(status?.lanIp, "/galaxy")' in galaxy
assert "gx-btn gx-btn--tonal" in recordings and "Open Recordings Locally" in recordings
assert "gx-btn gx-btn--tonal" in galaxy and "Open Galaxy Locally" in galaxy
home = _read("js/views/Home.js") home = _read("js/views/Home.js")
home_css = _read("css/home.css") home_css = _read("css/home.css")