estate sale

This commit is contained in:
firestar5683
2026-08-28 13:45:11 -05:00
parent 11b2987c50
commit ebb096a956
25 changed files with 492 additions and 110 deletions
+74 -2
View File
@@ -209,7 +209,79 @@ class CarState(CarStateBase):
def get_can_parsers(CP):
gps_config = get_car_gps_config(CP)
gps_messages = [(name, 0) for name in gps_config.messages] if gps_config is not None else []
pt_messages = [
("BrakeSysFeatures", 50),
("Yaw_Data_FD1", 100),
("DesiredTorqBrk", 50),
("EngVehicleSpThrottle", 100),
("EngVehicleSpThrottle2", 50),
("BrakeSnData_4", 50),
("EngBrakeData", 10),
("EPAS_INFO", 50),
("Cluster_Info1_FD1", 10),
("Steering_Data_FD1", 10),
("BodyInfo_3_FD1", 2),
("RCMStatusMessage2_FD1", 10),
("BCM_Lamp_Stat_FD1", 0),
*gps_messages,
]
if CP.flags & FordFlags.ALT_STEER_ANGLE:
pt_messages += [
("SteeringPinion_Data_Alt", 100),
("ParkAid_Data", 50),
]
else:
pt_messages += [("SteeringPinion_Data", 100)]
if CP.flags & FordFlags.CANFD:
pt_messages += [
("Lane_Assist_Data3_FD1", 33),
("Cluster_Info_3_FD1", 10),
]
else:
pt_messages += [("INSTRUMENT_PANEL", 1)]
if CP.transmissionType == TransmissionType.automatic:
if CP.flags & FordFlags.CANFD:
pt_messages += [("Gear_Shift_by_Wire_FD1", 10)]
elif CP.flags & FordFlags.ALT_STEER_ANGLE:
pt_messages += [("TransGearData", 10)]
else:
pt_messages += [("PowertrainData_10", 10)]
if CP.enableBsm and not (CP.flags & FordFlags.CANFD):
pt_messages += [
("Side_Detect_L_Stat", 5),
("Side_Detect_R_Stat", 5),
]
cam_messages = [
("ACCDATA", 50),
("ACCDATA_2", 50),
("ACCDATA_3", 5),
("IPMA_Data", 1),
]
if CP.flags & FordFlags.CANFD:
cam_messages += [
("Traffic_RecognitnData", 1),
("IPMA_Data2", 1),
]
else:
cam_messages += [("Traffic_RecognitnData", 0)]
if CP.enableBsm and CP.flags & FordFlags.CANFD:
cam_messages += [
("Side_Detect_L_Stat", 5),
("Side_Detect_R_Stat", 5),
]
if CP.flags & FordFlags.LKA_STEERING:
cam_messages += [("LateralMotionControl", 20)]
return {
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], gps_messages, CanBus(CP).main),
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CanBus(CP).camera),
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], pt_messages, CanBus(CP).main),
Bus.cam: CANParser(DBC[CP.carFingerprint][Bus.pt], cam_messages, CanBus(CP).camera),
}
@@ -4,6 +4,7 @@ from types import SimpleNamespace
from hypothesis import settings, given, strategies as st
from parameterized import parameterized
import pytest
from opendbc.car import Bus, gen_empty_fingerprint
from opendbc.can import CANPacker
@@ -274,6 +275,23 @@ def test_mach_e_can_gps_messages_are_optional_main_bus_inputs():
assert all(parser.message_states[address].ignore_alive for address in (0x462, 0x463, 0x464))
def test_lightning_low_rate_camera_messages_use_declared_frequencies():
cp = CarInterface.get_params(CAR.FORD_F_150_LIGHTNING_MK1, gen_empty_fingerprint(), [], True, False, False, None)
cp.enableBsm = True
parser = CarInterface.CarState.get_can_parsers(cp)[Bus.cam]
expected_frequencies = {
"IPMA_Data": 1,
"Traffic_RecognitnData": 1,
"Side_Detect_L_Stat": 5,
"Side_Detect_R_Stat": 5,
}
for message, frequency in expected_frequencies.items():
state = parser.message_states[parser.dbc.name_to_msg[message].address]
assert state.frequency == frequency
assert state.timeout_threshold == pytest.approx(10e9 / frequency)
def test_hands_free_cluster_status_is_opt_in():
packer = CANPacker("ford_lincoln_base_pt")
CAN = SimpleNamespace(main=0)
@@ -111,6 +111,7 @@ class HyundaiSafetyFlags(IntFlag):
class HyundaiStarPilotSafetyFlags(IntFlag):
AOL_MAIN_LKAS_ON_ENGAGE = 128
AOL_MAIN_LKAS_SYNC = 32
HAS_LDA_BUTTON = 1024
AOL_LKAS_ON_ENGAGE = 2048
+1
View File
@@ -260,6 +260,7 @@ class CarInterfaceBase(ABC):
if candidate == HYUNDAI.HYUNDAI_ELANTRA_HEV_2024 and \
getattr(starpilot_toggles, "always_on_lateral_main", False):
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
fp_ret.safetyConfigs[-1].safetyParam |= HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value
# LKASButtonControl == 9 means BUTTON_FUNCTIONS["AOL_TOGGLE"] in starpilot_variables.
if params.get_bool("AlwaysOnLateral") and params.get_int("LKASButtonControl") == 9:
@@ -22,6 +22,7 @@ _LEGACY_2025_REENGAGE_MAX_STEER_RATE = 2.0
_LEGACY_2025_REENGAGE_MAX_ANGLE_DELTA = 1.0
_LEGACY_2025_RECLAIM_FRAMES = 36
_LEGACY_2025_RECLAIM_EXPONENT = 2.5
_ANGLE_OVERRIDE_CONFIRM_FRAMES = 2
_ANGLE_OVERRIDE_HOLD_FRAMES = 10
_ANGLE_REENGAGE_SETTLE_FRAMES = 8
_ANGLE_REENGAGE_MAX_STEER_RATE = 2.0
@@ -47,6 +48,7 @@ class CarController(CarControllerBase):
self.apply_torque_last = 0
self.apply_steer_last = 0
self.driver_override = False
self.angle_override_confirm_frames = 0
self.legacy_2025_lkas_active = False
self.legacy_2025_handoff_active = False
self.legacy_2025_override_hold_frames = 0
@@ -139,6 +141,8 @@ class CarController(CarControllerBase):
return msg
def _reset_legacy_2025_handoff(self):
self.driver_override = False
self.angle_override_confirm_frames = 0
self.legacy_2025_handoff_active = False
self.legacy_2025_override_hold_frames = 0
self.legacy_2025_reengage_settle_frames = 0
@@ -151,7 +155,8 @@ class CarController(CarControllerBase):
self._reset_legacy_2025_handoff()
return False
if getattr(CS.out, "steeringPressed", False):
driver_override = self._update_angle_driver_override(CS)
if driver_override:
self.legacy_2025_handoff_active = True
self.legacy_2025_override_hold_frames = _LEGACY_2025_OVERRIDE_HOLD_FRAMES
self.legacy_2025_reengage_settle_frames = 0
@@ -202,6 +207,8 @@ class CarController(CarControllerBase):
return target_angle
def _reset_angle_handoff(self):
self.driver_override = False
self.angle_override_confirm_frames = 0
self.angle_handoff_active = False
self.angle_override_hold_frames = 0
self.angle_reengage_settle_frames = 0
@@ -214,7 +221,8 @@ class CarController(CarControllerBase):
self._reset_angle_handoff()
return False
if getattr(CS.out, "steeringPressed", False):
driver_override = self._update_angle_driver_override(CS)
if driver_override:
self.angle_handoff_active = True
self.angle_override_hold_frames = _ANGLE_OVERRIDE_HOLD_FRAMES
self.angle_reengage_settle_frames = 0
@@ -253,6 +261,22 @@ class CarController(CarControllerBase):
self.angle_reclaim_start_angle = CS.out.steeringAngleDeg
return True
def _update_angle_driver_override(self, CS):
"""Debounce the higher-confidence raw torque override signal for angle cars."""
abs_torque = abs(getattr(CS.out, "steeringTorque", 0.0))
if self.driver_override:
if abs_torque < self.p.STEER_OVERRIDE_TORQUE_LOW:
self.driver_override = False
elif abs_torque > self.p.STEER_OVERRIDE_TORQUE_HIGH:
self.angle_override_confirm_frames += 1
if self.angle_override_confirm_frames >= _ANGLE_OVERRIDE_CONFIRM_FRAMES:
self.driver_override = True
self.angle_override_confirm_frames = 0
else:
self.angle_override_confirm_frames = 0
return self.driver_override
def _angle_reclaim_target(self, target_angle):
if self.angle_reclaim_frames <= 0:
return target_angle
@@ -325,12 +349,6 @@ class CarController(CarControllerBase):
self.angle_lkas_active = lkas_active
return subarucan.create_steering_control_angle(self.packer, apply_steer, lkas_active, self.angle_bus)
abs_torque = abs(CS.out.steeringTorque)
if abs_torque > self.p.STEER_OVERRIDE_TORQUE_HIGH:
self.driver_override = True
elif abs_torque < self.p.STEER_OVERRIDE_TORQUE_LOW:
self.driver_override = False
mads_only = CC.latActive and not getattr(CC, "enabled", False)
mads_only_ok = CS.out.vEgoRaw > _ANGLE_MADS_MIN_SPEED and \
abs(CS.out.steeringAngleDeg) < _ANGLE_MADS_MAX_STEER_ANGLE
@@ -351,6 +351,7 @@ def test_legacy_2025_waits_for_manual_steering_to_settle_before_reengaging():
vEgoRaw=6.2,
steeringAngleDeg=-121.55,
steeringRateDeg=350.0,
steeringTorque=250.0,
steeringPressed=True,
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
@@ -359,10 +360,14 @@ def test_legacy_2025_waits_for_manual_steering_to_settle_before_reengaging():
msg = controller.lateral_angle(CC, CS)
parser.update([(1, [msg])])
msg = controller.lateral_angle(CC, CS)
parser.update([(2, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
CS.out.steeringPressed = False
CS.out.steeringTorque = 0.0
CS.out.steeringAngleDeg = -113.78
msg = controller.lateral_angle(CC, CS)
parser.update([(2, [msg])])
@@ -411,6 +416,7 @@ def test_legacy_2025_manual_handoff_reclaim_is_gradual():
vEgoRaw=3.7,
steeringAngleDeg=2.5,
steeringRateDeg=-45.0,
steeringTorque=250.0,
steeringPressed=True,
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
@@ -419,9 +425,13 @@ def test_legacy_2025_manual_handoff_reclaim_is_gradual():
msg = controller.lateral_angle(CC, CS)
parser.update([(1, [msg])])
msg = controller.lateral_angle(CC, CS)
parser.update([(2, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
CS.out.steeringPressed = False
CS.out.steeringTorque = 0.0
CS.out.steeringRateDeg = 0.0
for i in range(19):
msg = controller.lateral_angle(CC, CS)
@@ -480,6 +490,10 @@ def test_angle_controller_tracks_driver_override():
msg = controller.lateral_angle(CC, CS)
assert not controller.driver_override
msg = controller.lateral_angle(CC, CS)
assert controller.driver_override
assert controller.p.STEER_OVERRIDE_TORQUE_HIGH == 150
assert controller.p.STEER_OVERRIDE_TORQUE_LOW == 100
@@ -541,7 +555,7 @@ def test_ascent_angle_controller_uses_fixed_angle_rate_limits():
vEgoRaw=21.66,
steeringAngleDeg=-25.77,
steeringRateDeg=0.0,
steeringTorque=-149.0,
steeringTorque=-250.0,
steeringPressed=False,
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
@@ -563,7 +577,7 @@ def test_angle_controller_yields_until_manual_steering_settles(platform):
vEgoRaw=21.66,
steeringAngleDeg=-25.06,
steeringRateDeg=35.0,
steeringTorque=-149.0,
steeringTorque=-250.0,
steeringPressed=True,
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
@@ -572,10 +586,14 @@ def test_angle_controller_yields_until_manual_steering_settles(platform):
msg = controller.lateral_angle(CC, CS)
parser.update([(1, [msg])])
msg = controller.lateral_angle(CC, CS)
parser.update([(2, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
CS.out.steeringPressed = False
CS.out.steeringTorque = 0.0
CS.out.steeringAngleDeg = -17.91
CS.out.steeringRateDeg = 0.0
for i in range(18):
@@ -323,6 +323,7 @@ class TestCarInterfaces:
)
assert fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_LKAS_ON_ENGAGE.value
assert fp_car_params.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE.value
def test_toyota_disable_openpilot_long_sets_stock_long_safety_flag(self):
CarInterface = interfaces[TOYOTA_CAR.TOYOTA_PRIUS_TSS2]
@@ -51,6 +51,9 @@ bool hyundai_has_lda_button = false;
extern bool hyundai_aol_lkas_on_engage;
bool hyundai_aol_lkas_on_engage = false;
extern bool hyundai_aol_main_lkas_on_engage;
bool hyundai_aol_main_lkas_on_engage = false;
extern bool hyundai_non_scc;
bool hyundai_non_scc = false;
@@ -78,6 +81,7 @@ void hyundai_common_init(uint16_t param) {
const uint16_t HYUNDAI_PARAM_ALT_LIMITS_2 = 512;
const int HYUNDAI_PARAM_HAS_LDA_BUTTON = 1024;
const uint16_t HYUNDAI_PARAM_AOL_MAIN_LKAS_ON_ENGAGE = 128;
const uint16_t HYUNDAI_PARAM_AOL_LKAS_ON_ENGAGE = 2048;
const uint16_t HYUNDAI_PARAM_NON_SCC = 4096;
const uint16_t HYUNDAI_PARAM_CAN_CANFD_BLENDED = 8192;
@@ -94,6 +98,7 @@ void hyundai_common_init(uint16_t param) {
hyundai_can_canfd_blended = GET_FLAG(param, HYUNDAI_PARAM_CAN_CANFD_BLENDED);
hyundai_has_lda_button = GET_FLAG(param, HYUNDAI_PARAM_HAS_LDA_BUTTON);
hyundai_aol_main_lkas_on_engage = GET_FLAG(param, HYUNDAI_PARAM_AOL_MAIN_LKAS_ON_ENGAGE);
hyundai_aol_lkas_on_engage = GET_FLAG(param, HYUNDAI_PARAM_AOL_LKAS_ON_ENGAGE);
hyundai_non_scc = GET_FLAG(param, HYUNDAI_PARAM_NON_SCC);
hyundai_cancel_button_enable = GET_FLAG(param, HYUNDAI_PARAM_CANCEL_BTN_ENABLE);
@@ -165,7 +170,12 @@ void hyundai_common_cruise_buttons_check(const int cruise_button, const bool mai
if (main_button && !main_button_prev) {
if (!hyundai_aol_main_lkas_sync) {
acc_main_on = !acc_main_on;
const bool main_turning_on = !acc_main_on;
acc_main_on = main_turning_on;
if (main_turning_on && hyundai_aol_main_lkas_on_engage &&
((alternative_experience & ALT_EXP_ALWAYS_ON_LATERAL) != 0)) {
lkas_on = true;
}
}
}
main_button_prev = main_button;
@@ -1102,6 +1102,7 @@ class SafetyTest(SafetyTestBase):
continue
if {attr, current_test}.issubset({'TestHyundaiLongitudinalSafety', 'TestHyundaiLongitudinalSafetyCameraSCC',
'TestHyundaiSafetyFCEVLong', 'TestHyundaiLongitudinalAolLkasOnEngageSafety',
'TestHyundaiLongitudinalAolMainLkasOnEngageSafety',
'TestHyundaiSafetyCanRefreshLong', 'TestHyundaiSafetyCanRefreshLongCameraSCC',
'TestHyundaiCanCanfdBlendedLongitudinalSafety',
'TestHyundaiLegacyLongitudinalSafety',
@@ -1157,6 +1158,7 @@ class SafetyTest(SafetyTestBase):
if attr.startswith('TestHyundaiLongitudinal') or attr in ('TestHyundaiSafetyFCEVLong',
'TestHyundaiLongitudinalAolLkasOnEngageSafety',
'TestHyundaiLongitudinalAolMainLkasOnEngageSafety',
'TestHyundaiCanCanfdBlendedLongitudinalSafety',
'TestHyundaiLegacyLongitudinalSafety',
'TestHyundaiLegacyLongitudinalSafetyHEV'):
@@ -622,6 +622,32 @@ class TestHyundaiLongitudinalAolLkasOnEngageSafety(HyundaiAolLkasOnEngageBase, T
self.safety.init_tests()
class TestHyundaiLongitudinalAolMainLkasOnEngageSafety(TestHyundaiLongitudinalSafety):
def setUp(self):
self.packer = CANPackerSafety("hyundai_kia_generic")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(
CarParams.SafetyModel.hyundai,
HyundaiSafetyFlags.LONG | HyundaiStarPilotSafetyFlags.AOL_MAIN_LKAS_ON_ENGAGE,
)
self.safety.init_tests()
def test_aol_lkas_auto_enables_on_main_engagement(self):
self.safety.set_alternative_experience(ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL)
self.safety.set_controls_allowed(False)
self._rx(self._button_msg(Buttons.NONE, main_button=1))
self._rx(self._button_msg(Buttons.NONE, main_button=0))
self.assertTrue(self.safety.get_acc_main_on())
self.assertTrue(self.safety.get_lkas_on())
self.assertTrue(self.safety.get_aol_allowed())
self._rx(self._user_brake_msg(True))
self.assertFalse(self.safety.get_controls_allowed())
self._set_prev_torque(0)
self.assertTrue(self._tx(self._torque_cmd_msg(self.MAX_RATE_UP)))
class TestHyundaiAolLkasOnEngageStockSafety(HyundaiAolLkasOnEngageStockBase, TestHyundaiSafety):
def setUp(self):
self.packer = CANPackerSafety("hyundai_kia_generic")
+4 -1
View File
@@ -558,10 +558,13 @@ def compile_driving(
command += ["--behavior-version", version]
compile_env = build_compile_env(supercombo=input_format == "supercombo")
if external_gpu:
gpu_debug = os.environ.get("STARPILOT_GPU_DEBUG", "1")
if gpu_debug not in {"1", "2"}:
gpu_debug = "1"
for qcom_only_flag in ("IMAGE", "NOLOCALS", "OPENPILOT_HACKS"):
compile_env.pop(qcom_only_flag, None)
compile_env.update({
"DEBUG": "1",
"DEBUG": gpu_debug,
"DEV": "USB+AMD:LLVM",
"WARP_DEV": "QCOM",
"FLOAT16": "1",
+8
View File
@@ -8,6 +8,7 @@ from openpilot.selfdrive.controls.lib.latcontrol import LatControl
from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import (
RAV4_TSS2_CARS,
SUBARU_IMPREZA_CARS,
get_honda_crv_5g_pid_output,
get_rav4_tss2_pid_output,
get_subaru_impreza_pid_output_scale,
)
@@ -103,6 +104,7 @@ class LatControlPID(LatControl):
self.honda_lateral_pid_kp_scale = 1.0
self.honda_lateral_pid_ki_scale = 1.0
self.is_civic_bosch_modified = CP.carFingerprint == HONDA.HONDA_CIVIC_BOSCH and bool(CP.flags & HondaFlags.EPS_MODIFIED)
self.is_honda_crv_5g = CP.carFingerprint == HONDA.HONDA_CRV_5G
self.is_subaru_impreza = CP.carFingerprint in SUBARU_IMPREZA_CARS
self.is_rav4_tss2 = CP.carFingerprint in RAV4_TSS2_CARS
self.prev_angle_steers_des_no_offset = 0.0
@@ -171,6 +173,12 @@ class LatControlPID(LatControl):
output_torque = raw_output_torque * get_subaru_impreza_pid_output_scale(error)
output_torque = float(max(min(output_torque, self.steer_max), -self.steer_max))
if self.is_honda_crv_5g:
output_torque = get_honda_crv_5g_pid_output(
output_torque, self.prev_output_torque, angle_steers_des_no_offset, CS.vEgo,
)
output_torque = float(max(min(output_torque, self.steer_max), -self.steer_max))
if self.is_rav4_tss2:
output_torque = get_rav4_tss2_pid_output(output_torque, self.prev_output_torque,
angle_steers_des_no_offset, CS.vEgo)
@@ -1176,6 +1176,14 @@ RAV4_TSS2_PID_CENTER_ANGLE = 14.0
RAV4_TSS2_PID_CENTER_ANGLE_WIDTH = 3.0
RAV4_TSS2_PID_OUTPUT_SCALE_MIN = 0.62
RAV4_TSS2_PID_OUTPUT_ALPHA_MIN = 0.28
HONDA_CRV_5G_PID_LOW_SPEED = 18.0 * CV.MPH_TO_MS
HONDA_CRV_5G_PID_LOW_SPEED_WIDTH = 3.0 * CV.MPH_TO_MS
HONDA_CRV_5G_PID_CENTER_ANGLE = 14.0
HONDA_CRV_5G_PID_CENTER_ANGLE_WIDTH = 3.0
HONDA_CRV_5G_PID_OUTPUT_SCALE_MIN = 0.62
HONDA_CRV_5G_PID_OUTPUT_ALPHA_MIN = 0.28
RAV4_TSS2_CENTER_FRICTION_THRESHOLD_GAIN = 0.14
RAV4_TSS2_CENTER_FRICTION_LAT = 0.30
RAV4_TSS2_CENTER_FRICTION_LAT_WIDTH = 0.08
@@ -1808,6 +1816,21 @@ def get_rav4_tss2_pid_output(output_torque: float, prev_output_torque: float,
return float(prev_output_torque + output_alpha * (limited_output - prev_output_torque))
def get_honda_crv_5g_pid_output(output_torque: float, prev_output_torque: float,
desired_angle_deg: float, v_ego: float) -> float:
"""Damp low-speed CR-V 5G center reversals without blunting real turns."""
speed_weight = _sigmoid((HONDA_CRV_5G_PID_LOW_SPEED - max(v_ego, 0.0)) /
HONDA_CRV_5G_PID_LOW_SPEED_WIDTH)
center_weight = _sigmoid((HONDA_CRV_5G_PID_CENTER_ANGLE - abs(desired_angle_deg)) /
HONDA_CRV_5G_PID_CENTER_ANGLE_WIDTH)
envelope = speed_weight * center_weight
output_scale = 1.0 - ((1.0 - HONDA_CRV_5G_PID_OUTPUT_SCALE_MIN) * envelope)
output_alpha = 1.0 - ((1.0 - HONDA_CRV_5G_PID_OUTPUT_ALPHA_MIN) * envelope)
limited_output = output_torque * output_scale
return float(prev_output_torque + output_alpha * (limited_output - prev_output_torque))
def _rav4_tss2_center_envelope(desired_lateral_accel: float, v_ego: float) -> float:
speed_weight = _sigmoid((RAV4_TSS2_CENTER_SPEED - max(v_ego, 0.0)) /
RAV4_TSS2_CENTER_SPEED_WIDTH)
@@ -3,8 +3,10 @@ import numpy as np
HONDA_HRV_3G_FAR_FOLLOW_BRAKE_SLEW_RATE = 3.0
HONDA_HRV_3G_FAR_FOLLOW_RELEASE_SLEW_RATE = 2.0
HONDA_CRV_5G_FAR_FOLLOW_BRAKE_SLEW_RATE = 2.5
HONDA_CRV_5G_FAR_FOLLOW_RELEASE_SLEW_RATE = 1.75
HONDA_CRV_5G_FAR_FOLLOW_BRAKE_SLEW_RATE = 1.5
HONDA_CRV_5G_FAR_FOLLOW_RELEASE_SLEW_RATE = 1.0
HONDA_ACCORD_FAR_FOLLOW_BRAKE_SLEW_RATE = 2.0
HONDA_ACCORD_FAR_FOLLOW_RELEASE_SLEW_RATE = 1.5
HONDA_HRV_3G_UNTRACKED_SLOW_LEAD_DECEL_SCALE = 1.35
HONDA_ACCORD_LEAD_DEPART_ACCEL_HOLD_MAX_ACCEL = 0.85
HONDA_ACCORD_LEAD_DEPART_ACCEL_ASSIST = 0.25
@@ -19,6 +21,7 @@ HONDA_ACCORD_STOP_GO_MIN_MODEL_PROB = 0.95
HONDA_ACCORD_STOP_GO_ACCEL_RISE_RATE = 4.0
HYUNDAI_ELANTRA_LEAD_FOLLOW_JERK_SCALE = 1.25
GENESIS_GV70_ELECTRIFIED_LEAD_FOLLOW_JERK_SCALE = 1.35
FORD_LIGHTNING_LEAD_FOLLOW_JERK_SCALE = 1.20
GM_SILVERADO_EARLY_FOLLOW_MIN_EGO_SPEED = 18.0
GM_SILVERADO_EARLY_FOLLOW_MAX_DISTANCE = 130.0
GM_SILVERADO_EARLY_FOLLOW_MIN_MODEL_PROB = 0.85
@@ -33,6 +36,7 @@ FORD_LIGHTNING_FAR_FOLLOW_BRAKE_SLEW_RATE = 2.5
FORD_LIGHTNING_FAR_FOLLOW_RELEASE_SLEW_RATE = 1.75
FORD_LIGHTNING_STANDSTILL_GUARD_DISTANCE_MARGIN = 5.0
FORD_LIGHTNING_STANDSTILL_GUARD_MAX_LEAD_SPEED = 0.60
FORD_LIGHTNING_GAP_SETTLE_MAX_EXTRA_GAP = 3.0
TOYOTA_SIENNA_POST_DEPARTURE_RESTOP_MAX_EGO_SPEED = 2.0
TOYOTA_SIENNA_POST_DEPARTURE_RESTOP_MAX_LEAD_SPEED = 0.45
TOYOTA_SIENNA_POST_DEPARTURE_RESTOP_MAX_LEAD_DELTA = 0.35
@@ -254,6 +258,8 @@ def allow_honda_crv_5g_vision_gap_settle(CP):
def get_standstill_gap_settle_max_extra_gap(CP):
if is_honda_crv_5g(CP):
return HONDA_CRV_5G_GAP_SETTLE_MAX_EXTRA_GAP
if is_ford_f150_lightning(CP):
return FORD_LIGHTNING_GAP_SETTLE_MAX_EXTRA_GAP
return 1.5
@@ -387,6 +393,11 @@ def allow_radar_standstill_gap_settle(CP):
def get_far_follow_output_slew_rates(CP):
if CP.brand == "honda" and str(CP.carFingerprint) == "HONDA_ACCORD":
return (
HONDA_ACCORD_FAR_FOLLOW_BRAKE_SLEW_RATE,
HONDA_ACCORD_FAR_FOLLOW_RELEASE_SLEW_RATE,
)
if CP.brand == "honda" and str(CP.carFingerprint) == "HONDA_HRV_3G":
return (
HONDA_HRV_3G_FAR_FOLLOW_BRAKE_SLEW_RATE,
@@ -425,6 +436,8 @@ def get_lead_follow_jerk_scale(CP):
str(getattr(CP, "carFingerprint", "")) == "GENESIS_GV70_ELECTRIFIED_1ST_GEN"
):
return GENESIS_GV70_ELECTRIFIED_LEAD_FOLLOW_JERK_SCALE
if is_ford_f150_lightning(CP):
return FORD_LIGHTNING_LEAD_FOLLOW_JERK_SCALE
return 1.0
@@ -27,6 +27,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_pid import (
LatControlPID,
get_civic_bosch_modified_pid_output_alpha,
get_civic_bosch_modified_pid_output_scale,
get_honda_crv_5g_pid_output,
)
from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import (
clear_flm_runtime_overrides,
@@ -1901,6 +1902,35 @@ class TestLatControl:
assert abs(large_turn) > abs(low_speed)
assert highway > low_speed
def test_honda_crv_5g_pid_output_damps_low_speed_center_reversals(self):
low_speed = get_honda_crv_5g_pid_output(1.0, -1.0, 4.0, 8.0 * 0.44704)
large_turn = get_honda_crv_5g_pid_output(1.0, -1.0, 24.0, 8.0 * 0.44704)
highway = get_honda_crv_5g_pid_output(1.0, -1.0, 4.0, 25.0 * 0.44704)
assert abs(low_speed) < 0.50
assert abs(large_turn) > abs(low_speed)
assert highway > low_speed
def test_honda_crv_5g_pid_output_update_path(self, monkeypatch):
controller, VM, CS, params, starpilot_toggles = self._build_pid_controller(HONDA.HONDA_CRV_5G)
CS.vEgo = 8.0 * 0.44704
CS.steeringAngleDeg = 4.0
tuned_output, _, lac_log = controller.update(
True, CS, VM, params, False, 0.0, False, 0.2, None, None, starpilot_toggles,
)
monkeypatch.setattr(latcontrol_pid, "get_honda_crv_5g_pid_output", lambda output, *_args: output)
base_controller, base_VM, base_CS, base_params, base_toggles = self._build_pid_controller(HONDA.HONDA_CRV_5G)
base_CS.vEgo = 8.0 * 0.44704
base_CS.steeringAngleDeg = 4.0
base_output, _, _ = base_controller.update(
True, base_CS, base_VM, base_params, False, 0.0, False, 0.2, None, None, base_toggles,
)
assert controller.is_honda_crv_5g
assert lac_log.active
assert abs(tuned_output) < abs(base_output)
def test_rav4_tss2_torque_center_tune_fades_before_real_turns(self):
low_speed_center = get_rav4_tss2_center_output_scale(0.05, 8.0)
low_speed_turn = get_rav4_tss2_center_output_scale(1.0, 8.0)
@@ -329,8 +329,8 @@ def test_crv_far_follow_output_slew_damps_nonurgent_lead_transition():
planner.lead_two = make_lead(status=False)
brake_rate, release_rate = get_far_follow_output_slew_rates(CP)
assert brake_rate == pytest.approx(2.5)
assert release_rate == pytest.approx(1.75)
assert brake_rate == pytest.approx(1.5)
assert release_rate == pytest.approx(1.0)
initial = planner.get_vehicle_far_follow_slew_target(
v_ego, prev_target=0.0, target=-0.6, output_should_stop=False, panic_bypass=False,
@@ -343,6 +343,45 @@ def test_crv_far_follow_output_slew_damps_nonurgent_lead_transition():
assert smoothed == pytest.approx(initial + release_rate * planner.dt)
def test_accord_far_follow_output_slew_damps_nonurgent_radar_transition():
v_ego = 24.0
CP = CarInterface.get_non_essential_params(CAR.HONDA_ACCORD)
planner = LongitudinalPlanner(CP, init_v=v_ego)
planner.lead_one = make_lead(status=True, d_rel=58.0, v_lead=20.0, radar=True, model_prob=0.99)
planner.lead_two = make_lead(status=False)
brake_rate, release_rate = get_far_follow_output_slew_rates(CP)
assert brake_rate == pytest.approx(2.0)
assert release_rate == pytest.approx(1.5)
initial = planner.get_vehicle_far_follow_slew_target(
v_ego, prev_target=0.0, target=-0.6, output_should_stop=False, panic_bypass=False,
)
smoothed = planner.get_vehicle_far_follow_slew_target(
v_ego, prev_target=initial, target=0.4, output_should_stop=False, panic_bypass=False,
)
assert initial == pytest.approx(-0.6)
assert smoothed == pytest.approx(initial + release_rate * planner.dt)
@pytest.mark.parametrize("output_should_stop,panic_bypass", [(True, False), (False, True)])
def test_accord_far_follow_output_slew_bypasses_urgent_scenes(output_should_stop, panic_bypass):
CP = CarInterface.get_non_essential_params(CAR.HONDA_ACCORD)
planner = LongitudinalPlanner(CP, init_v=24.0)
planner.lead_one = make_lead(status=True, d_rel=58.0, v_lead=20.0, radar=True, model_prob=0.99)
planner.lead_two = make_lead(status=False)
planner.far_follow_output_slew_active = True
target = planner.get_vehicle_far_follow_slew_target(
24.0, prev_target=0.4, target=-1.0,
output_should_stop=output_should_stop, panic_bypass=panic_bypass,
)
assert target == pytest.approx(-1.0)
assert not planner.far_follow_output_slew_active
@pytest.mark.parametrize("d_rel,v_lead,output_should_stop,panic_bypass", [
(20.0, 20.0, False, False),
(35.0, 18.0, False, False),
@@ -755,6 +794,7 @@ def test_lightning_stopped_lead_guard_tune_is_vehicle_specific():
assert get_standstill_stopped_lead_guard_distance_margin(civic) == pytest.approx(3.0)
assert get_standstill_stopped_lead_guard_max_lead_speed(lightning, 0.45) == pytest.approx(0.60)
assert get_standstill_stopped_lead_guard_max_lead_speed(civic, 0.45) == pytest.approx(0.45)
assert get_standstill_gap_settle_max_extra_gap(lightning) == pytest.approx(3.0)
assert get_tracked_lead_catchup_headway_margins(lightning) == pytest.approx((0.10, 0.25))
assert get_tracked_lead_catchup_bias_gain(lightning) == pytest.approx(0.65)
assert get_tracked_lead_catchup_headway_margins(civic) is None
@@ -43,6 +43,7 @@ def test_force_stop_jerk_scale_is_platform_specific():
def test_lead_follow_jerk_scale_is_platform_specific():
assert get_lead_follow_jerk_scale(SimpleNamespace(brand="hyundai", carFingerprint="HYUNDAI_ELANTRA_2021")) == 1.25
assert get_lead_follow_jerk_scale(SimpleNamespace(brand="hyundai", carFingerprint="GENESIS_GV70_ELECTRIFIED_1ST_GEN")) == 1.35
assert get_lead_follow_jerk_scale(SimpleNamespace(brand="ford", carFingerprint="FORD_F_150_LIGHTNING_MK1")) == 1.20
assert get_lead_follow_jerk_scale(SimpleNamespace(brand="other", carFingerprint="OTHER_CAR")) == 1.0
+4 -1
View File
@@ -136,7 +136,7 @@ class ModelRenderer(Widget):
model = sm['modelV2']
radar_state = sm['radarState'] if sm.valid['radarState'] else None
lead_one = radar_state.leadOne if radar_state else None
render_lead_indicator = self._longitudinal_control and radar_state is not None and lead_indicator_enabled(self._params, hide_by_default=True)
render_lead_indicator = self._should_render_lead_indicator(radar_state)
# Update model data when needed
model_updated = sm.updated['modelV2']
@@ -161,6 +161,9 @@ class ModelRenderer(Widget):
if render_lead_indicator and radar_state:
self._draw_lead_indicator()
def _should_render_lead_indicator(self, radar_state) -> bool:
return radar_state is not None and lead_indicator_enabled(self._params, hide_by_default=True)
def _update_raw_points(self, model):
"""Update raw 3D points from model data"""
self._path.raw_points = np.array([model.position.x, model.position.y, model.position.z], dtype=np.float32).T
@@ -0,0 +1,33 @@
from types import SimpleNamespace
import openpilot.selfdrive.ui.mici.onroad.model_renderer as model_renderer
class _FakeParams:
def __init__(self, enabled: bool):
self.enabled = enabled
def get(self, key):
assert key == "HideLeadMarker"
return b"0" if self.enabled else b"1"
def get_bool(self, key):
assert key == "HideLeadMarker"
return not self.enabled
def test_lead_indicator_renders_in_aol_without_longitudinal_control(monkeypatch):
monkeypatch.setattr(model_renderer, "ui_state", SimpleNamespace(always_on_lateral_active=True))
renderer = object.__new__(model_renderer.ModelRenderer)
renderer._params = _FakeParams(enabled=True)
renderer._longitudinal_control = False
assert renderer._should_render_lead_indicator(SimpleNamespace())
def test_lead_indicator_still_honors_disabled_setting():
renderer = object.__new__(model_renderer.ModelRenderer)
renderer._params = _FakeParams(enabled=False)
assert not renderer._should_render_lead_indicator(SimpleNamespace())
assert not renderer._should_render_lead_indicator(None)
+13 -8
View File
@@ -8,12 +8,10 @@ import urllib.parse
from datetime import datetime
from pathlib import Path
from openpilot.starpilot.common.starpilot_download_utilities import HF_BUCKET_URL, GITHUB_URL
from openpilot.starpilot.common.starpilot_utilities import delete_file, is_url_pingable
RESOURCES_REPO = os.getenv("STARPILOT_RESOURCES_REPO", "firestar5683/StarPilot-Resources")
GITLAB_RESOURCES_REPO = os.getenv("STARPILOT_GITLAB_RESOURCES_REPO", "firestar5683/FrogPilot-Resources")
GITHUB_URL = f"https://raw.githubusercontent.com/{RESOURCES_REPO}"
GITLAB_URL = f"https://gitlab.com/{GITLAB_RESOURCES_REPO}/-/raw"
LFS_POINTER_PREFIX = b"version https://git-lfs.github.com/spec/v1\n"
MAX_MULTIPART_FILES = 100
@@ -204,12 +202,19 @@ def get_remote_file_size(url, suppress_errors=False):
handle_request_error(error, None, None, None, None)
return 0
def get_resource_urls():
"""Return resource origins in priority order; GitHub is the only fallback."""
urls = []
if is_url_pingable("https://huggingface.co"):
urls.append(HF_BUCKET_URL)
if is_url_pingable("https://github.com") or is_url_pingable("https://api.github.com"):
urls.append(GITHUB_URL)
return urls
def get_repository_url():
if is_url_pingable("https://github.com"):
if check_github_rate_limit():
return GITHUB_URL
if is_url_pingable("https://gitlab.com"):
return GITLAB_URL
for url in get_resource_urls():
return url
return None
def handle_error(destination, error_message, error, download_param, progress_param, params_memory):
+56 -30
View File
@@ -5,12 +5,12 @@ import re
import urllib.request
from pathlib import Path
from urllib.parse import quote
from openpilot.starpilot.assets.download_functions import (
GITLAB_URL,
download_file,
download_multipart_file,
get_repository_url,
get_resource_urls,
handle_error,
handle_request_error,
verify_download,
@@ -461,21 +461,41 @@ class ModelManager:
handle_request_error(error, None, None, None, None)
return []
def _get_manifest(self, repo_url: str) -> tuple[str | None, list[dict]]:
@staticmethod
def _is_huggingface_url(url: str) -> bool:
return "huggingface.co/buckets/" in url
@staticmethod
def _hf_manifest_paths(manifest_version: str) -> tuple[str, ...]:
return (
f"model_names_{manifest_version}.json",
f"manifests/model_names_{manifest_version}.json",
)
def _get_manifest(self, resource_urls: str | list[str]) -> tuple[str | None, list[dict]]:
if isinstance(resource_urls, str):
resource_urls = [resource_urls]
for manifest_version in MANIFEST_CANDIDATES:
for manifest_path in self._manifest_paths(manifest_version):
model_info = self._fetch_manifest(f"{repo_url}/{manifest_path}")
if not model_info:
continue
for resource_url in resource_urls:
manifest_paths = (
self._hf_manifest_paths(manifest_version)
if self._is_huggingface_url(resource_url)
else self._manifest_paths(manifest_version)
)
for manifest_path in manifest_paths:
model_info = self._fetch_manifest(f"{resource_url}/{manifest_path}")
if not model_info:
continue
filtered = [
model for model in model_info
if is_supported_artifact_format(model.get("artifact_format"))
]
if not filtered:
continue
filtered = [
model for model in model_info
if is_supported_artifact_format(model.get("artifact_format"))
]
if not filtered:
continue
return manifest_version, filtered
return manifest_version, filtered
return None, []
@@ -659,12 +679,12 @@ class ModelManager:
if self.downloading_model:
return
repo_url = get_repository_url()
if repo_url is None:
print("GitHub and GitLab are offline...")
resource_urls = get_resource_urls()
if not resource_urls:
print("Hugging Face and GitHub are offline...")
return
manifest_version, model_info = self._get_manifest(repo_url)
manifest_version, model_info = self._get_manifest(resource_urls)
if not model_info:
print("No compatible tinygrad manifest found.")
return
@@ -713,9 +733,9 @@ class ModelManager:
self.downloading_model = False
return
repo_url = get_repository_url()
if not repo_url:
handle_error(None, "GitHub and GitLab are offline...", "Repository unavailable", MODEL_DOWNLOAD_PARAM, DOWNLOAD_PROGRESS_PARAM, self.params_memory)
resource_urls = get_resource_urls()
if not resource_urls:
handle_error(None, "Hugging Face and GitHub are offline...", "Repository unavailable", MODEL_DOWNLOAD_PARAM, DOWNLOAD_PROGRESS_PARAM, self.params_memory)
self.downloading_model = False
return
@@ -742,12 +762,18 @@ class ModelManager:
if custom_url:
candidate_urls.append((custom_url, True, False))
file_url = f"{repo_url}/Models/{filename}"
candidate_urls.append((file_url, False, True))
for resource_url in resource_urls:
if self._is_huggingface_url(resource_url):
artifact_urls_for_source = [
f"{resource_url}/models/{quote(self._canonical_model_key(model_to_download), safe='')}/{filename}",
f"{resource_url}/{filename}",
]
else:
artifact_urls_for_source = [f"{resource_url}/Models/{filename}"]
fallback_url = f"{GITLAB_URL}/Models/{filename}"
if fallback_url != file_url:
candidate_urls.append((fallback_url, False, True))
for artifact_url in artifact_urls_for_source:
if not any(existing[0] == artifact_url for existing in candidate_urls):
candidate_urls.append((artifact_url, False, True))
download_succeeded = False
for candidate_url, allow_unknown_size, allow_multipart in candidate_urls:
@@ -805,12 +831,12 @@ class ModelManager:
self.params_memory.remove(ALLOW_GPU_DOWNLOAD_WITHOUT_GPU_PARAM)
def _download_all_models(self, allow_gpu_without_gpu: bool):
repo_url = get_repository_url()
if not repo_url:
handle_error(None, "GitHub and GitLab are offline...", "Repository unavailable", MODEL_DOWNLOAD_ALL_PARAM, DOWNLOAD_PROGRESS_PARAM, self.params_memory)
resource_urls = get_resource_urls()
if not resource_urls:
handle_error(None, "Hugging Face and GitHub are offline...", "Repository unavailable", MODEL_DOWNLOAD_ALL_PARAM, DOWNLOAD_PROGRESS_PARAM, self.params_memory)
return
manifest_version, model_info = self._get_manifest(repo_url)
manifest_version, model_info = self._get_manifest(resource_urls)
if not model_info:
handle_error(None, "Unable to fetch models...", "Model list unavailable", MODEL_DOWNLOAD_ALL_PARAM, DOWNLOAD_PROGRESS_PARAM, self.params_memory)
return
+17 -1
View File
@@ -23,6 +23,22 @@ def test_v24_manifest_is_loaded_from_models_checkout():
assert ModelManager._manifest_paths("v24") == ("Models/model_names_v24.json",)
def test_resource_sources_prefer_huggingface_then_github(monkeypatch):
monkeypatch.setattr(download_functions, "is_url_pingable", lambda url: True)
assert download_functions.get_resource_urls() == [
download_functions.HF_BUCKET_URL,
download_functions.GITHUB_URL,
]
assert all("gitlab" not in url for url in download_functions.get_resource_urls())
def test_huggingface_manifest_has_root_and_manifests_fallbacks():
assert ModelManager._hf_manifest_paths("v24") == (
"model_names_v24.json",
"manifests/model_names_v24.json",
)
def test_old_manifest_ids_resolve_to_v23_namespace():
manager = object.__new__(ModelManager)
manager.available_models = ["pop223", "tr14223"]
@@ -36,7 +52,7 @@ def test_model_cleanup_matches_legacy_split_artifacts():
assert model_manager.is_driving_artifact_file("driving_vision_tinygrad.pkl")
assert model_manager.is_driving_artifact_file("driving_off_policy_tinygrad.pkl.p00")
assert not model_manager.is_driving_artifact_file("dmonitoring_model_tinygrad.pkl")
assert not model_manager.is_driving_artifact_file("local-test_driving_tinygrad.pkl")
assert model_manager.is_driving_artifact_file("local-test_driving_tinygrad.pkl")
def test_behavior_version_does_not_control_artifact_layout():
+51 -45
View File
@@ -12,9 +12,8 @@ import zipfile
from datetime import date, timedelta
from dateutil import easter
from pathlib import Path
from urllib.parse import quote_plus
from openpilot.starpilot.common.starpilot_download_utilities import GITHUB_URL, GITLAB_URL, download_file, get_repository_url, handle_error, verify_download
from openpilot.starpilot.common.starpilot_download_utilities import HF_BUCKET, GITHUB_URL, download_file, get_resource_urls, handle_error, verify_download
from openpilot.starpilot.common.theme_asset_names import find_matching_theme_asset_file, find_matching_theme_asset_name
from openpilot.starpilot.common.starpilot_utilities import delete_file, extract_zip, load_json_file, update_json_file
from openpilot.starpilot.common.starpilot_variables import ACTIVE_THEME_PATH, RANDOM_EVENTS_PATH, RESOURCES_REPO, THEME_SAVE_PATH
@@ -219,44 +218,49 @@ class ThemeManager:
def download_theme(self, theme_component, theme_name, asset_param, starpilot_toggles):
self.downloading_theme = True
repo_url = get_repository_url(self.session)
if not repo_url:
handle_error(None, asset_param, "Repository unavailable", "GitHub and GitLab are offline...", self.params_memory, DOWNLOAD_PROGRESS_PARAM)
resource_urls = get_resource_urls(self.session)
if not resource_urls:
handle_error(None, asset_param, "Repository unavailable", "Hugging Face and GitHub are offline...", self.params_memory, DOWNLOAD_PROGRESS_PARAM)
self.downloading_theme = False
return
alternate_url = GITLAB_URL if "raw.githubusercontent" in repo_url else GITHUB_URL
primary_source = "GitLab" if "gitlab" in repo_url else "GitHub"
if theme_component == "boot_logos":
download_link = f"{repo_url}/Themes/bootlogo"
download_path = THEME_SAVE_PATH / "bootlogos" / theme_name
extensions = [".png", ".jpg", ".jpeg"]
name_candidates = list(dict.fromkeys([theme_name, theme_name.replace("_", "-"), theme_name.replace("-", "_")]))
elif theme_component == "distance_icons":
download_link = f"{repo_url}/Distance-Icons/{theme_name}"
download_path = THEME_SAVE_PATH / "theme_packs" / theme_name / theme_component
extensions = [".zip"]
name_candidates = [theme_name]
elif theme_component == "steering_wheels":
download_link = f"{repo_url}/Steering-Wheels/{theme_name}"
download_path = THEME_SAVE_PATH / theme_component / theme_name
extensions = [".gif", ".png"]
name_candidates = [theme_name]
else:
download_link = f"{repo_url}/Themes/{theme_name}/{theme_component}"
download_path = THEME_SAVE_PATH / "theme_packs" / theme_name / theme_component
extensions = [".zip"]
name_candidates = [theme_name]
for extension in extensions:
theme_path = download_path.with_suffix(extension)
theme_urls = [f"{download_link}/{candidate}{extension}" for candidate in name_candidates] if theme_component == "boot_logos" else [download_link + extension]
theme_urls = []
for resource_url in resource_urls:
source_prefix = f"{resource_url}/theme" if "huggingface.co/buckets/" in resource_url else resource_url
if theme_component == "boot_logos":
path_prefix = f"{source_prefix}/Themes/bootlogo"
theme_urls.extend(f"{path_prefix}/{candidate}{extension}" for candidate in name_candidates)
elif theme_component == "distance_icons":
theme_urls.append(f"{source_prefix}/Distance-Icons/{theme_name}{extension}")
elif theme_component == "steering_wheels":
theme_urls.append(f"{source_prefix}/Steering-Wheels/{theme_name}{extension}")
else:
theme_urls.append(f"{source_prefix}/Themes/{theme_name}/{theme_component}{extension}")
for theme_url in theme_urls:
delete_file(theme_path)
print(f"Downloading theme from {primary_source}: {theme_name}")
source = "Hugging Face" if "huggingface.co/buckets/" in theme_url else "GitHub"
print(f"Downloading theme from {source}: {theme_name}")
download_file(CANCEL_DOWNLOAD_PARAM, theme_path, asset_param, self.params_memory, DOWNLOAD_PROGRESS_PARAM, self.session, theme_url)
if self.params_memory.get_bool(CANCEL_DOWNLOAD_PARAM):
@@ -267,7 +271,7 @@ class ThemeManager:
return
if verify_download(theme_path, self.params_memory, self.session, theme_url):
print(f"Theme {theme_name} downloaded and verified successfully from {primary_source}!")
print(f"Theme {theme_name} downloaded and verified successfully from {source}!")
self.update_theme_size(theme_component, theme_name, theme_path.stat().st_size)
if extension == ".zip":
@@ -282,21 +286,34 @@ class ThemeManager:
self.update_themes(starpilot_toggles)
return
if self.handle_verification_failure(extension, theme_component, theme_name, asset_param, theme_path, download_path, starpilot_toggles, alternate_url):
return
handle_error(download_path, asset_param, "Download failed...", "Download failed...", self.params_memory, DOWNLOAD_PROGRESS_PARAM)
self.downloading_theme = False
def fetch_assets(self, repo_url, starpilot_toggles):
is_github = "github" in repo_url
is_gitlab = "gitlab" in repo_url
repo_encoded = quote_plus(RESOURCES_REPO)
is_huggingface = "huggingface.co/buckets/" in repo_url
assets = {"boot_logos": [], "themes": {}, "wheels": []}
try:
def list_files(branch):
if is_huggingface:
response = self.session.get(f"https://huggingface.co/api/buckets/{HF_BUCKET}/tree?recursive=true", timeout=10)
response.raise_for_status()
prefix = {
"Themes": "theme/Themes/",
"Distance-Icons": "theme/Distance-Icons/",
"Steering-Wheels": "theme/Steering-Wheels/",
}[branch]
return [
{
"path": item.get("path", "")[len(prefix):],
"name": Path(item.get("path", "")).name,
"type": item.get("type"),
"size": item.get("size", 0),
}
for item in response.json()
if item.get("type") == "file" and item.get("path", "").startswith(prefix)
]
if is_github:
response = self.session.get(f"https://api.github.com/repos/{RESOURCES_REPO}/git/trees/{branch}?recursive=1", timeout=10)
response.raise_for_status()
@@ -310,27 +327,12 @@ class ThemeManager:
for item in response.json().get("tree", [])
if item.get("type") == "blob"
]
if is_gitlab:
response = self.session.get(f"https://gitlab.com/api/v4/projects/{repo_encoded}/repository/tree?ref={branch}&recursive=true", timeout=10)
response.raise_for_status()
return [
{
"path": item.get("path", ""),
"name": item.get("name", ""),
"type": item.get("type"),
"size": 0,
}
for item in response.json()
if item.get("type") in ("blob", "file")
]
print(f"Unsupported repository URL: {repo_url}")
return []
def file_size(branch, path, fallback):
if is_github:
if is_github or is_huggingface:
return int(fallback or 0)
response = self.session.head(f"https://gitlab.com/api/v4/projects/{repo_encoded}/repository/files/{quote_plus(path)}/raw?ref={branch}", timeout=10)
return int(response.headers.get("content-length", 0)) if response.ok else 0
for branch in ["Distance-Icons", "Steering-Wheels"]:
for item in list_files(branch):
@@ -402,7 +404,8 @@ class ThemeManager:
return assets
except requests.exceptions.RequestException as error:
print(f"Failed to fetch theme sizes from {'GitHub' if is_github else 'GitLab'}: {error}")
source = "Hugging Face" if is_huggingface else "GitHub"
print(f"Failed to fetch theme sizes from {source}: {error}")
return {}
@staticmethod
@@ -465,9 +468,8 @@ class ThemeManager:
"christmas_week": date(year, 12, 25)
}
def handle_verification_failure(self, extension, theme_component, theme_name, asset_param, theme_path, download_path, starpilot_toggles, fallback_url=GITLAB_URL):
is_github = "raw.githubusercontent" in fallback_url
source = "GitHub" if is_github else "GitLab"
def handle_verification_failure(self, extension, theme_component, theme_name, asset_param, theme_path, download_path, starpilot_toggles, fallback_url=GITHUB_URL):
source = "GitHub"
if theme_component == "boot_logos":
download_link = f"{fallback_url}/Themes/bootlogo"
@@ -750,13 +752,17 @@ class ThemeManager:
self.sync_local_resources()
repo_url = get_repository_url(self.session)
if repo_url is None:
print("GitHub and GitLab are offline...")
resource_urls = get_resource_urls(self.session)
if not resource_urls:
print("Hugging Face and GitHub are offline...")
self.update_theme_params([], [], [], [], [], [], [])
return
assets = self.fetch_assets(repo_url, starpilot_toggles)
assets = {}
for repo_url in resource_urls:
assets = self.fetch_assets(repo_url, starpilot_toggles)
if assets:
break
if not assets:
return
@@ -7,9 +7,9 @@ from datetime import datetime, timezone
from openpilot.starpilot.common.starpilot_utilities import delete_file, is_url_pingable
from openpilot.starpilot.common.starpilot_variables import RESOURCES_REPO
GITLAB_RESOURCES_REPO = os.getenv("STARPILOT_GITLAB_RESOURCES_REPO", "firestar5683/FrogPilot-Resources")
HF_BUCKET = os.getenv("STARPILOT_HF_BUCKET", "firestar4430/StarPilot-Resources")
HF_BUCKET_URL = f"https://huggingface.co/buckets/{HF_BUCKET}/resolve"
GITHUB_URL = f"https://raw.githubusercontent.com/{RESOURCES_REPO}"
GITLAB_URL = f"https://gitlab.com/{GITLAB_RESOURCES_REPO}/-/raw"
def download_file(cancel_param, destination, download_param, params_memory, progress_param, session, url, offset_bytes=0, total_bytes=0):
try:
@@ -85,13 +85,21 @@ def get_remote_file_size(params_memory, session, url):
def get_repository_url(session):
if (is_url_pingable("https://github.com") or is_url_pingable("https://api.github.com")) and not github_rate_limited(session):
return GITHUB_URL
if is_url_pingable("https://gitlab.com"):
return GITLAB_URL
for url in get_resource_urls(session):
return url
return None
def get_resource_urls(session):
"""Return resource origins in priority order; GitHub is the only fallback."""
urls = []
if is_url_pingable("https://huggingface.co"):
urls.append(HF_BUCKET_URL)
if is_url_pingable("https://github.com") or is_url_pingable("https://api.github.com"):
urls.append(GITHUB_URL)
return urls
def github_rate_limited(session):
try:
response = session.get("https://api.github.com/rate_limit", timeout=10)
+1 -1
View File
@@ -203,7 +203,7 @@ def transition_onroad(error_log):
error_log.unlink()
def update_checks(now, model_manager, theme_manager, thread_manager, params, params_memory, starpilot_toggles, boot_run=False):
while not (is_url_pingable("https://github.com") or is_url_pingable("https://gitlab.com")):
while not (is_url_pingable("https://huggingface.co") or is_url_pingable("https://github.com")):
time.sleep(60)
model_manager.update_models(boot_run)