mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-12 19:33:48 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9fb4c497b | |||
| 50e2c21dbd | |||
| 54c3fb13f3 | |||
| 7f3bd61292 | |||
| dec4a0884a | |||
| 4edc8ab86a | |||
| b3a14cb48d | |||
| f47322cbee | |||
| a08065f282 | |||
| a33bec1ca4 |
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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 you’re 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"),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user