Compare commits

..

10 Commits

Author SHA1 Message Date
whoisdomi d9fb4c497b C3X Cooling Curve 2026-09-09 15:21:19 -05:00
Prabhaav Pillai 50e2c21dbd click for home! 2026-09-09 13:22:01 -04:00
Prabhaav Pillai 54c3fb13f3 galaxy banner clarity 2026-09-09 12:56:14 -04:00
Prabhaav Pillai 7f3bd61292 make the theme toggle consistient with back button 2026-09-09 02:44:16 -04:00
firestarsdog dec4a0884a Big Boom 2026-09-09 02:36:46 -04:00
firestarsdog 4edc8ab86a Revert "trying to make firefox less laggy"
This reverts commit b3a14cb48d.
2026-09-09 02:30:05 -04:00
Prabhaav Pillai b3a14cb48d trying to make firefox less laggy 2026-09-09 02:14:29 -04:00
firestarsdog f47322cbee are your fingies fixed 2026-09-09 02:09:10 -04:00
firestarsdog a08065f282 zik try dis 2026-09-09 01:45:06 -04:00
firestar5683 a33bec1ca4 uno mas lil dip 2026-09-08 21:15:21 -05:00
14 changed files with 299 additions and 197 deletions
@@ -10,9 +10,8 @@ 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, \ from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
CANFD_ANGLE_LONGITUDINAL_CAR, CANFD_RADAR_ECU_KEEPALIVE_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, \ CANFD_RADAR_LIVE_LONGITUDINAL_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \
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
@@ -860,9 +859,10 @@ 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
persistent_lfa_status_cars = (CAR.HYUNDAI_IONIQ_6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.KIA_EV6) longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False))
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 persistent_lfa_status_cars else self.long_active_ecu if self.CP.carFingerprint in lfa_status_cars else longitudinal_active
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,10 +889,11 @@ 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 and self.CP.carFingerprint != CAR.KIA_SPORTAGE_HEV_2026: if angle_lkas_alt:
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 and angle_lkas_alt and ( forward_stock_lkas = (self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR or
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)
@@ -919,7 +920,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 and self.CP.carFingerprint != CAR.KIA_SPORTAGE_HEV_2026: if angle_lkas_alt:
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,
@@ -994,7 +995,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_ECU_KEEPALIVE_CAR and self.frame % radar_heartbeat_step == 0: if self.CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_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,21 +142,7 @@ 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 CP.carFingerprint == CAR.KIA_SPORTAGE_HEV_2026: if lat_active:
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,
+9 -11
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_ECU_KEEPALIVE_CAR, \ CANFD_RADAR_LIVE_LONGITUDINAL_CAR, \
RADAR_LIVE_LONGITUDINAL_CAR, \ RADAR_LIVE_LONGITUDINAL_CAR, \
UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \ UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \
LEGACY_LONGITUDINAL_CAR, \ LEGACY_LONGITUDINAL_CAR, \
@@ -27,15 +27,6 @@ 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)
@@ -362,7 +353,14 @@ class CarInterface(CarInterfaceBase):
params = Params() params = Params()
if communication_control is None: if communication_control is None:
communication_control = get_communication_control_request(CP.carFingerprint) if CP.carFingerprint in CANFD_RADAR_LIVE_LONGITUDINAL_CAR:
# 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, uds from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs
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, get_communication_control_request from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX
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,19 +129,6 @@ 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)
@@ -2565,10 +2552,9 @@ 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)]
@pytest.mark.parametrize("car", [CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV6, CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN]) def test_ioniq_6_keeps_lfa_status_when_longitudinal_is_inactive(self):
def test_egmp_keeps_lfa_status_when_longitudinal_is_inactive(self, car):
CP = CarParams.new_message() CP = CarParams.new_message()
CP.carFingerprint = car CP.carFingerprint = CAR.HYUNDAI_IONIQ_6
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
@@ -2589,38 +2575,6 @@ 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
@@ -2753,7 +2707,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_keeps_status_and_suppression_alive(self, standstill): def test_sportage_angle_lkas_alt_forwards_stock_status_when_inactive(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 |
@@ -2761,67 +2715,16 @@ 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())
lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 32) if i != 7} cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg={},
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)
lkas_msgs = [msg for msg in msgs if msg[0] == 0x110] assert not [msg for msg in msgs if msg[0] in (0x110, 0x12A)]
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,13 +1218,6 @@ 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,6 +404,7 @@ 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
@@ -503,6 +504,11 @@ 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,
@@ -510,6 +516,7 @@ 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:
+3 -2
View File
@@ -89,12 +89,13 @@ 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) -> bool: steering_pressed: bool, previous_lateral_active: 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: if not lateral_active or steer_fault_latched:
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,15 +35,9 @@ def test_kona_non_scc_aol_gate_does_not_change_fault_or_normal_lateral_gates():
) )
def test_kona_non_scc_recovers_after_temporary_fault_clears(): def test_kona_non_scc_does_not_retry_after_a_latched_temporary_fault():
assert not get_kona_non_scc_lateral_active( assert not get_kona_non_scc_lateral_active(
False, False, True, True, False, False, False, True, False, True, False, False, True, False, False, False, False, True, False, 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,
) )
@@ -36,6 +36,7 @@
--glass-border-light: rgba(255, 255, 255, 0.2); --glass-border-light: rgba(255, 255, 255, 0.2);
--glass-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.35); --glass-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.35);
--glass-blur: blur(4px); --glass-blur: blur(4px);
--card-scroll-bg: rgba(22, 18, 38, 0.95);
--glass-text: #f3f2f8; --glass-text: #f3f2f8;
--glass-accent: #9d72ff; --glass-accent: #9d72ff;
@@ -105,6 +106,7 @@
--surface-container: rgba(255, 255, 255, 0.75); --surface-container: rgba(255, 255, 255, 0.75);
--surface-container-high: rgba(255, 255, 255, 0.95); --surface-container-high: rgba(255, 255, 255, 0.95);
--surface-container-low: rgba(240, 243, 252, 0.75); --surface-container-low: rgba(240, 243, 252, 0.75);
--card-scroll-bg: rgba(245, 246, 252, 0.96);
--on-surface: #171526; --on-surface: #171526;
--on-surface-variant: #5f5b7d; --on-surface-variant: #5f5b7d;
@@ -186,6 +188,8 @@ body {
pointer-events: none; pointer-events: none;
transform: translateZ(0); transform: translateZ(0);
will-change: transform; will-change: transform;
isolation: isolate;
contain: strict;
} }
#galaxy-bg::before { #galaxy-bg::before {
@@ -204,7 +208,6 @@ body {
opacity: 0.5; opacity: 0.5;
will-change: transform; will-change: transform;
transform: translate3d(var(--gx-para-x), 0, 0); transform: translate3d(var(--gx-para-x), 0, 0);
animation: galaxy-drift-smooth 160s linear infinite;
} }
#galaxy-bg::after { #galaxy-bg::after {
@@ -221,7 +224,6 @@ body {
opacity: 0.6; opacity: 0.6;
will-change: transform; will-change: transform;
transform: translate3d(var(--gx-para-x), 0, 0); transform: translate3d(var(--gx-para-x), 0, 0);
animation: galaxy-drift-smooth 100s linear infinite;
} }
#galaxy-bg .galaxy-hero { #galaxy-bg .galaxy-hero {
@@ -234,11 +236,6 @@ body {
animation: galaxy-twinkle 6s ease-in-out infinite alternate; animation: galaxy-twinkle 6s ease-in-out infinite alternate;
} }
@keyframes galaxy-drift-smooth {
from { transform: translate3d(0, 0, 0); }
to { transform: translate3d(0, -400px, 0); }
}
@keyframes galaxy-twinkle { @keyframes galaxy-twinkle {
0%, 100% { opacity: 0.3; transform: scale(0.9); } 0%, 100% { opacity: 0.3; transform: scale(0.9); }
50% { opacity: 1; transform: scale(1.2); } 50% { opacity: 1; transform: scale(1.2); }
@@ -250,6 +247,11 @@ body {
} }
} }
/* Keep decorative animation out of modal backdrops and drawer content. */
body:has(.gx-scrim, .gx-underlay, .gx-drawer.open) #galaxy-bg .galaxy-hero {
display: none;
}
[data-theme="light"] #galaxy-bg { [data-theme="light"] #galaxy-bg {
background-color: #eef1fb; background-color: #eef1fb;
background-image: background-image:
@@ -306,6 +308,8 @@ ul { list-style: none; margin: 0; padding: 0; }
position: sticky; position: sticky;
top: 0; top: 0;
z-index: var(--z-appbar); z-index: var(--z-appbar);
transform: translateZ(0);
isolation: isolate;
} }
.gx-appbar__pill { .gx-appbar__pill {
@@ -325,7 +329,8 @@ ul { list-style: none; margin: 0; padding: 0; }
transition: background-color var(--motion-fast), border-color var(--motion-fast); transition: background-color var(--motion-fast), border-color var(--motion-fast);
} }
.gx-appbar__back { .gx-appbar .gx-appbar__back,
.gx-appbar .gx-theme-toggle {
background: var(--glass-bg); background: var(--glass-bg);
backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
@@ -339,7 +344,8 @@ ul { list-style: none; margin: 0; padding: 0; }
transition: transform var(--motion-fast), background-color var(--motion-fast); transition: transform var(--motion-fast), background-color var(--motion-fast);
} }
.gx-appbar__back:active { transform: scale(0.92); } .gx-appbar .gx-appbar__back:active,
.gx-appbar .gx-theme-toggle:active { transform: scale(0.92); }
.gx-appbar__title { .gx-appbar__title {
background: linear-gradient(135deg, #8b6cc5 0%, #5ec8c8 55%, #d4789c 100%); background: linear-gradient(135deg, #8b6cc5 0%, #5ec8c8 55%, #d4789c 100%);
@@ -358,6 +364,24 @@ ul { list-style: none; margin: 0; padding: 0; }
vertical-align: middle; vertical-align: middle;
} }
.gx-appbar__home {
display: inline-flex;
align-items: center;
cursor: pointer;
padding: 2px 8px;
border-radius: var(--radius-full);
transition: background-color var(--motion-fast), transform var(--motion-fast);
}
.gx-appbar__home:hover { background-color: var(--glass-bg-hover); }
.gx-appbar__home:active { transform: scale(0.96); }
.gx-appbar__home:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
.gx-appbar__right { .gx-appbar__right {
align-items: center; align-items: center;
display: flex; display: flex;
@@ -411,27 +435,6 @@ ul { list-style: none; margin: 0; padding: 0; }
.gx-back-btn { display: none; } .gx-back-btn { display: none; }
.gx-menu-btn { display: inline-flex; } .gx-menu-btn { display: inline-flex; }
.gx-theme-toggle {
background: var(--glass-bg);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border: 1px solid var(--glass-border);
border-radius: 50%;
box-shadow: var(--glass-shadow);
color: var(--glass-text);
flex: none;
height: var(--touch-target);
width: var(--touch-target);
transition: transform var(--motion-fast), background-color var(--motion-fast), border-color var(--motion-fast);
}
.gx-theme-toggle:active { transform: scale(0.92); }
[data-theme="light"] .gx-theme-toggle {
border: 1.5px solid var(--primary);
box-shadow: 0 2px 8px rgba(120, 73, 232, 0.2);
}
.gx-searchwrap { .gx-searchwrap {
align-items: center; align-items: center;
display: flex; display: flex;
@@ -513,7 +516,10 @@ ul { list-style: none; margin: 0; padding: 0; }
position: fixed; position: fixed;
bottom: calc(18px + env(safe-area-inset-bottom, 0px)); bottom: calc(18px + env(safe-area-inset-bottom, 0px));
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%) translateZ(0);
will-change: transform;
isolation: isolate;
contain: layout style;
z-index: 1000; z-index: 1000;
display: flex; display: flex;
@@ -589,7 +595,37 @@ ul { list-style: none; margin: 0; padding: 0; }
box-shadow: var(--elev-1); box-shadow: var(--elev-1);
margin-bottom: var(--sp-4); margin-bottom: var(--sp-4);
overflow: hidden; overflow: hidden;
transition: transform var(--motion-fast), box-shadow var(--motion-fast); transition: transform var(--motion-fast), box-shadow var(--motion-fast), background-color 0.15s ease-out;
}
/* Surgical content-visibility for top-level non-collapsible cards */
.dh-card:not(.dh-stat),
.gx-content > div > .gx-card {
content-visibility: auto;
contain-intrinsic-size: 280px;
contain-intrinsic-size: auto 280px;
}
.dh-stat {
content-visibility: auto;
contain-intrinsic-size: 110px;
contain-intrinsic-size: auto 110px;
}
/* Disarm containment on collapsible sections and nested cards */
.gx-card:has(.gx-section__body),
.gx-card .gx-card,
.gx-tree-node .gx-card {
content-visibility: visible !important;
contain-intrinsic-size: none !important;
}
/* Velocity-Adaptive Glass: bypass blur on scrolling cards with contrast boost */
body.is-scrolling .gx-card,
body.is-scrolling .gx-tile {
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
background: var(--card-scroll-bg) !important;
} }
.gx-unit-note { .gx-unit-note {
@@ -1459,6 +1495,9 @@ input[type="color"].gx-color {
.gx-fade-enter-active, .gx-fade-leave-active { transition: opacity var(--motion-base); } .gx-fade-enter-active, .gx-fade-leave-active { transition: opacity var(--motion-base); }
.gx-fade-enter-from, .gx-fade-leave-to { opacity: 0; } .gx-fade-enter-from, .gx-fade-leave-to { opacity: 0; }
/* Keep the underlay (and star suppression) until the drawer finishes closing. */
.gx-underlay.gx-fade-leave-active { transition: opacity var(--motion-slow); }
.gx-slide-enter-active, .gx-slide-leave-active { .gx-slide-enter-active, .gx-slide-leave-active {
transition: transform var(--motion-base), opacity var(--motion-base); transition: transform var(--motion-base), opacity var(--motion-base);
} }
@@ -90,6 +90,71 @@ app.mount("#galaxy-app")
initRouter() initRouter()
// Disable card blur during document scrolling.
;(() => {
let timer = null
let scrollEnded = true
const touches = new Set()
const nativeScrollEnd = "onscrollend" in document
const body = document.body
const isModalEvent = (e) => {
const t = e.target
return t instanceof Element && t.closest(".gx-scrim, .gx-sheet, .gx-dialog, .gx-drawer") !== null
}
const setScrolling = (active) => {
if (active) {
if (!body.classList.contains("is-scrolling")) body.classList.add("is-scrolling")
} else if (!touches.size && body.classList.contains("is-scrolling")) {
body.classList.remove("is-scrolling")
}
}
const scheduleRestore = () => {
clearTimeout(timer)
const scrollY = window.scrollY
// Finger release and completion notifications can precede the last movement.
// Keep glass disabled until the scroll position has also settled.
timer = setTimeout(() => {
if (window.scrollY !== scrollY) scheduleRestore()
else setScrolling(false)
}, 120)
}
document.addEventListener("scroll", () => {
scrollEnded = false
setScrolling(true)
clearTimeout(timer)
if (!nativeScrollEnd) scheduleRestore()
}, { passive: true })
document.addEventListener("scrollend", () => {
scrollEnded = true
scheduleRestore()
}, { passive: true })
window.addEventListener("touchstart", (e) => {
if (isModalEvent(e)) return
for (const touch of e.changedTouches) touches.add(touch.identifier)
}, { passive: true })
const releaseTouches = (e) => {
for (const touch of e.changedTouches) touches.delete(touch.identifier)
if (!touches.size && (scrollEnded || !nativeScrollEnd)) scheduleRestore()
}
window.addEventListener("touchend", releaseTouches, { passive: true })
window.addEventListener("touchcancel", releaseTouches, { passive: true })
window.addEventListener("hashchange", () => {
scrollEnded = true
touches.clear()
clearTimeout(timer)
body.classList.remove("is-scrolling")
}, { passive: true })
})()
// Layer 2: Ambient Hero Stars Spawner
;(() => { ;(() => {
const bg = document.getElementById("galaxy-bg") const bg = document.getElementById("galaxy-bg")
if (!bg) return if (!bg) return
@@ -81,6 +81,9 @@ export const AppShell = {
bottomNavTo(item) { bottomNavTo(item) {
navigate(item.link) navigate(item.link)
}, },
goHome() {
navigate("/")
},
isActive(link) { isActive(link) {
return this.activePath === link || (link !== "/" && this.activePath.startsWith(link)) return this.activePath === link || (link !== "/" && this.activePath.startsWith(link))
}, },
@@ -102,7 +105,10 @@ export const AppShell = {
<button type="button" class="gx-icon-btn gx-menu-btn" aria-label="Menu" @click="store.drawerOpen = true"> <button type="button" class="gx-icon-btn gx-menu-btn" aria-label="Menu" @click="store.drawerOpen = true">
<i class="bi bi-list"></i> <i class="bi bi-list"></i>
</button> </button>
<span class="gx-appbar__home" role="button" tabindex="0"
aria-label="Galaxy home" @click="goHome" @keydown.enter="goHome" @keydown.space.prevent="goHome">
<span class="gx-appbar__title">Galaxy</span> <span class="gx-appbar__title">Galaxy</span>
</span>
<div class="gx-searchwrap"> <div class="gx-searchwrap">
<input ref="searchInput" class="gx-search gx-appbar__search" type="search" placeholder="Search toggles..." <input ref="searchInput" class="gx-search gx-appbar__search" type="search" placeholder="Search toggles..."
v-model="search" aria-label="Search toggles" /> v-model="search" aria-label="Search toggles" />
@@ -17,9 +17,9 @@ export const DevModeBanner = {
<i class="bi bi-shield-lock gx-alert__icon"></i> <i class="bi bi-shield-lock gx-alert__icon"></i>
<div class="gx-alert__body"> <div class="gx-alert__body">
<strong>{{ hiddenCount }} advanced setting{{ hiddenCount !== 1 ? "s" : "" }} hidden.</strong> <strong>{{ hiddenCount }} advanced setting{{ hiddenCount !== 1 ? "s" : "" }} hidden.</strong>
<span>Advanced features are tucked away until you enable Developer Mode.</span> <span>Want more advanced features or missing a few toggles? Enable Developer Mode whenever youre ready.</span>
</div> </div>
<button type="button" class="gx-btn gx-btn--tonal" @click="unlock">Enable Developer Mode</button> <button type="button" class="gx-btn gx-btn--tonal" @click="unlock">Go to Developer Tab</button>
</div> </div>
`, `,
} }
@@ -277,7 +277,7 @@ def test_ui_eliminates_slider_toggle_flicker():
def test_ui_developer_mode_banner_offers_unlock(): def test_ui_developer_mode_banner_offers_unlock():
banner = _read("js/components/DevModeBanner.js") banner = _read("js/components/DevModeBanner.js")
assert "Enable Developer Mode" in banner assert "Go to Developer Tab" in banner
assert 'navigate("/settings/developer")' in banner assert 'navigate("/settings/developer")' in banner
assert "advanced setting" in banner assert "advanced setting" in banner
@@ -295,6 +295,8 @@ def test_ui_has_bottom_navigation_and_drawer():
assert "gx-appbar" in shell assert "gx-appbar" in shell
assert "Search toggles" in shell assert "Search toggles" in shell
assert ">Galaxy</span>" in shell assert ">Galaxy</span>" in shell
assert "gx-appbar__home" in shell
assert "goHome" in shell and 'navigate("/")' in shell
def test_ui_search_visible_on_mobile_and_content_full_width(): def test_ui_search_visible_on_mobile_and_content_full_width():
@@ -538,6 +540,110 @@ def test_ui_mobile_polish_regressions():
assert 'tr("Longitudinal Maneuver Mode")' not in c4_developer assert 'tr("Longitudinal Maneuver Mode")' not in c4_developer
@pytest.mark.parametrize("native_scrollend", [False, True])
def test_scroll_coordinator_gesture_lifecycle(native_scrollend):
node = _node_exe()
if node is None:
pytest.skip("no node.js runtime available")
script = r"""
const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const native = process.argv[2] === 'true';
const source = fs.readFileSync(process.argv[1], 'utf8');
const handlers = { document: {}, window: {} };
const classes = new Set();
const timers = new Map();
let nextTimer = 0;
const window = {
scrollY: 0,
addEventListener: (name, fn) => { handlers.window[name] = fn; },
};
class Element {
constructor(modal = false) { this.modal = modal; }
closest() { return this.modal ? this : null; }
}
const document = {
body: { classList: {
contains: k => classes.has(k), add: k => classes.add(k), remove: k => classes.delete(k),
} },
addEventListener: (name, fn) => { handlers.document[name] = fn; },
};
if (native) document.onscrollend = null;
vm.runInNewContext(source.slice(source.indexOf('// Disable card blur'), source.indexOf('// Layer 2')), {
document, Element,
window,
setTimeout: fn => { timers.set(++nextTimer, fn); return nextTimer; },
clearTimeout: id => timers.delete(id),
});
const fire = (scope, name, ids = [], modal = false) => handlers[scope][name]?.({
target: new Element(modal), changedTouches: ids.map(identifier => ({ identifier })),
});
const tick = () => { const pending = [...timers.values()]; timers.clear(); pending.forEach(fn => fn()); };
const active = () => classes.has('is-scrolling');
// Inertial movement can continue between delivered scroll events after release.
fire('window', 'touchstart', [10]);
fire('document', 'scroll');
fire('window', 'touchend', [10]);
if (native) fire('document', 'scrollend');
window.scrollY = 100;
tick();
assert.equal(active(), true, 'finger release must not end momentum scrolling');
window.scrollY = 160;
tick();
assert.equal(active(), true, 'continued movement must keep blur disabled');
tick();
assert.equal(active(), false, 'restore after completion and a stable position');
fire('document', 'scrollend');
fire('window', 'wheel');
assert.equal(active(), false, 'wheel without document movement');
fire('window', 'touchstart', [1, 2]);
fire('document', 'scroll');
fire('window', 'touchend', [1]);
fire('window', 'touchstart', [3], true);
fire('window', 'touchend', [3], true);
tick();
assert.equal(active(), true, 'remaining page finger holds blur disabled');
fire('window', 'touchcancel', [2]);
tick();
assert.equal(active(), native, 'native completion must be authoritative');
fire('document', 'scrollend');
tick();
assert.equal(active(), false);
// More scroll events after a completion signal invalidate its pending restore.
fire('document', 'scroll');
fire('document', 'scrollend');
fire('document', 'scroll');
tick();
assert.equal(active(), native, 'new scrolling cancels the previous native completion');
fire('document', 'scrollend');
tick();
assert.equal(active(), false);
fire('window', 'touchstart', [4]);
fire('document', 'scroll');
fire('document', 'scrollend');
assert.equal(active(), true, 'hold survives completion');
fire('window', 'touchend', [4]);
tick();
assert.equal(active(), false, 'release after completion cannot leave state stuck');
fire('document', 'scroll');
fire('window', 'hashchange');
tick();
assert.equal(active(), false, 'navigation clears pending gesture');
fire('document', 'scroll');
tick();
assert.equal(active(), native, 'fallback only on unsupported browsers');
fire('document', 'scrollend');
tick();
assert.equal(active(), false);
"""
result = subprocess.run(
[node, "-e", script, str(UI_ROOT / "js/app.js"), str(native_scrollend).lower()],
capture_output=True, text=True,
)
assert result.returncode == 0, result.stdout + result.stderr
def _node_exe(): def _node_exe():
candidates = [ candidates = [
shutil.which("node"), shutil.which("node"),
+8 -5
View File
@@ -7,9 +7,12 @@ from openpilot.common.swaglog import cloudlog
from openpilot.common.pid import PIDController from openpilot.common.pid import PIDController
from openpilot.system.hardware import HARDWARE from openpilot.system.hardware import HARDWARE
# raise fan setpoint on tici/tizi to reduce noise # comma 3/3X (tici/tizi) run a more aggressive, cooler-targeting curve than comma 4 (mici)
# after raising LMH threshold in AGNOS 18.1 to prevent CPU throttling IS_MICI = HARDWARE.get_device_type() == "mici"
OFFSET = 0 if HARDWARE.get_device_type() == "mici" else 5 OFFSET = 0 if IS_MICI else -5
K_P = 0 if IS_MICI else 1.0
FF_LOW = 60.0 if IS_MICI else 55.0
FF_HIGH = 100.0 if IS_MICI else 80.0
class BaseFanController(ABC): class BaseFanController(ABC):
@abstractmethod @abstractmethod
@@ -23,7 +26,7 @@ class TiciFanController(BaseFanController):
cloudlog.info("Setting up TICI fan handler") cloudlog.info("Setting up TICI fan handler")
self.last_ignition = False self.last_ignition = False
self.controller = PIDController(k_p=0, k_i=4e-3, rate=(1 / DT_HW)) self.controller = PIDController(k_p=K_P, k_i=4e-3, rate=(1 / DT_HW))
def update(self, cur_temp: float, ignition: bool) -> int: def update(self, cur_temp: float, ignition: bool) -> int:
self.controller.pos_limit = 100 if ignition else 30 self.controller.pos_limit = 100 if ignition else 30
@@ -35,7 +38,7 @@ class TiciFanController(BaseFanController):
error = cur_temp - (75 + OFFSET) error = cur_temp - (75 + OFFSET)
fan_pwr_out = int(self.controller.update( fan_pwr_out = int(self.controller.update(
error=error, error=error,
feedforward=np.interp(cur_temp, [60.0 + OFFSET, 100.0 + OFFSET], [0, 100]) feedforward=np.interp(cur_temp, [FF_LOW, FF_HIGH], [0, 100])
)) ))
self.last_ignition = ignition self.last_ignition = ignition