greased lightning

This commit is contained in:
firestar5683
2026-08-18 14:15:11 -05:00
parent 21805a5454
commit 5d9b55adcb
30 changed files with 483 additions and 76 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -238,8 +238,8 @@
|Hyundai|Kona Non-SCC 2019|No Smart Cruise Control (Non-SCC)|[Community](community)|
|Hyundai|Nexo 2021|All|[Upstream](#upstream)|
|Hyundai|Palisade 2020-22|All|[Upstream](#upstream)|
|Hyundai|Palisade 2023-24|HDA2|[Community](#community)|
|Hyundai|Palisade (with HDA II) 2023-24|Highway Driving Assist II|[Upstream](#upstream)|
|Hyundai|Palisade 2023-25|HDA2|[Community](#community)|
|Hyundai|Palisade (with HDA II) 2023-25|Highway Driving Assist II|[Upstream](#upstream)|
|Hyundai|Palisade (without HDA II) 2023-25|Highway Driving Assist|[Upstream](#upstream)|
|Hyundai|Santa Cruz 2022-24|Smart Cruise Control (SCC)|[Upstream](#upstream)|
|Hyundai|Santa Cruz (without HDA II) 2025|Smart Cruise Control (SCC)|[Upstream](#upstream)|
+1 -1
View File
@@ -55,7 +55,7 @@ class CAR(Platforms):
EXTRA_HYUNDAI = ExtraPlatformConfig(
[
CommunityCarDocs("Hyundai Palisade 2023-24", package="HDA2"),
CommunityCarDocs("Hyundai Palisade 2023-25", package="HDA2"),
CommunityCarDocs("Kia Telluride 2023-24", package="HDA2"),
],
)
@@ -721,15 +721,25 @@ class CarController(CarControllerBase):
def create_can_msgs(self, apply_steer_req, apply_torque, torque_fault, set_speed_in_units, accel, stopping, hud_control, actuators, CS, CC, lka_icon, lfa_icon):
can_sends = []
can_canfd_blended = bool(self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED)
blended_hda2 = can_canfd_blended and bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING)
# HUD messages
sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint,
hud_control)
if can_canfd_blended and self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
if blended_hda2:
can_sends.extend(hyundaicanfd.create_steering_messages(
self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque, 0.0,
))
if self.long_active_ecu:
can_sends.extend(hyundaican.create_lkas11_can_canfd_blended(
self.packer, self.frame, self.CP, apply_torque, apply_steer_req,
torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled,
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
left_lane_warning, right_lane_warning, CS.msg_364,
include_alerts=False,
counter_mod=0xF,
))
if self.frame % 5 == 0:
can_sends.append(hyundaicanfd.create_suppress_lfa(
self.packer, self.CAN, CS.lfa_block_msg, False,
@@ -760,13 +770,22 @@ class CarController(CarControllerBase):
can_sends.extend(self._create_can_redneck_button_messages(CS))
if self.long_active_ecu and can_canfd_blended:
can_sends.extend(hyundaican.create_radar_aux_messages(self.packer, self.CAN, self.frame))
if blended_hda2:
can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame, blended_hda2=True))
can_sends.extend(hyundaican.create_radar_aux_messages(self.packer, self.CAN, self.frame, hda2=blended_hda2))
if self.frame % 2 == 0 and self.long_active_ecu:
# TODO: unclear if this is needed
jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0
use_fca = self.CP.flags & HyundaiFlags.USE_FCA.value
if can_canfd_blended:
if blended_hda2:
stopping = stopping and CS.out.vEgoRaw < 0.1
can_sends.extend(hyundaican.create_acc_commands_can_canfd_blended_hda2(
self.packer, CC.enabled, accel, self.accel_last, jerk, int(self.frame / 2), hud_control,
set_speed_in_units, stopping, CC.cruiseControl.override, use_fca, self.CP,
))
self.accel_last = accel
elif can_canfd_blended:
can_sends.extend(hyundaican.create_acc_commands_can_canfd_blended(self.packer, CC.enabled, accel, jerk,
int(self.frame / 2), hud_control,
set_speed_in_units, stopping,
@@ -777,7 +796,7 @@ class CarController(CarControllerBase):
CC.cruiseControl.override, use_fca, self.CP))
# 20 Hz LFA MFA message
if self.frame % 5 == 0 and self.CP.flags & HyundaiFlags.SEND_LFA.value:
if self.frame % 5 == 0 and (self.CP.flags & HyundaiFlags.SEND_LFA.value or (self.long_active_ecu and blended_hda2)):
can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled, self.frame, self.CP, lfa_icon))
# 5 Hz ACC options
+1 -1
View File
@@ -667,7 +667,7 @@ class CarState(CarStateBase):
("CGW2", 5),
("WHL_SPD11", 50),
("SAS11", 100),
("SCC12", 50),
("SCC12", 0 if CP.openpilotLongitudinalControl and CP.flags & HyundaiFlags.CANFD_LKA_STEERING else 50),
("EMS12", 100),
("EMS16", 100),
("LVR12", 100),
+72 -11
View File
@@ -106,7 +106,8 @@ def create_checksum_can_canfd_blended(packer, bus, addr, values):
def create_lkas11_can_canfd_blended(packer, frame, CP, apply_steer, steer_req,
torque_fault, lkas11, sys_warning, sys_state, enabled,
left_lane, right_lane,
left_lane_depart, right_lane_depart, msg_364):
left_lane_depart, right_lane_depart, msg_364,
include_alerts=True, counter_mod=0x10):
bus = CanBus(CP).ECAN
values = {
"CF_Lkas_LdwsActivemode": int(left_lane) + (int(right_lane) << 1),
@@ -116,7 +117,7 @@ def create_lkas11_can_canfd_blended(packer, frame, CP, apply_steer, steer_req,
"CR_Lkas_StrToqReq": apply_steer,
"CF_Lkas_ActToi": steer_req,
"CF_Lkas_ToiFlt": torque_fault,
"CF_Lkas_MsgCount": frame % 0x10,
"CF_Lkas_MsgCount": frame % counter_mod,
"NEW_SIGNAL_1": 0,
"NEW_SIGNAL_5": 100,
}
@@ -130,13 +131,13 @@ def create_lkas11_can_canfd_blended(packer, frame, CP, apply_steer, steer_req,
alerts_364.setdefault("BYTE5", 0)
alerts_364.setdefault("BYTE6", 0)
alerts_364.setdefault("BYTE7", 0)
alerts_364["COUNTER"] = frame % 0x10
alerts_364["COUNTER"] = frame % counter_mod
alerts_364["CHECKSUM"] = create_checksum_can_canfd_blended(packer, bus, "ALERTS_364", alerts_364)
return [
packer.make_can_msg("LKAS11", bus, values),
packer.make_can_msg("ALERTS_364", bus, alerts_364),
]
ret = [packer.make_can_msg("LKAS11", bus, values)]
if include_alerts:
ret.append(packer.make_can_msg("ALERTS_364", bus, alerts_364))
return ret
def create_clu11(packer, frame, clu11, button, CP):
@@ -233,6 +234,56 @@ def create_acc_commands_can_canfd_blended(packer, enabled, accel, upper_jerk, id
return commands
def create_acc_commands_can_canfd_blended_hda2(packer, enabled, accel, accel_last, upper_jerk, idx,
hud_control, set_speed, stopping, long_override, use_fca, CP):
commands = []
bus = CanBus(CP).ECAN
jerk = 5.0
if not enabled or long_override:
accel_raw, accel_value = 0.0, 0.0
else:
accel_raw = accel
accel_value = max(accel_last - jerk / 50.0, min(accel, accel_last + jerk / 50.0))
message_values = [
("SCC11", {
"aReqRaw": accel_raw,
"aReqValue": accel_value,
"JerkUpperLimit": upper_jerk,
"JerkLowerLimit": jerk if enabled else 1.0,
}),
("SCC12", {
"MainMode_ACC": 1,
"ACCMode_Inactive": 0 if enabled else 1,
"TauGapSet": hud_control.leadDistanceBars,
"VSetDis": set_speed,
"ACC_ObjDist": 1,
"ACCMode": 2 if enabled and long_override else 1 if enabled else 0,
"StopReq": 1 if stopping else 0,
}),
("SCC14", {
"ACC_ObjRelSpd": 0,
"ObjValid": 0,
"ObjStatus": 2 if hud_control.leadVisible and enabled else 1 if hud_control.leadVisible else 0,
}),
]
if use_fca and not (CP.flags & HyundaiFlags.CAMERA_SCC):
# These values reproduce the stock status bytes without requesting AEB/FCA actuation.
message_values.append(("FCA11", {
"cr_vsm_deccmd": 255,
"cf_vsm_deccmdact": 0,
}))
for name, values in message_values:
values["COUNTER"] = idx % 0xF
values["CHECKSUM"] = create_checksum_can_canfd_blended(packer, bus, name, values)
commands.append(packer.make_can_msg(name, bus, values))
return commands
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP):
commands = []
@@ -324,17 +375,27 @@ def create_frt_radar_opt(packer):
return packer.make_can_msg("FRT_RADAR11", 0, frt_radar11_values)
def create_radar_aux_messages(packer, CAN, frame):
def create_radar_aux_messages(packer, CAN, frame, hda2=False):
commands = []
for addr, freq, values in (
message_specs = (
("RADAR_0x363", 2, {"FCA_ESA": 1}),
("RADAR_0x398", 5, {"BYTE4": 0x80, "BYTE5": 0x5D}),
("RADAR_0x399", 5, {"BYTE2": 0x02}),
("RADAR_0x39a", 5, {"BYTE7": 0xFF}),
("RADAR_0x39b", 5, {}),
("RADAR_0x39c", 5, {"BYTE5": 0xE0, "BYTE6": 0x79}),
("RADAR_0x43a", 20, {"BYTE2": 0x07}),
) if hda2 else (
("RADAR_0x363", 2, {"FCA_ESA": 1}),
("RADAR_0x398", 5, {"BYTE4": 0x80, "BYTE5": 0x10}),
):
)
for addr, freq, values in message_specs:
if frame % freq != 0:
continue
msg_values = values | {"COUNTER": frame % 0x10}
msg_values = values | {"COUNTER": frame % (0xF if hda2 else 0x10)}
msg_values["CHECKSUM"] = create_checksum_can_canfd_blended(packer, CAN.ECAN, addr, msg_values)
commands.append(packer.make_can_msg(addr, CAN.ECAN, msg_values))
@@ -258,7 +258,7 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
ret = []
if CP.flags & HyundaiFlags.CANFD_LKA_STEERING:
lkas_msg = "LKAS_ALT" if CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT else "LKAS"
if CP.openpilotLongitudinalControl or send_lfa_status:
if (CP.openpilotLongitudinalControl and not CP.flags & HyundaiFlags.CAN_CANFD_BLENDED) or send_lfa_status:
ret.append(packer.make_can_msg("LFA", CAN.ECAN, lfa_values))
if lfa_only:
return ret
@@ -820,7 +820,7 @@ def create_fca_warning_light(packer, CAN, frame):
return ret
def create_adrv_messages(packer, CAN, frame):
def create_adrv_messages(packer, CAN, frame, blended_hda2=False):
# messages needed to car happy after disabling
# the ADAS Driving ECU to do longitudinal control
@@ -830,6 +830,9 @@ def create_adrv_messages(packer, CAN, frame):
}
ret.append(packer.make_can_msg("ADRV_0x51", CAN.ACAN, values))
if blended_hda2:
return ret
ret.extend(create_fca_warning_light(packer, CAN, frame))
if frame % 5 == 0:
@@ -9,6 +9,7 @@ from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
RADAR_LIVE_LONGITUDINAL_CAR, \
UNSUPPORTED_LONGITUDINAL_CAR, HyundaiSafetyFlags, \
LEGACY_LONGITUDINAL_CAR, \
CAN_CANFD_BLENDED_HDA2_LONGITUDINAL_CAR, \
HyundaiStarPilotSafetyFlags, \
hyundai_cancel_button_enables_cruise, \
kia_ev6_gt_line_longitudinal_tuning
@@ -199,7 +200,8 @@ class CarInterface(CarInterfaceBase):
else:
# Shared configuration for non CAN-FD cars
ret.alphaLongitudinalAvailable = candidate not in UNSUPPORTED_LONGITUDINAL_CAR or candidate in LEGACY_LONGITUDINAL_CAR
if ret.flags & HyundaiFlags.CAN_CANFD_BLENDED and ret.flags & HyundaiFlags.CANFD_LKA_STEERING:
if ret.flags & HyundaiFlags.CAN_CANFD_BLENDED and ret.flags & HyundaiFlags.CANFD_LKA_STEERING and \
candidate not in CAN_CANFD_BLENDED_HDA2_LONGITUDINAL_CAR:
ret.alphaLongitudinalAvailable = False
ret.enableBsm = 0x58b in fingerprint[CAN.ECAN]
@@ -549,14 +549,17 @@ class TestHyundaiFingerprint:
assert CP.flags & HyundaiFlags.CAN_CANFD_BLENDED
assert CP.flags & HyundaiFlags.CANFD_LKA_STEERING
assert not CP.alphaLongitudinalAvailable
assert not CP.openpilotLongitudinalControl
assert CP.alphaLongitudinalAvailable
assert CP.openpilotLongitudinalControl
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_CANFD_BLENDED
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CANFD_LKA_STEERING
assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.LONG
assert can_bus.ACAN == 0
assert can_bus.ECAN == 1
assert parsers[Bus.pt].bus == 1
assert parsers[Bus.cam].bus == 2
scc12_state = next(state for state in parsers[Bus.pt].message_states.values() if state.name == "SCC12")
assert scc12_state.frequency == 0
assert CarControllerParams(CP).STEER_MAX == 384
def test_palisade_telluride_hda2_sends_lkas_and_camera_suppression(self):
@@ -587,6 +590,41 @@ class TestHyundaiFingerprint:
assert (0x2A4, 0) in msg_addrs_buses
assert not ({0x340, 0x364} & {addr for addr, _, _ in msgs})
def test_palisade_telluride_hda2_long_sends_complete_support_set(self):
fingerprint = gen_empty_fingerprint()
fingerprint[2][0x50] = 16
fingerprint[1][0x38D] = 8
car_fw = [CarParams.CarFw(ecu=Ecu.adas, fwVersion=b"", address=0x730, brand="hyundai")]
CP = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, fingerprint, car_fw, True, False, False, None)
controller = CarController(DBC[CP.carFingerprint], CP)
controller.frame = 0
hud_control = SimpleNamespace(
visualAlert=CarControl.HUDControl.VisualAlert.none,
leftLaneVisible=True,
rightLaneVisible=True,
leftLaneDepart=False,
rightLaneDepart=False,
leadDistanceBars=3,
leadVisible=True,
)
lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 24) if i != 7}
lfa_block_msg["COUNTER"] = 0
CS = SimpleNamespace(lfa_block_msg=lfa_block_msg, redneck_send_button=Buttons.NONE, lkas11={}, msg_364={},
out=SimpleNamespace(vEgoRaw=5.0))
CC = SimpleNamespace(enabled=True, cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False))
actuators = SimpleNamespace(longControlState=LongCtrlState.pid)
msgs = controller.create_can_msgs(True, 0, False, 42.0, 0.0, False, hud_control, actuators, CS, CC, 2, 2)
msg_addrs_buses = {(addr, bus) for addr, _, bus in msgs}
assert {
(0x50, 0), (0x2A4, 0), (0x51, 0),
(0x340, 1), (0x485, 1), (0x420, 1), (0x421, 1), (0x389, 1), (0x38D, 1),
(0x363, 1), (0x398, 1), (0x399, 1), (0x39A, 1), (0x39B, 1), (0x39C, 1), (0x43A, 1),
} <= msg_addrs_buses
assert (0x364, 1) not in msg_addrs_buses
def test_g70_aol_uses_active_lkas_icon(self):
CP = CarInterface.get_params(CAR.GENESIS_G70_2020, gen_empty_fingerprint(), [], False, False, False, None)
controller = CarController(DBC[CP.carFingerprint], CP)
@@ -2418,6 +2456,46 @@ class TestHyundaiFingerprint:
assert parser.vl["SCC14"]["ObjStatus"] == 1
assert parser.vl["RADAR_0x363"]["FCA_ESA"] == 1
def test_can_canfd_blended_hda2_acc_commands_use_hda2_layout(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.HYUNDAI_PALISADE_2023
CP.flags = int(HyundaiFlags.CAN_CANFD_BLENDED | HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.USE_FCA)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [
("SCC11", 0),
("SCC12", 0),
("SCC14", 0),
("FCA11", 0),
], 1)
msgs = hyundaican.create_acc_commands_can_canfd_blended_hda2(
packer,
enabled=True,
accel=-1.0,
accel_last=0.0,
upper_jerk=2.5,
idx=15,
hud_control=SimpleNamespace(leadDistanceBars=3, leadVisible=True),
set_speed=42,
stopping=False,
long_override=False,
use_fca=True,
CP=CP,
)
parser.update([(1, msgs)])
assert parser.can_valid
assert parser.vl["SCC11"]["aReqRaw"] == pytest.approx(-1.0)
assert parser.vl["SCC11"]["aReqValue"] == pytest.approx(-0.1)
assert parser.vl["SCC11"]["COUNTER"] == 0
assert parser.vl["SCC12"]["VSetDis"] == 42
assert parser.vl["SCC14"]["ObjValid"] == 0
assert parser.vl["SCC14"]["ObjStatus"] == 2
assert parser.vl["FCA11"]["aeb_cmd_act"] == 0
assert parser.vl["FCA11"]["fca_cmd_act"] == 0
assert next(dat for addr, dat, _ in msgs if addr == 0x38D)[4:7] == b"\xC0\x3F\x7F"
def test_can_acc_optional_messages_use_enabled_fca_usm(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.GENESIS_G90
+5 -1
View File
@@ -481,7 +481,7 @@ class CAR(Platforms):
[
HyundaiCarDocs("Hyundai Palisade (without HDA II) 2023-25", "Highway Driving Assist",
car_parts=CarParts.common([CarHarness.hyundai_a])),
HyundaiCarDocs("Hyundai Palisade (with HDA II) 2023-24", "Highway Driving Assist II",
HyundaiCarDocs("Hyundai Palisade (with HDA II) 2023-25", "Highway Driving Assist II",
car_parts=CarParts.common([CarHarness.hyundai_r])),
HyundaiCarDocs("Kia Telluride (without HDA II) 2023-25", "Highway Driving Assist",
car_parts=CarParts.common([CarHarness.hyundai_l])),
@@ -968,6 +968,10 @@ CANCEL_BUTTON_ENABLE_CARS = frozenset({
CAR.HYUNDAI_PALISADE_2023,
})
CAN_CANFD_BLENDED_HDA2_LONGITUDINAL_CAR = frozenset({
CAR.HYUNDAI_PALISADE_2023,
})
KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES = frozenset({
"C4DLC",
})
@@ -1,7 +1,7 @@
import numpy as np
from opendbc.can import CANPacker
from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, structs
from opendbc.car import Bus, DT_CTRL, structs
from opendbc.car.common.filter_simple import FirstOrderFilter
from opendbc.car.lateral import apply_std_steer_angle_limits
from opendbc.car.interfaces import CarControllerBase
@@ -55,7 +55,7 @@ class CarController(CarControllerBase):
CC.longActive and brake_pressure > 0, brake_mode))
if self.frame % 100 == 0:
can_sends.append(make_tester_present_msg(0x707, 0, suppress_response=True))
can_sends.append(nissancan.create_leaf_adas_tester_present())
### STEER ###
steer_hud_alert = 1 if hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw) else 0
+93 -23
View File
@@ -1,6 +1,9 @@
from opendbc.car import get_safety_config, structs, uds
import time
from opendbc.car import get_safety_config, structs
from opendbc.car.disable_ecu import disable_ecu, ecu_log
from opendbc.car.interfaces import CarInterfaceBase
from opendbc.car.isotp_parallel_query import IsoTpParallelQuery
from opendbc.car.nissan.carcontroller import CarController
from opendbc.car.nissan.carstate import CarState
from opendbc.car.nissan.values import CAR, CarControllerParams, NissanSafetyFlags, \
@@ -9,8 +12,22 @@ from opendbc.car.nissan.values import CAR, CarControllerParams, NissanSafetyFlag
LEAF_ADAS_ECU_ADDR = 0x707
LEAF_ADAS_ECU_BUS = 0
LEAF_ADAS_COMMAND_BUS = 1
LEAF_ADAS_COMMAND_ADDRS = frozenset((0x1C3, 0x2B0))
LEAF_2025_SV_PLUS_CAMERA_FW = b'6WK2CDB\x04\x18\x00\x00\x00\x00\x00R=1\x18\x99\x10\x00\x00\x00\x80'
# This Leaf camera uses KWP2000 rather than UDS for session management.
LEAF_KWP_AFTER_SALES_REQUEST = b"\x10\xC0"
LEAF_KWP_AFTER_SALES_RESPONSE = b"\x50\xC0"
LEAF_KWP_DISABLE_NORMAL_TX = b"\x28\x01"
LEAF_KWP_ENABLE_NORMAL_TX = b"\x29\x01"
LEAF_KWP_ENABLE_NORMAL_TX_RESPONSE = b"\x69"
LEAF_KWP_SESSIONS = (
(LEAF_KWP_AFTER_SALES_REQUEST, LEAF_KWP_AFTER_SALES_RESPONSE),
(NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_DIAGNOSTIC_RESPONSE_KWP),
)
def is_leaf_2025_sv_plus_longitudinal(candidate, car_fw):
return candidate == CAR.NISSAN_LEAF and any(
@@ -19,6 +36,62 @@ def is_leaf_2025_sv_plus_longitudinal(candidate, car_fw):
)
def leaf_adas_commands_silent(can_recv, settle_time=0.05, observe_time=0.15):
"""Confirm the stock ADAS command sender stopped while bus 1 is still observable."""
if can_recv is None:
return False
try:
# Let already-published CAN batches age out, then discard everything queued
# before the communication-control response.
time.sleep(settle_time)
can_recv()
saw_adas_bus_traffic = False
deadline = time.monotonic() + observe_time
while time.monotonic() < deadline:
packets = can_recv(wait_for_one=True)
for packet in packets:
for msg in packet:
if msg.src != LEAF_ADAS_COMMAND_BUS:
continue
saw_adas_bus_traffic = True
if msg.address in LEAF_ADAS_COMMAND_ADDRS:
ecu_log(f"Nissan Leaf ADAS TX still active: {hex(msg.address)} on bus {msg.src}")
return False
except Exception as e:
ecu_log(f"Nissan Leaf ADAS TX silence verification exception: {e}")
return False
if not saw_adas_bus_traffic:
ecu_log("Nissan Leaf ADAS TX silence could not be verified: no bus 1 traffic observed")
return saw_adas_bus_traffic
def restore_leaf_adas_tx(can_recv, can_send):
"""Re-enable normal KWP message transmission, trying both safe diagnostic sessions."""
if can_recv is None or can_send is None:
return False
for diag_request, diag_response in LEAF_KWP_SESSIONS:
try:
ecu_log(f"Nissan Leaf ADAS TX restore using KWP session {diag_request.hex()}")
query = IsoTpParallelQuery(
can_send, can_recv, LEAF_ADAS_ECU_BUS, [LEAF_ADAS_ECU_ADDR],
[diag_request, LEAF_KWP_ENABLE_NORMAL_TX],
[diag_response, LEAF_KWP_ENABLE_NORMAL_TX_RESPONSE],
response_offset=NISSAN_RX_OFFSET,
)
if query.get_data(0.2):
ecu_log("Nissan Leaf ADAS normal TX restored")
return True
except Exception as e:
ecu_log(f"Nissan Leaf ADAS TX restore exception: {e}")
ecu_log("Nissan Leaf ADAS normal TX restore was not confirmed")
return False
class CarInterface(CarInterfaceBase):
CarState = CarState
CarController = CarController
@@ -62,39 +135,36 @@ class CarInterface(CarInterfaceBase):
@staticmethod
def init(CP, can_recv, can_send):
if not (CP.openpilotLongitudinalControl and CP.carFingerprint == CAR.NISSAN_LEAF):
if not (CP.openpilotLongitudinalControl and CP.alphaLongitudinalAvailable and CP.carFingerprint == CAR.NISSAN_LEAF):
return
from openpilot.common.params import Params
params = Params()
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL,
uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX,
uds.MESSAGE_TYPE.NORMAL])
ecu_disabled = disable_ecu(can_recv, can_send, bus=LEAF_ADAS_ECU_BUS, addr=LEAF_ADAS_ECU_ADDR,
com_cont_req=communication_control, require_response=True, response_offset=NISSAN_RX_OFFSET)
if not ecu_disabled:
# Nissan firmware queries use the KWP-style default session. Try it after
# standard UDS extended-session control, but still require a positive 0x68 response.
ecu_disabled = False
for diag_request, diag_response in LEAF_KWP_SESSIONS:
ecu_log(f"Nissan Leaf ADAS takeover using KWP session {diag_request.hex()}")
ecu_disabled = disable_ecu(can_recv, can_send, bus=LEAF_ADAS_ECU_BUS, addr=LEAF_ADAS_ECU_ADDR,
com_cont_req=communication_control, require_response=True,
diag_request=NISSAN_DIAGNOSTIC_REQUEST_KWP, diag_response=NISSAN_DIAGNOSTIC_RESPONSE_KWP,
response_offset=NISSAN_RX_OFFSET)
params.put_bool("EcuDisableFailed", not ecu_disabled)
if ecu_disabled:
ecu_log("Nissan Leaf ADAS TX disabled; experimental longitudinal control enabled")
com_cont_req=LEAF_KWP_DISABLE_NORMAL_TX, require_response=True, retry=3,
diag_request=diag_request, diag_response=diag_response, response_offset=NISSAN_RX_OFFSET)
if ecu_disabled:
break
takeover_confirmed = ecu_disabled and leaf_adas_commands_silent(can_recv)
params.put_bool("EcuDisableFailed", not takeover_confirmed)
if takeover_confirmed:
ecu_log("Nissan Leaf ADAS TX disable and command silence confirmed; experimental longitudinal control enabled")
else:
# A response can be lost after the ECU accepts 0x28. Always attempt to
# restore stock transmission before falling back to stock longitudinal.
restore_leaf_adas_tx(can_recv, can_send)
CP.safetyConfigs[-1].safetyParam &= ~NissanSafetyFlags.LONG_CONTROL.value
CP.openpilotLongitudinalControl = False
CP.pcmCruise = True
ecu_log("Nissan Leaf ADAS TX disable failed; falling back to stock longitudinal control")
ecu_log("Nissan Leaf ADAS takeover was not confirmed; falling back to stock longitudinal control")
@staticmethod
def deinit(CP, can_recv, can_send):
if not (CP.openpilotLongitudinalControl and CP.carFingerprint == CAR.NISSAN_LEAF):
if not (CP.openpilotLongitudinalControl and CP.alphaLongitudinalAvailable and CP.carFingerprint == CAR.NISSAN_LEAF):
return
communication_control = bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL,
0x80 | uds.CONTROL_TYPE.ENABLE_RX_ENABLE_TX,
uds.MESSAGE_TYPE.NORMAL])
disable_ecu(can_recv, can_send, bus=LEAF_ADAS_ECU_BUS, addr=LEAF_ADAS_ECU_ADDR,
com_cont_req=communication_control, response_offset=NISSAN_RX_OFFSET)
restore_leaf_adas_tx(can_recv, can_send)
@@ -6,6 +6,11 @@ from opendbc.car.nissan.values import CAR
nissan_checksum = crcmod.mkCrcFun(0x11d, initCrc=0x00, rev=False, xorOut=0xff)
def create_leaf_adas_tester_present():
"""KWP2000 tester-present with a response requested, for the 2025 Leaf ADAS ECU."""
return CanData(0x707, b"\x02\x3E\x01\x00\x00\x00\x00\x00", 0)
def create_accel_command(raw_command, frame, active):
"""Build the Leaf ADAS propulsion/regen request (0x2B0)."""
raw_command = int(raw_command)
@@ -2,9 +2,10 @@ from types import SimpleNamespace
import pytest
from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs, uds
from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs
from opendbc.car.can_definitions import CanData
from opendbc.car.nissan.carstate import CarState
from opendbc.car.nissan.interface import CarInterface, LEAF_2025_SV_PLUS_CAMERA_FW
from opendbc.car.nissan.interface import CarInterface, LEAF_2025_SV_PLUS_CAMERA_FW, leaf_adas_commands_silent, restore_leaf_adas_tx
from opendbc.car.nissan.values import CAR, CarControllerParams, NissanSafetyFlags
@@ -82,7 +83,7 @@ def test_alpha_long_controller_sends_stock_shaped_commands_and_keepalive():
assert can_sends[0x2B0][1].hex() == "ff6090ac5b000e03"
assert can_sends[0x1C3][1].hex() == "000000006400ff27"
assert can_sends[0x707][1].hex() == "023e800000000000"
assert can_sends[0x707][1].hex() == "023e010000000000"
assert all(can_sends[addr][2] == 1 for addr in (0x2B0, 0x1C3))
assert can_sends[0x707][2] == 0
@@ -137,6 +138,8 @@ def test_leaf_ecu_disable_is_strict_and_falls_back(monkeypatch, ecu_disabled):
return ecu_disabled
monkeypatch.setattr("opendbc.car.nissan.interface.disable_ecu", fake_disable_ecu)
monkeypatch.setattr("opendbc.car.nissan.interface.leaf_adas_commands_silent", lambda *_: ecu_disabled)
monkeypatch.setattr("opendbc.car.nissan.interface.restore_leaf_adas_tx", lambda *_: True)
monkeypatch.setattr("opendbc.car.nissan.interface.ecu_log", lambda *_: None)
CarInterface.init(CP, None, None)
@@ -145,9 +148,10 @@ def test_leaf_ecu_disable_is_strict_and_falls_back(monkeypatch, ecu_disabled):
assert calls[0]["bus"] == 0
assert calls[0]["response_offset"] == 0x20
assert calls[0]["require_response"] is True
assert calls[0]["com_cont_req"] == bytes([uds.SERVICE_TYPE.COMMUNICATION_CONTROL,
uds.CONTROL_TYPE.ENABLE_RX_DISABLE_TX,
uds.MESSAGE_TYPE.NORMAL])
assert calls[0]["diag_request"] == b"\x10\xc0"
assert calls[0]["diag_response"] == b"\x50\xc0"
assert calls[0]["com_cont_req"] == b"\x28\x01"
assert calls[0]["retry"] == 3
if not ecu_disabled:
assert calls[1]["diag_request"] == b"\x10\x81"
assert calls[1]["diag_response"] == b"\x50\x81"
@@ -161,9 +165,58 @@ def test_leaf_kwp_session_can_confirm_ecu_disable(monkeypatch):
results = iter((False, True))
monkeypatch.setattr("opendbc.car.nissan.interface.disable_ecu", lambda *args, **kwargs: next(results))
monkeypatch.setattr("opendbc.car.nissan.interface.leaf_adas_commands_silent", lambda *_: True)
monkeypatch.setattr("opendbc.car.nissan.interface.ecu_log", lambda *_: None)
CarInterface.init(CP, None, None)
assert CP.openpilotLongitudinalControl
assert not CP.pcmCruise
assert CP.safetyConfigs[-1].safetyParam & NissanSafetyFlags.LONG_CONTROL
def test_leaf_positive_disable_response_without_command_silence_falls_back(monkeypatch):
CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None)
restore_calls = []
monkeypatch.setattr("opendbc.car.nissan.interface.disable_ecu", lambda *args, **kwargs: True)
monkeypatch.setattr("opendbc.car.nissan.interface.leaf_adas_commands_silent", lambda *_: False)
monkeypatch.setattr("opendbc.car.nissan.interface.restore_leaf_adas_tx", lambda *args: restore_calls.append(args) or True)
monkeypatch.setattr("opendbc.car.nissan.interface.ecu_log", lambda *_: None)
CarInterface.init(CP, None, None)
assert len(restore_calls) == 1
assert not CP.openpilotLongitudinalControl
assert CP.pcmCruise
assert not (CP.safetyConfigs[-1].safetyParam & NissanSafetyFlags.LONG_CONTROL)
def test_leaf_adas_command_silence_requires_live_bus_without_stock_commands(monkeypatch):
monkeypatch.setattr("opendbc.car.nissan.interface.ecu_log", lambda *_: None)
def unrelated_bus_traffic(wait_for_one=False):
return [] if not wait_for_one else [[CanData(0x123, b"\x00", 1)]]
assert leaf_adas_commands_silent(unrelated_bus_traffic, settle_time=0, observe_time=0.001)
def stock_command_traffic(wait_for_one=False):
return [] if not wait_for_one else [[CanData(0x2B0, b"\x00" * 8, 1)]]
assert not leaf_adas_commands_silent(stock_command_traffic, settle_time=0, observe_time=0.001)
assert not leaf_adas_commands_silent(lambda wait_for_one=False: [], settle_time=0, observe_time=0.001)
def test_leaf_adas_restore_uses_kwp_enable_normal_transmission(monkeypatch):
queries = []
class FakeQuery:
def __init__(self, can_send, can_recv, bus, addrs, request, response, response_offset):
queries.append((bus, addrs, request, response, response_offset))
def get_data(self, timeout):
return {(0x707, None): b""}
monkeypatch.setattr("opendbc.car.nissan.interface.IsoTpParallelQuery", FakeQuery)
monkeypatch.setattr("opendbc.car.nissan.interface.ecu_log", lambda *_: None)
assert restore_leaf_adas_tx(lambda **kwargs: [], lambda msgs: None)
assert queries == [(0, [0x707], [b"\x10\xc0", b"\x29\x01"], [b"\x50\xc0", b"\x69"], 0x20)]
+42 -11
View File
@@ -92,13 +92,16 @@ static bool hyundai_legacy = false;
static bool hyundai_can_canfd_blended_hda2 = false;
static bool hyundai_acc_main_on_rx_prev = false;
#define HYUNDAI_CAN_CANFD_BLENDED_HDA2_RX_CHECKS() \
#define HYUNDAI_CAN_CANFD_BLENDED_HDA2_COMMON_RX_CHECKS() \
{.msg = {{0x260, 1, 8, 100U, .max_counter = 3U, .ignore_quality_flag = true}, \
{0x371, 1, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }}}, \
{.msg = {{0x386, 1, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
{.msg = {{0x394, 1, 8, 50U, .max_counter = 7U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
{.msg = {{0x251, 1, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
{.msg = {{0x4F1, 1, 4, 50U, .ignore_checksum = true, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
{.msg = {{0x4F1, 1, 4, 50U, .ignore_checksum = true, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
#define HYUNDAI_CAN_CANFD_BLENDED_HDA2_RX_CHECKS() \
HYUNDAI_CAN_CANFD_BLENDED_HDA2_COMMON_RX_CHECKS() \
HYUNDAI_SCC11_ADDR_CHECK(1) \
HYUNDAI_SCC12_ADDR_CHECK(1, true)
@@ -281,14 +284,21 @@ static bool hyundai_tx_hook(const CANPacket_t *msg) {
bool tx = true;
// FCA11: Block any potential actuation
// FCA11: Block any potential actuation. The blended HDA II layout uses
// different static fields, but its explicit AEB/FCA request bits stay zero.
if (msg->addr == 0x38DU) {
int CR_VSM_DecCmd = msg->data[1];
bool FCA_CmdAct = GET_BIT(msg, 20U);
bool CF_VSM_DecCmdAct = GET_BIT(msg, 31U);
if (hyundai_can_canfd_blended_hda2) {
if (GET_BIT(msg, 16U) || GET_BIT(msg, 19U)) {
tx = false;
}
} else {
int CR_VSM_DecCmd = msg->data[1];
bool FCA_CmdAct = GET_BIT(msg, 20U);
bool CF_VSM_DecCmdAct = GET_BIT(msg, 31U);
if ((CR_VSM_DecCmd != 0) || FCA_CmdAct || CF_VSM_DecCmdAct) {
tx = false;
if ((CR_VSM_DecCmd != 0) || FCA_CmdAct || CF_VSM_DecCmdAct) {
tx = false;
}
}
}
@@ -348,7 +358,7 @@ static bool hyundai_tx_hook(const CANPacket_t *msg) {
}
// UDS: Only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address
if (msg->addr == 0x7D0U) {
if ((msg->addr == 0x7D0U) || (msg->addr == 0x730U)) {
if ((GET_BYTES(msg, 0, 4) != 0x00803E02U) || (GET_BYTES(msg, 4, 4) != 0x0U)) {
tx = false;
}
@@ -399,6 +409,27 @@ static safety_config hyundai_init(uint16_t param) {
{0x2A4, 0, 24, .check_relay = true},
};
static const CanMsg HYUNDAI_CAN_CANFD_BLENDED_HDA2_LONG_TX_MSGS[] = {
{0x50, 0, 16, .check_relay = true},
{0x4F1, 1, 4, .check_relay = false},
{0x2A4, 0, 24, .check_relay = true},
{0x51, 0, 32, .check_relay = false},
{0x730, 1, 8, .check_relay = false},
{0x340, 1, 8, .check_relay = true},
{0x485, 1, 8, .check_relay = true},
{0x420, 1, 8, .check_relay = true},
{0x421, 1, 8, .check_relay = true},
{0x389, 1, 8, .check_relay = true},
{0x38D, 1, 8, .check_relay = false},
{0x363, 1, 8, .check_relay = false},
{0x398, 1, 8, .check_relay = false},
{0x399, 1, 8, .check_relay = false},
{0x39A, 1, 8, .check_relay = false},
{0x39B, 1, 8, .check_relay = false},
{0x39C, 1, 8, .check_relay = false},
{0x43A, 1, 8, .check_relay = false},
};
static const CanMsg HYUNDAI_CAN_CANFD_BLENDED_LONG_TX_MSGS[] = {
{0x340, 0, 8, .check_relay = true},
{0x4F1, 0, 4, .check_relay = false},
@@ -462,10 +493,10 @@ static safety_config hyundai_init(uint16_t param) {
}
if (hyundai_can_canfd_blended_hda2) {
static RxCheck hyundai_can_canfd_blended_hda2_long_rx_checks[] = {
HYUNDAI_CAN_CANFD_BLENDED_HDA2_RX_CHECKS()
HYUNDAI_CAN_CANFD_BLENDED_HDA2_COMMON_RX_CHECKS()
};
SET_RX_CHECKS(hyundai_can_canfd_blended_hda2_long_rx_checks, ret);
SET_TX_MSGS(HYUNDAI_CAN_CANFD_BLENDED_HDA2_TX_MSGS, ret);
SET_TX_MSGS(HYUNDAI_CAN_CANFD_BLENDED_HDA2_LONG_TX_MSGS, ret);
} else if (hyundai_camera_scc) {
if (hyundai_can_refresh_msgs) {
SET_TX_MSGS(HYUNDAI_CAMERA_SCC_LONG_REFRESH_TX_MSGS, ret);
+2 -1
View File
@@ -174,7 +174,8 @@ static bool nissan_tx_hook(const CANPacket_t *msg) {
}
if (nissan_longitudinal && (msg->addr == 0x707U) && (msg->bus == 0U)) {
violation |= (msg->data[0] != 0x02U) || (msg->data[1] != 0x3EU) || (msg->data[2] != 0x80U);
// KWP2000 TesterPresent, response required. The Leaf camera rejects UDS 0x3E80.
violation |= (msg->data[0] != 0x02U) || (msg->data[1] != 0x3EU) || (msg->data[2] != 0x01U);
for (int i = 3; i < 8; i++) {
violation |= msg->data[i] != 0U;
}
@@ -254,6 +254,10 @@ class TestHyundaiCanCanfdBlendedSafety(TestHyundaiSafety):
class TestHyundaiCanCanfdBlendedHda2Safety(unittest.TestCase):
TX_MSGS = [[0x50, 0], [0x4F1, 1], [0x2A4, 0]]
LONG_TX_MSGS = TX_MSGS + [
[0x51, 0], [0x730, 1], [0x340, 1], [0x485, 1], [0x420, 1], [0x421, 1], [0x389, 1], [0x38D, 1],
[0x363, 1], [0x398, 1], [0x399, 1], [0x39A, 1], [0x39B, 1], [0x39C, 1], [0x43A, 1],
]
def setUp(self):
self.packer = CANPackerSafety("hyundai_palisade_2023_generated")
@@ -291,6 +295,52 @@ class TestHyundaiCanCanfdBlendedHda2Safety(unittest.TestCase):
self.assertEqual(-1, self.safety.safety_fwd_hook(2, 0x50))
self.assertEqual(-1, self.safety.safety_fwd_hook(2, 0x2A4))
def test_hda2_longitudinal_support_messages_require_long_flag(self):
flags = HyundaiSafetyFlags.CAN_CANFD_BLENDED | HyundaiSafetyFlags.CANFD_LKA_STEERING | HyundaiSafetyFlags.LONG
self.safety.set_safety_hooks(CarParams.SafetyModel.hyundai, flags)
self.safety.init_tests()
for addr, bus in self.LONG_TX_MSGS:
if addr == 0x50:
msg = self._lkas_msg()
elif addr == 0x420:
msg = self.packer.make_can_msg_panda("SCC11", bus, {"aReqRaw": 0.0, "aReqValue": 0.0})
elif addr == 0x730:
msg = libsafety_py.make_CANPacket(addr, bus, b"\x02\x3E\x80\x00\x00\x00\x00\x00")
else:
length = 32 if addr == 0x51 else 24 if addr == 0x2A4 else 4 if addr == 0x4F1 else 8
msg = common.make_msg(bus, addr, length)
self.assertTrue(self.safety.safety_tx_hook(msg), hex(addr))
self.safety.set_safety_hooks(
CarParams.SafetyModel.hyundai,
HyundaiSafetyFlags.CAN_CANFD_BLENDED | HyundaiSafetyFlags.CANFD_LKA_STEERING,
)
self.safety.init_tests()
for addr, bus in self.LONG_TX_MSGS[len(self.TX_MSGS):]:
length = 32 if addr == 0x51 else 8
self.assertFalse(self.safety.safety_tx_hook(common.make_msg(bus, addr, length)), hex(addr))
def test_hda2_longitudinal_acceleration_and_diagnostics_are_checked(self):
flags = HyundaiSafetyFlags.CAN_CANFD_BLENDED | HyundaiSafetyFlags.CANFD_LKA_STEERING | HyundaiSafetyFlags.LONG
self.safety.set_safety_hooks(CarParams.SafetyModel.hyundai, flags)
self.safety.init_tests()
self.safety.set_controls_allowed(True)
for accel, allowed in ((-3.5, True), (3.5, True), (-3.51, False), (3.51, False)):
msg = self.packer.make_can_msg_panda("SCC11", 1, {"aReqRaw": accel, "aReqValue": accel})
self.assertEqual(allowed, self.safety.safety_tx_hook(msg))
valid_tester = libsafety_py.make_CANPacket(0x730, 1, b"\x02\x3E\x80\x00\x00\x00\x00\x00")
invalid_tester = libsafety_py.make_CANPacket(0x730, 1, b"\x03\x28\x83\x01\x00\x00\x00\x00")
self.assertTrue(self.safety.safety_tx_hook(valid_tester))
self.assertFalse(self.safety.safety_tx_hook(invalid_tester))
fca_status = self.packer.make_can_msg_panda("FCA11", 1, {"cr_vsm_deccmd": 255, "cf_vsm_deccmdact": 0})
fca_request = self.packer.make_can_msg_panda("FCA11", 1, {"aeb_cmd_act": 1})
self.assertTrue(self.safety.safety_tx_hook(fca_status))
self.assertFalse(self.safety.safety_tx_hook(fca_request))
class TestHyundaiSafetyFCEV(TestHyundaiSafety):
def setUp(self):
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
import unittest
from opendbc.car import make_tester_present_msg
from opendbc.car.nissan import nissancan
from opendbc.car.nissan.values import NissanSafetyFlags
from opendbc.car.structs import CarParams
@@ -261,7 +260,7 @@ class TestNissanLeafLongSafety(TestNissanLeafSafety):
self.assertTrue(self._tx(self._brake_msg(0, active=False, brake_mode=False)))
def test_tester_present(self):
tester_present = make_tester_present_msg(0x707, 0, suppress_response=True)
tester_present = nissancan.create_leaf_adas_tester_present()
self.assertTrue(self._tx(self._make_msg(tester_present)))
for index in range(8):
+1 -1
View File
@@ -1,2 +1,2 @@
extern const uint8_t gitversion[19];
const uint8_t gitversion[19] = "DEV-50e1c1d3-DEBUG";
const uint8_t gitversion[19] = "DEV-21805a54-DEBUG";
+1 -1
View File
@@ -1 +1 @@
DEV-50e1c1d3-DEBUG
DEV-21805a54-DEBUG
Binary file not shown.
@@ -11,7 +11,7 @@ import { MapsManager } from "/assets/components/tools/maps.js"
import { NavDestination } from "/assets/components/navigation/navigation_destination.js?v=nav-search-context-2"
import { NavKeys } from "/assets/components/navigation/navigation_keys.js?v=app-keys-session-1"
import { RouteRecordings } from "/assets/components/recordings/dashcam_routes.js"
import { SettingsView } from "/assets/components/settings.js"
import { SettingsView } from "/assets/components/settings.js?v=router-cycle-fix-1"
import { ScreenRecordings } from "/assets/components/recordings/screen_recordings.js"
import { Sidebar } from "/assets/components/sidebar.js?v=lateral-tuning-1"
import { SentryMode } from "/assets/components/tools/sentry.js"
@@ -1,6 +1,14 @@
import { html } from "/assets/vendor/arrow-core.js"
import { upperFirst } from "/assets/js/utils.js"
import { Navigate } from "/assets/components/router.js"
function navigate(href) {
// Avoid importing router.js here: settings.js is loaded by router.js, and
// a cache-busted router URL would otherwise create a second router module.
if (typeof window.__theGalaxyNavigate === "function") {
return window.__theGalaxyNavigate(href)
}
window.location.assign(href)
}
export function SettingsView({ params }) {
const state = {
@@ -15,7 +23,7 @@ export function SettingsView({ params }) {
const setting = results.find((item) => item.key === state.selectedSubsection) ?? {}
if (!setting.subsettings || Object.keys(setting.subsettings).length === 0) {
return Navigate(`/settings/${state.selectedSection}`)
return navigate(`/settings/${state.selectedSection}`)
}
state.heading = setting.key
@@ -48,7 +48,7 @@
<link rel="stylesheet" href="/assets/components/tools/tsk_manager.css">
<script type="module">
import("/assets/components/router.js?v=favorite-c4-hint-1").catch((err) => {
import("/assets/components/router.js?v=router-cycle-fix-1").catch((err) => {
console.error("[the_galaxy] bootstrap failed", err);
const target = document.getElementById("app") || document.body;
const pre = document.createElement("pre");
@@ -0,0 +1,22 @@
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[4]
SETTINGS_PATH = REPO_ROOT / "starpilot/system/the_galaxy/assets/components/settings.js"
ROUTER_PATH = REPO_ROOT / "starpilot/system/the_galaxy/assets/components/router.js"
INDEX_PATH = REPO_ROOT / "starpilot/system/the_galaxy/templates/index.html"
def test_settings_does_not_create_a_second_router_module():
source = SETTINGS_PATH.read_text(encoding="utf-8")
assert "/assets/components/router.js" not in source
assert "window.__theGalaxyNavigate" in source
def test_router_and_settings_cache_bust_is_consistent():
router = ROUTER_PATH.read_text(encoding="utf-8")
index = INDEX_PATH.read_text(encoding="utf-8")
assert "/assets/components/settings.js?v=router-cycle-fix-1" in router
assert "/assets/components/router.js?v=router-cycle-fix-1" in index
@@ -4464,6 +4464,7 @@ def setup(app):
"/assets/components/router.js",
"/assets/components/sentry_notifications.js",
"/assets/js/utils.js",
"/assets/components/settings.js",
"/assets/components/home/home.js",
"/assets/components/home/home.css",
"/assets/components/tools/device_settings.js",