This commit is contained in:
firestar5683
2026-07-12 21:47:48 -05:00
parent e9a2c925ed
commit e4dfbd1a60
43 changed files with 844 additions and 142 deletions
+4 -1
View File
@@ -126,7 +126,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SecOCKey", {PERSISTENT | DONT_LOG, STRING}},
{"ShowDebugInfo", {PERSISTENT, BOOL}},
{"ShowAllToggles", {PERSISTENT, BOOL, "0", "0", 3}},
{"TryRaylibUI", {PERSISTENT, BOOL, "0"}},
{"TryRaylibUI", {PERSISTENT, BOOL, "1"}},
{"UseOldUI", {PERSISTENT, BOOL, "0"}},
{"UsePrebuilt", {PERSISTENT, BOOL, "1"}},
{"RouteCount", {PERSISTENT, INT, "0"}},
{"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
@@ -581,6 +582,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"StartupMessageTop", {PERSISTENT, STRING, "Be ready to take over at any time", "Be ready to take over at any time", 0}},
{"StaticPedalsOnUI", {PERSISTENT, BOOL, "0", "0", 1}},
{"SteerDelay", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
{"SteerDelayModeMigrated", {PERSISTENT, BOOL}},
{"SteerDelayStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
{"SteerFriction", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
{"SteerFrictionStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
@@ -632,6 +634,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"UpdateTinygrad", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
{"UpdateWheelImage", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
{"UseActiveTheme", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}},
{"UseAutoSteerDelay", {PERSISTENT, BOOL, "1", "1", 3}},
{"UseKonikServer", {PERSISTENT, BOOL, "0", "0", 2}},
{"UseSI", {PERSISTENT, BOOL, "1", "1", 3}},
{"UserFavorites", {PERSISTENT, STRING, "", "", 1}},
+59 -27
View File
@@ -16,6 +16,11 @@ AVERAGE_ROAD_ROLL = 0.06 # ~3.4 degrees, 6% superelevation. higher actual roll
MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL - (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL) # ~2.4 m/s^2
def apply_ford_angle(desired_angle_deg: float, current_angle_deg: float) -> float:
relative_angle = desired_angle_deg - current_angle_deg
return float(np.clip(relative_angle, -5.8, 5.8))
def anti_overshoot(apply_curvature, apply_curvature_last, v_ego):
diff = 0.1
tau = 5 # 5s smooths over the overshoot
@@ -65,6 +70,7 @@ class CarController(CarControllerBase):
self.CAN = fordcan.CanBus(CP)
self.apply_curvature_last = 0
self.apply_angle_last = 0
self.anti_overshoot_curvature_last = 0
self.accel = 0.0
self.gas = 0.0
@@ -98,38 +104,62 @@ class CarController(CarControllerBase):
can_sends.append(fordcan.create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, tja_toggle=True))
### lateral control ###
# send steer msg at 20Hz
if (self.frame % CarControllerParams.STEER_STEP) == 0:
# Bronco and some other cars consistently overshoot curv requests
# Apply some deadzone + smoothing convergence to avoid oscillations
if self.CP.carFingerprint in (CAR.FORD_BRONCO_SPORT_MK1, CAR.FORD_F_150_MK14):
self.anti_overshoot_curvature_last = anti_overshoot(actuators.curvature, self.anti_overshoot_curvature_last, CS.out.vEgoRaw)
apply_curvature = self.anti_overshoot_curvature_last
if self.CP.flags & FordFlags.LKA_STEERING:
lka_active = CC.latActive and CS.lkas_available
if lka_active:
self.apply_angle_last = apply_ford_angle(actuators.steeringAngleDeg, CS.out.steeringAngleDeg)
current_curvature = -CS.out.yawRate / max(CS.out.vEgoRaw, 0.1)
self.apply_curvature_last = apply_ford_curvature_limits(actuators.curvature, self.apply_curvature_last, current_curvature,
CS.out.vEgoRaw, 0., True, self.CP)
else:
apply_curvature = actuators.curvature
self.apply_angle_last = 0.
self.apply_curvature_last = 0.
# apply rate limits, curvature error limit, and clip to signal range
current_curvature = -CS.out.yawRate / max(CS.out.vEgoRaw, 0.1)
# Keep the stock LMC heartbeat present while steering through Lane_Assist_Data1.
if (self.frame % CarControllerParams.STEER_STEP) == 0:
can_sends.append(fordcan.create_lat_ctl_msg(self.packer, self.CAN, False, 0., 0., 0., 0.,
stock_lmc=CS.lateral_motion_control))
self.apply_curvature_last = apply_ford_curvature_limits(apply_curvature, self.apply_curvature_last, current_curvature,
CS.out.vEgoRaw, 0., CC.latActive, self.CP)
if (self.frame % CarControllerParams.LKA_STEP) == 0:
direction = 0
if lka_active:
direction = 2 if CS.out.steeringAngleDeg > 0 else 4
ramp_type = 1 if abs(self.apply_angle_last) >= 5 else 0
can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN, active=lka_active, apply_angle=self.apply_angle_last,
direction=direction, ramp_type=ramp_type, curvature=-self.apply_curvature_last))
else:
# send steer msg at 20Hz
if (self.frame % CarControllerParams.STEER_STEP) == 0:
# Bronco and some other cars consistently overshoot curv requests
# Apply some deadzone + smoothing convergence to avoid oscillations
if self.CP.carFingerprint in (CAR.FORD_BRONCO_SPORT_MK1, CAR.FORD_F_150_MK14):
self.anti_overshoot_curvature_last = anti_overshoot(actuators.curvature, self.anti_overshoot_curvature_last, CS.out.vEgoRaw)
apply_curvature = self.anti_overshoot_curvature_last
else:
apply_curvature = actuators.curvature
if self.CP.flags & FordFlags.CANFD:
# TODO: extended mode
# Ford uses four individual signals to dictate how to drive to the car. Curvature alone (limited to 0.02m/s^2)
# can actuate the steering for a large portion of any lateral movements. However, in order to get further control on
# steer actuation, the other three signals are necessary. Ford controls vehicles differently than most other makes.
# A detailed explanation on ford control can be found here:
# https://www.f150gen14.com/forum/threads/introducing-bluepilot-a-ford-specific-fork-for-comma3x-openpilot.24241/#post-457706
mode = 1 if CC.latActive else 0
counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10
can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -self.apply_curvature_last, 0., counter))
else:
can_sends.append(fordcan.create_lat_ctl_msg(self.packer, self.CAN, CC.latActive, 0., 0., -self.apply_curvature_last, 0.))
# apply rate limits, curvature error limit, and clip to signal range
current_curvature = -CS.out.yawRate / max(CS.out.vEgoRaw, 0.1)
# send lka msg at 33Hz
if (self.frame % CarControllerParams.LKA_STEP) == 0:
can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN))
self.apply_curvature_last = apply_ford_curvature_limits(apply_curvature, self.apply_curvature_last, current_curvature,
CS.out.vEgoRaw, 0., CC.latActive, self.CP)
if self.CP.flags & FordFlags.CANFD:
# TODO: extended mode
# Ford uses four individual signals to dictate how to drive to the car. Curvature alone (limited to 0.02m/s^2)
# can actuate the steering for a large portion of any lateral movements. However, in order to get further control on
# steer actuation, the other three signals are necessary. Ford controls vehicles differently than most other makes.
# A detailed explanation on ford control can be found here:
# https://www.f150gen14.com/forum/threads/introducing-bluepilot-a-ford-specific-fork-for-comma3x-openpilot.24241/#post-457706
mode = 1 if CC.latActive else 0
counter = (self.frame // CarControllerParams.STEER_STEP) % 0x10
can_sends.append(fordcan.create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -self.apply_curvature_last, 0., counter))
else:
can_sends.append(fordcan.create_lat_ctl_msg(self.packer, self.CAN, CC.latActive, 0., 0., -self.apply_curvature_last, 0.))
# send lka msg at 33Hz
if (self.frame % CarControllerParams.LKA_STEP) == 0:
can_sends.append(fordcan.create_lka_msg(self.packer, self.CAN))
### longitudinal control ###
# send acc msg at 50Hz
@@ -195,6 +225,8 @@ class CarController(CarControllerBase):
self.lead_distance_bars_last = hud_control.leadDistanceBars
new_actuators = actuators.as_builder()
if self.CP.flags & FordFlags.LKA_STEERING:
new_actuators.steeringAngleDeg = self.apply_angle_last + CS.out.steeringAngleDeg
new_actuators.curvature = self.apply_curvature_last
new_actuators.accel = self.accel
new_actuators.gas = self.gas
+11
View File
@@ -20,6 +20,8 @@ class CarState(CarStateBase):
self.distance_button = 0
self.lc_button = 0
self.lkas_available = False
self.lateral_motion_control = None
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
cp = can_parsers[Bus.pt]
@@ -108,6 +110,15 @@ class CarState(CarStateBase):
# Stock values from IPMA so that we can retain some stock functionality
self.acc_tja_status_stock_values = cp_cam.vl["ACCDATA_3"]
self.lkas_status_stock_values = cp_cam.vl["IPMA_Data"]
if self.CP.flags & FordFlags.LKA_STEERING:
try:
self.lkas_available = cp.vl["Lane_Assist_Data3_FD1"]["LaActAvail_D_Actl"] == 3
except KeyError:
self.lkas_available = False
try:
self.lateral_motion_control = cp_cam.vl["LateralMotionControl"]
except KeyError:
self.lateral_motion_control = None
ret.buttonEvents = [
*create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}),
@@ -106,10 +106,12 @@ FW_VERSIONS = {
},
CAR.FORD_F_150_MK14: {
(Ecu.eps, 0x730, None): [
b'ML3V-14D003-BA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'ML3V-14D003-BC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'ML3V-14D003-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.abs, 0x760, None): [
b'ML34-2D053-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'NL34-2D053-CA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PL34-2D053-CA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PL34-2D053-CC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -117,6 +119,7 @@ FW_VERSIONS = {
b'PL3V-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x764, None): [
b'ML3T-14D049-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'ML3T-14D049-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
@@ -124,6 +127,7 @@ FW_VERSIONS = {
b'ML3T-14H102-ABR\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'ML3T-14H102-ABT\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'ML3T-14H102-ACA\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PJ6T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'RJ6T-14H102-ACJ\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -211,6 +215,7 @@ FW_VERSIONS = {
b'PB3C-2D053-ZD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PB3C-2D053-ZG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PB3C-2D053-ZJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'RB3C-2D053-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x764, None): [
b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -220,4 +225,18 @@ FW_VERSIONS = {
b'RJ6T-14H102-BBB\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
},
CAR.FORD_TRANSIT_MK5: {
(Ecu.eps, 0x730, None): [
b'KK21-14D003-AM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.abs, 0x760, None): [
b'NK41-2D053-DF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x764, None): [
b'PC4T-14D049-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x706, None): [
b'NK3T-14F397-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
},
}
+54 -19
View File
@@ -1,3 +1,5 @@
import math
from opendbc.car import CanBusBase, structs
HUDControl = structs.CarControl.HUDControl
@@ -33,20 +35,40 @@ def calculate_lat_ctl2_checksum(mode: int, counter: int, dat: bytearray) -> int:
return 0xFF - (checksum & 0xFF)
def create_lka_msg(packer, CAN: CanBus):
def create_lka_msg(packer, CAN: CanBus, active: bool = False, apply_angle: float = 0.0,
direction: int = 0, ramp_type: int = 0, curvature: float = 0.0):
"""
Creates an empty CAN message for the Ford LKA Command.
Creates a CAN message for the Ford LKA Command.
This command can apply "Lane Keeping Aid" maneuvers, which are subject to the PSCM lockout.
On LKA-steering platforms, this command applies Lane Keeping Aid maneuvers through the PSCM.
Frequency is 33Hz.
"""
return packer.make_can_msg("Lane_Assist_Data1", CAN.main, {})
if active:
mrad = math.radians(max(-5.8, min(5.8, apply_angle))) * 1000.0
mrad = max(-102.4, min(102.3, mrad))
curvature = max(-0.01023, min(0.01023, curvature))
else:
mrad = 0.0
direction = 0
ramp_type = 0
curvature = 0.0
values = {
"LkaDrvOvrrd_D_Rq": 0,
"LkaActvStats_D2_Req": direction if active else 0,
"LaRefAng_No_Req": mrad,
"LaRampType_B_Req": ramp_type,
"LaCurvature_No_Calc": curvature,
"LdwActvStats_D_Req": 0,
"LdwActvIntns_D_Req": 3,
}
return packer.make_can_msg("Lane_Assist_Data1", CAN.main, values)
def create_lat_ctl_msg(packer, CAN: CanBus, lat_active: bool, path_offset: float, path_angle: float, curvature: float,
curvature_rate: float):
curvature_rate: float, stock_lmc=None):
"""
Creates a CAN message for the Ford TJA/LCA Command.
@@ -68,20 +90,33 @@ def create_lat_ctl_msg(packer, CAN: CanBus, lat_active: bool, path_offset: float
Frequency is 20Hz.
"""
values = {
"LatCtlRng_L_Max": 0, # Unknown [0|126] meter
"HandsOffCnfm_B_Rq": 0, # Unknown: 0=Inactive, 1=Active [0|1]
"LatCtl_D_Rq": 1 if lat_active else 0, # Mode: 0=None, 1=ContinuousPathFollowing, 2=InterventionLeft,
# 3=InterventionRight, 4-7=NotUsed [0|7]
"LatCtlRampType_D_Rq": 0, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3]
# Makes no difference with curvature control
"LatCtlPrecision_D_Rq": 1, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3]
# The stock system always uses comfortable
"LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter
"LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians
"LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2
"LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter
}
if stock_lmc is not None:
values = {
"LatCtlRng_L_Max": stock_lmc["LatCtlRng_L_Max"],
"HandsOffCnfm_B_Rq": stock_lmc["HandsOffCnfm_B_Rq"],
"LatCtl_D_Rq": 0,
"LatCtlRampType_D_Rq": stock_lmc["LatCtlRampType_D_Rq"],
"LatCtlPrecision_D_Rq": stock_lmc["LatCtlPrecision_D_Rq"],
"LatCtlPathOffst_L_Actl": stock_lmc["LatCtlPathOffst_L_Actl"],
"LatCtlPath_An_Actl": stock_lmc["LatCtlPath_An_Actl"],
"LatCtlCurv_NoRate_Actl": stock_lmc["LatCtlCurv_NoRate_Actl"],
"LatCtlCurv_No_Actl": stock_lmc["LatCtlCurv_No_Actl"],
}
else:
values = {
"LatCtlRng_L_Max": 0, # Unknown [0|126] meter
"HandsOffCnfm_B_Rq": 0, # Unknown: 0=Inactive, 1=Active [0|1]
"LatCtl_D_Rq": 1 if lat_active else 0, # Mode: 0=None, 1=ContinuousPathFollowing, 2=InterventionLeft,
# 3=InterventionRight, 4-7=NotUsed [0|7]
"LatCtlRampType_D_Rq": 0, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3]
# Makes no difference with curvature control
"LatCtlPrecision_D_Rq": 1, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3]
# The stock system always uses comfortable
"LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter
"LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians
"LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2
"LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter
}
return packer.make_can_msg("LateralMotionControl", CAN.main, values)
+3 -1
View File
@@ -31,7 +31,7 @@ class CarInterface(CarInterfaceBase):
ret.radarUnavailable = Bus.radar not in DBC[candidate]
ret.steerControlType = structs.CarParams.SteerControlType.angle
ret.steerActuatorDelay = 0.2
ret.steerActuatorDelay = 0.05 if ret.flags & FordFlags.LKA_STEERING else 0.2
ret.steerLimitTimer = 1.0
ret.steerAtStandstill = True
@@ -63,6 +63,8 @@ class CarInterface(CarInterfaceBase):
if fingerprint[CAN.camera].get(0x3d6) != 8 or fingerprint[CAN.camera].get(0x186) != 8:
carlog.error('dashcamOnly: SecOC is unsupported')
ret.dashcamOnly = True
elif ret.flags & FordFlags.LKA_STEERING:
ret.safetyConfigs[-1].safetyParam |= FordSafetyFlags.LKA_STEERING.value
else:
# Lock out if the car does not have needed lateral and longitudinal control APIs.
# Note that we also check CAN for adaptive cruise, but no known signal for LCA exists
+13
View File
@@ -46,11 +46,13 @@ class CarControllerParams:
class FordSafetyFlags(IntFlag):
LONG_CONTROL = 1
CANFD = 2
LKA_STEERING = 4
class FordFlags(IntFlag):
# Static flags
CANFD = 1
LKA_STEERING = 2
class RADAR:
@@ -111,6 +113,13 @@ class FordCANFDPlatformConfig(FordPlatformConfig):
self.flags |= FordFlags.CANFD
@dataclass
class FordLKASteeringPlatformConfig(FordPlatformConfig):
def init(self):
super().init()
self.flags |= FordFlags.LKA_STEERING
@dataclass
class FordF150LightningPlatform(FordCANFDPlatformConfig):
def init(self):
@@ -178,6 +187,10 @@ class CAR(Platforms):
[FordCarDocs("Ford Ranger 2024", "Adaptive Cruise Control with Lane Centering", setup_video="https://www.youtube.com/watch?v=2oJlXCKYOy0")],
CarSpecs(mass=2000, wheelbase=3.27, steerRatio=17.0),
)
FORD_TRANSIT_MK5 = FordLKASteeringPlatformConfig(
[FordCarDocs("Ford Transit 2025", "Co-Pilot360 Assist+")],
CarSpecs(mass=2068, wheelbase=3.302, steerRatio=16.7),
)
# FW response contains a combined software and part number
@@ -1018,6 +1018,17 @@ FW_VERSIONS = {
b'\xf1\x00CN7 MDPS C 1.00 1.04 56310BY050\x00 4CNHC104',
],
},
CAR.HYUNDAI_ELANTRA_HEV_2026: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA510 240509',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00CN7_ RDR ----- 1.00 1.01 99110-AA500 ',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00CN7 MDPS C 1.00 1.03 56300BY670\x00 4CSHC103',
],
},
CAR.HYUNDAI_KONA_HEV: {
(Ecu.abs, 0x7d1, None): [
b'\xf1\x00OS IEB \x01 104 \x11 58520-CM000',
@@ -295,6 +295,14 @@ class CAR(Platforms):
CarSpecs(mass=3017 * CV.LB_TO_KG, wheelbase=2.72, steerRatio=12.9, tireStiffnessFactor=0.65),
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID,
)
# 2026 CN7 Hybrid Limited. Initial port based on route
# 24d8ddb7d33b028f/00000008--d1f2ac19cc; keep this separate from the
# 2021-23 platform until its changed CAN receive checks are validated.
HYUNDAI_ELANTRA_HEV_2026 = HyundaiPlatformConfig(
[HyundaiCarDocs("Hyundai Elantra Hybrid 2026", "Limited", car_parts=CarParts.common([CarHarness.hyundai_k]))],
HYUNDAI_ELANTRA_HEV_2021.specs,
flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID,
)
HYUNDAI_GENESIS = HyundaiPlatformConfig(
[
# TODO: check 2015 packages
@@ -36,6 +36,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"FORD_F_150_LIGHTNING_MK1" = [nan, 1.5, nan]
"FORD_MUSTANG_MACH_E_MK1" = [nan, 1.5, nan]
"FORD_RANGER_MK2" = [nan, 1.5, nan]
"FORD_TRANSIT_MK5" = [nan, 1.5, nan]
###
# No steering wheel
@@ -40,6 +40,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"HYUNDAI_ELANTRA" = "HYUNDAI_SONATA_LF"
"HYUNDAI_ELANTRA_GT_I30" = "HYUNDAI_SONATA_LF"
"HYUNDAI_ELANTRA_HEV_2021" = "HYUNDAI_SONATA"
"HYUNDAI_ELANTRA_HEV_2026" = "HYUNDAI_SONATA"
"HYUNDAI_TUCSON" = "HYUNDAI_SANTA_FE"
"HYUNDAI_SANTA_FE_2022" = "HYUNDAI_SANTA_FE_HEV_2022"
"KIA_K5_HEV_2020" = "KIA_K5_2021"
@@ -487,8 +487,13 @@ class CarController(CarControllerBase):
pcm_accel_cmd = float(np.clip(pcm_accel_cmd, self.params.ACCEL_MIN, self.params.ACCEL_MAX))
main_accel_cmd = 0. if self.CP.flags & ToyotaFlags.SECOC.value else pcm_accel_cmd
# Toyota's physical distance-button hold can collide with StarPilot's wheel-button
# actions and trip a temporary EPS fault. Suppress native long-press handling while
# the physical gap button is held so ACC only sees the hold as a plain button press.
allow_long_press = 0 if bool(getattr(CS, "distance_button", False)) else None
can_sends.append(toyotacan.create_accel_command(self.packer, main_accel_cmd, pcm_cancel_cmd, self.permit_braking, self.standstill_req, lead,
CS.acc_type, fcw_alert, self.distance_button, starpilot_toggles.reverse_cruise_increase))
CS.acc_type, fcw_alert, self.distance_button, starpilot_toggles.reverse_cruise_increase,
allow_long_press))
if self.CP.flags & ToyotaFlags.SECOC.value:
acc_cmd_2 = toyotacan.create_accel_command_2(self.packer, pcm_accel_cmd)
acc_cmd_2 = add_mac(self.secoc_key,
@@ -507,7 +512,9 @@ class CarController(CarControllerBase):
if self.CP.carFingerprint in UNSUPPORTED_DSU_CAR:
can_sends.append(toyotacan.create_acc_cancel_command(self.packer))
else:
can_sends.append(toyotacan.create_accel_command(self.packer, 0, pcm_cancel_cmd, True, False, lead, CS.acc_type, False, self.distance_button, starpilot_toggles.reverse_cruise_increase))
allow_long_press = 0 if bool(getattr(CS, "distance_button", False)) else None
can_sends.append(toyotacan.create_accel_command(self.packer, 0, pcm_cancel_cmd, True, False, lead, CS.acc_type, False,
self.distance_button, starpilot_toggles.reverse_cruise_increase, allow_long_press))
# *** hud ui ***
if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V:
@@ -357,6 +357,22 @@ class TestToyotaCarController:
assert parser.vl["LKAS_HUD"]["LEFT_LINE"] == 0
assert parser.vl["LKAS_HUD"]["RIGHT_LINE"] == 0
def test_acc_control_can_suppress_long_press_behavior_while_gap_button_is_held(self):
packer = CANPacker(DBC[CAR.TOYOTA_HIGHLANDER_TSS2][Bus.pt])
parser = CANParser(DBC[CAR.TOYOTA_HIGHLANDER_TSS2][Bus.pt], [("ACC_CONTROL", 0)], 0)
default_msg = toyotacan.create_accel_command(
packer, 0.0, False, True, False, False, 1, False, 0, False,
)
parser.update([(1, [default_msg])])
assert parser.vl["ACC_CONTROL"]["ALLOW_LONG_PRESS"] == 1
suppressed_msg = toyotacan.create_accel_command(
packer, 0.0, False, True, False, False, 1, False, 0, False, allow_long_press=0,
)
parser.update([(1, [suppressed_msg])])
assert parser.vl["ACC_CONTROL"]["ALLOW_LONG_PRESS"] == 0
def test_auto_brake_hold_sends_modified_pre_collision_after_timer(self):
controller = self._make_controller()
controller.packer = CANPacker(DBC[CAR.TOYOTA_CAMRY_TSS2][Bus.pt])
+6 -2
View File
@@ -40,8 +40,12 @@ def create_lta_steer_command_2(packer, frame):
return packer.make_can_msg("STEERING_LTA_2", 0, values)
def create_accel_command(packer, accel, pcm_cancel, permit_braking, standstill_req, lead, acc_type, fcw_alert, distance, reverse_cruise_active):
def create_accel_command(packer, accel, pcm_cancel, permit_braking, standstill_req, lead, acc_type, fcw_alert,
distance, reverse_cruise_active, allow_long_press=None):
# TODO: find the exact canceling bit that does not create a chime
if allow_long_press is None:
allow_long_press = 2 if reverse_cruise_active else 1
values = {
"ACCEL_CMD": accel,
"ACC_TYPE": acc_type,
@@ -50,7 +54,7 @@ def create_accel_command(packer, accel, pcm_cancel, permit_braking, standstill_r
"PERMIT_BRAKING": permit_braking,
"RELEASE_STANDSTILL": not standstill_req,
"CANCEL_REQ": pcm_cancel,
"ALLOW_LONG_PRESS": 2 if reverse_cruise_active else 1,
"ALLOW_LONG_PRESS": allow_long_press,
"ACC_CUT_IN": fcw_alert, # only shown when ACC enabled
}
return packer.make_can_msg("ACC_CONTROL", 0, values)
+7 -5
View File
@@ -86,6 +86,8 @@ static bool ford_get_quality_flag_valid(const CANPacket_t *msg) {
#define FORD_CANFD_INACTIVE_CURVATURE_RATE 1024U
static bool ford_lka_steering = false;
// Curvature rate limits
#define FORD_LIMITS(limit_lateral_acceleration) { \
.max_angle = 1000, /* 0.02 curvature */ \
@@ -227,12 +229,10 @@ static bool ford_tx_hook(const CANPacket_t *msg) {
// Safety check for Lane_Assist_Data1 action
if (msg->addr == FORD_Lane_Assist_Data1) {
// Do not allow steering using Lane_Assist_Data1 (Lane-Departure Aid).
// This message must be sent for Lane Centering to work, and can include
// values such as the steering angle or lane curvature for debugging,
// but the action (LkaActvStats_D2_Req) must be set to zero.
unsigned int action = msg->data[0] >> 5;
if (action != 0U) {
bool valid_lka_action = action == 0U;
valid_lka_action |= ford_lka_steering && controls_allowed && ((action == 2U) || (action == 4U));
if (!valid_lka_action) {
tx = false;
}
}
@@ -330,7 +330,9 @@ static safety_config ford_init(uint16_t param) {
};
const uint16_t FORD_PARAM_CANFD = 2;
const uint16_t FORD_PARAM_LKA_STEERING = 4;
const bool ford_canfd = GET_FLAG(param, FORD_PARAM_CANFD);
ford_lka_steering = GET_FLAG(param, FORD_PARAM_LKA_STEERING);
bool ford_longitudinal = false;
@@ -31,7 +31,9 @@ def is_steering_msg(mode, param, addr):
elif mode == CarParams.SafetyModel.subaru:
ret = addr == 0x122
elif mode == CarParams.SafetyModel.ford:
ret = addr == 0x3d6 if param & FordSafetyFlags.CANFD else addr == 0x3d3
ret = addr == (0x3ca if param & FordSafetyFlags.LKA_STEERING else
0x3d6 if param & FordSafetyFlags.CANFD else
0x3d3)
elif mode == CarParams.SafetyModel.nissan:
ret = addr == 0x169
elif mode == CarParams.SafetyModel.rivian:
@@ -67,7 +69,10 @@ def get_steer_value(mode, param, msg):
torque = ((msg.data[3] & 0x1F) << 8) | msg.data[2]
torque = -to_signed(torque, 13)
elif mode == CarParams.SafetyModel.ford:
if param & FordSafetyFlags.CANFD:
if param & FordSafetyFlags.LKA_STEERING:
action = msg.data[0] >> 5
angle = 1 if action in (2, 4) else 0
elif param & FordSafetyFlags.CANFD:
angle = ((msg.data[2] << 3) | (msg.data[3] >> 5)) - 1000
else:
angle = ((msg.data[0] << 3) | (msg.data[1] >> 5)) - 1000
+18 -6
View File
@@ -76,6 +76,7 @@ class TestFordSafetyBase(common.CarSafetyTest):
STEER_MESSAGE = 0
# Curvature control limits
LKA_STEERING = False
DEG_TO_CAN = 50000 # 1 / (2e-5) rad to can
MAX_CURVATURE = 0.02
MAX_CURVATURE_ERROR = 0.002
@@ -354,12 +355,13 @@ class TestFordSafetyBase(common.CarSafetyTest):
self._set_prev_desired_angle(sign * (curvature_offset + initial_curvature))
self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * (curvature_offset + desired_curvature), 0)))
def test_prevent_lkas_action(self):
self.safety.set_controls_allowed(1)
self.assertFalse(self._tx(self._lkas_command_msg(1)))
self.safety.set_controls_allowed(0)
self.assertFalse(self._tx(self._lkas_command_msg(1)))
def test_lkas_action(self):
for controls_allowed in (0, 1):
self.safety.set_controls_allowed(controls_allowed)
for action in range(8):
should_tx = action == 0
should_tx |= self.LKA_STEERING and controls_allowed and action in (2, 4)
self.assertEqual(should_tx, self._tx(self._lkas_command_msg(action)))
def test_acc_buttons(self):
for allowed in (0, 1):
@@ -484,6 +486,16 @@ class TestFordLongitudinalSafety(TestFordLongitudinalSafetyBase):
pass
class TestFordLKASteeringSafety(TestFordLongitudinalSafety):
LKA_STEERING = True
def setUp(self):
self.packer = CANPackerSafety("ford_lincoln_base_pt")
self.safety = libsafety_py.libsafety
self.safety.set_safety_hooks(CarParams.SafetyModel.ford, FordSafetyFlags.LKA_STEERING)
self.safety.init_tests()
class TestFordCANFDLongitudinalSafety(TestFordLongitudinalSafetyBase):
STEER_MESSAGE = MSG_LateralMotionControl2
@@ -66,9 +66,28 @@ def parse_args() -> argparse.Namespace:
action="store_true",
help="Mark three agreeing high-confidence regulatory model crops as strong consensus.",
)
parser.add_argument(
"--strong-model-min-proposal-confidence",
type=float,
help="Override proposal confidence required for a crop to contribute to strong model consensus.",
)
parser.add_argument(
"--strong-model-consensus-min-read-confidence",
type=float,
help="Override classifier confidence required for a crop to contribute to strong model consensus.",
)
parser.add_argument(
"--strong-model-consensus-min-support",
type=int,
help="Override the number of agreeing classifier crops required for strong model consensus.",
)
parser.add_argument("--initial-speed-limit", type=int, default=0, help="Seed each replay window with a currently published speed limit.")
parser.add_argument("--positive-only", action="store_true", help="Replay only reviewed speed signs, omitting ignored-crop windows.")
parser.add_argument("--negative-only", action="store_true", help="Replay only ignored not-speed-limit windows.")
parser.add_argument("--route-file", type=Path, help="Only replay routes listed one per line in this file.")
parser.add_argument("--strong-detection-confidence", type=float, help="Override one-frame publication confidence.")
parser.add_argument("--consistent-detections", type=int, help="Override matching reads required for an initial publication.")
parser.add_argument("--change-consistent-detections", type=int, help="Override matching reads required to change a publication.")
parser.add_argument("--max-cases", type=int, default=0, help="Optional evaluation cap after deduplication.")
return parser.parse_args()
@@ -111,7 +130,7 @@ def load_cases(queue_path: Path, labels_path: Path, dedupe_seconds: float) -> li
return cases
def replay_video_cases(cases: list[ReviewedCase], args: argparse.Namespace) -> dict[str, tuple[list[int], list[int], int]]:
def replay_video_cases(cases: list[ReviewedCase], args: argparse.Namespace) -> dict[str, tuple[list[dict[str, str]], int]]:
daemons = {
case.record_key: RouteReplayDaemon(
runtime_context=None,
@@ -160,9 +179,7 @@ def replay_video_cases(cases: list[ReviewedCase], args: argparse.Namespace) -> d
results = {}
for case in cases:
daemon = daemons[case.record_key]
candidates = [int(event["candidateSpeedLimitMph"]) for event in daemon.events if event["event"] == "candidate"]
publishes = [int(event["speedLimitMph"]) for event in daemon.events if event["event"] == "publish"]
results[case.record_key] = candidates, publishes, daemon.inference_frames
results[case.record_key] = daemon.events, daemon.inference_frames
return results
@@ -195,6 +212,20 @@ def main() -> int:
slv.LOW_SPEED_CHANGE_ALLOW_STRONG_CONSENSUS = True
if args.enable_strong_model_consensus:
slv.DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_ENABLED = True
if args.strong_model_min_proposal_confidence is not None:
slv.DETECTOR_CLASSIFIER_STRONG_MODEL_MIN_PROPOSAL_CONFIDENCE = args.strong_model_min_proposal_confidence
if args.strong_model_consensus_min_read_confidence is not None:
slv.DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_MIN_READ_CONFIDENCE = args.strong_model_consensus_min_read_confidence
if args.strong_model_consensus_min_support is not None:
if args.strong_model_consensus_min_support < 1:
raise ValueError("--strong-model-consensus-min-support must be at least 1")
slv.DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_MIN_SUPPORT = args.strong_model_consensus_min_support
if args.strong_detection_confidence is not None:
slv.STRONG_DETECTION_CONFIDENCE = args.strong_detection_confidence
if args.consistent_detections is not None:
slv.CONSISTENT_DETECTIONS = args.consistent_detections
if args.change_consistent_detections is not None:
slv.CHANGE_CONSISTENT_DETECTIONS = args.change_consistent_detections
cases = load_cases(queue_path, labels_path, args.dedupe_seconds)
if args.route_file:
selected_routes = {
@@ -203,13 +234,17 @@ def main() -> int:
cases = [case for case in cases if case.route in selected_routes]
if args.positive_only:
cases = [case for case in cases if not case.negative]
if args.negative_only:
cases = [case for case in cases if case.negative]
if args.positive_only and args.negative_only:
raise ValueError("--positive-only and --negative-only are mutually exclusive")
if args.max_cases > 0:
cases = cases[:args.max_cases]
output_rows: list[dict[str, object]] = []
positive_by_speed: dict[int, Counter[str]] = defaultdict(Counter)
negative_counts: Counter[str] = Counter()
results: dict[str, tuple[list[int], list[int], int]] = {}
results: dict[str, tuple[list[dict[str, str]], int]] = {}
cases_by_video: dict[Path, list[ReviewedCase]] = defaultdict(list)
for case in cases:
cases_by_video[case.source_video_path].append(case)
@@ -219,9 +254,15 @@ def main() -> int:
print(f"Replayed {index}/{len(cases_by_video)} video segments", flush=True)
for case in cases:
candidates, publishes, inference_frames = results.get(case.record_key, ([], [], 0))
events, inference_frames = results.get(case.record_key, ([], 0))
candidate_events = [event for event in events if event["event"] == "candidate"]
publish_events = [event for event in events if event["event"] == "publish"]
candidates = [int(event["candidateSpeedLimitMph"]) for event in candidate_events]
publishes = [int(event["speedLimitMph"]) for event in publish_events]
candidate_hit = case.expected_speed_limit_mph in candidates if not case.negative else False
publish_hit = case.expected_speed_limit_mph in publishes if not case.negative else False
wrong_candidate_values = [] if case.negative else [value for value in candidates if value != case.expected_speed_limit_mph]
wrong_publish_values = [] if case.negative else [value for value in publishes if value != case.expected_speed_limit_mph]
false_candidate = bool(candidates) if case.negative else False
false_publish = bool(publishes) if case.negative else False
if case.negative:
@@ -231,6 +272,8 @@ def main() -> int:
total=1,
candidate_hit=int(candidate_hit),
publish_hit=int(publish_hit),
wrong_candidate=int(bool(wrong_candidate_values)),
wrong_publish=int(bool(wrong_publish_values)),
)
output_rows.append({
"record_key": case.record_key,
@@ -241,8 +284,15 @@ def main() -> int:
"negative": case.negative,
"candidate_values": "|".join(str(value) for value in candidates),
"publish_values": "|".join(str(value) for value in publishes),
"candidate_confidences": "|".join(event.get("candidateConfidence", "") for event in candidate_events),
"candidate_strong_consensus": "|".join(event.get("candidateStrongConsensus", "") for event in candidate_events),
"publish_confidences": "|".join(event.get("confidence", "") for event in publish_events),
"candidate_hit": candidate_hit,
"publish_hit": publish_hit,
"wrong_candidate_values": "|".join(str(value) for value in wrong_candidate_values),
"wrong_publish_values": "|".join(str(value) for value in wrong_publish_values),
"wrong_candidate": bool(wrong_candidate_values),
"wrong_publish": bool(wrong_publish_values),
"false_candidate": false_candidate,
"false_publish": false_publish,
"inference_frames": inference_frames,
@@ -274,6 +324,9 @@ def main() -> int:
"low_speed_change_consistent_detections": slv.LOW_SPEED_CHANGE_CONSISTENT_DETECTIONS,
"low_speed_change_allow_strong_consensus": slv.LOW_SPEED_CHANGE_ALLOW_STRONG_CONSENSUS,
"strong_model_consensus_enabled": slv.DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_ENABLED,
"strong_model_min_proposal_confidence": slv.DETECTOR_CLASSIFIER_STRONG_MODEL_MIN_PROPOSAL_CONFIDENCE,
"strong_model_consensus_min_read_confidence": slv.DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_MIN_READ_CONFIDENCE,
"strong_model_consensus_min_support": slv.DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_MIN_SUPPORT,
"positive": dict(totals),
"positive_by_speed": {str(speed): dict(counts) for speed, counts in sorted(positive_by_speed.items())},
"negative": dict(negative_counts),
+2 -1
View File
@@ -13,6 +13,7 @@ from openpilot.common.realtime import config_realtime_process
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose, fft_next_good_size, parabolic_peak_interp
from openpilot.starpilot.common.lateral_delay import full_lateral_delay
from openpilot.starpilot.common.starpilot_variables import get_starpilot_toggles
BLOCK_SIZE = 100
@@ -180,7 +181,7 @@ class LateralLagEstimator:
self.window_sec = window_sec
self.okay_window_sec = okay_window_sec
self.min_recovery_buffer_sec = min_recovery_buffer_sec
self.initial_lag = CP.steerActuatorDelay + 0.2
self.initial_lag = full_lateral_delay(CP.steerActuatorDelay)
self.block_size = block_size
self.block_count = block_count
self.min_valid_block_count = min_valid_block_count
@@ -313,11 +313,20 @@ class StarPilotLateralLayout(_SettingsPage):
disabled_label=tr_noop("Not Available"),
visible=alt_on,
),
SettingRow(
"UseAutoSteerDelay", "toggle", tr_noop("Use Auto-Learned Delay"),
subtitle=tr_noop("Learn the full steering delay automatically. The manual value below is ignored while enabled."),
get_state=lambda: p.get_bool("UseAutoSteerDelay"),
set_state=lambda s: p.put_bool("UseAutoSteerDelay", s),
visible=lambda: alt_on() and cs.steerActuatorDelay != 0,
),
SettingRow(
"SteerDelay", "value", tr_noop("Actuator Delay"),
subtitle=tr_noop("Time between steering command and vehicle response."),
subtitle=tr_noop("Exact full delay between steering command and vehicle response."),
get_value=lambda: f"{p.get_float('SteerDelay'):.2f}s",
on_click=lambda: self._show_slider("SteerDelay", 0.01, 1.0, step=0.01, unit="s", value_type="float"),
enabled=lambda: not p.get_bool("UseAutoSteerDelay"),
disabled_label=tr_noop("Disabled while auto-learned delay is enabled."),
visible=lambda: alt_on() and cs.steerActuatorDelay != 0,
),
SettingRow(
+4 -1
View File
@@ -8,6 +8,7 @@ from openpilot.selfdrive.ui.ui_state import ui_state
from cereal import car, log, custom, messaging
from opendbc.car.gm.values import GMFlags
from opendbc.car.hyundai.values import HyundaiFlags
from openpilot.starpilot.common.lateral_delay import full_lateral_delay
@dataclass
class StarPilotCarState:
@@ -179,7 +180,9 @@ class StarPilotState:
)
self.car_state.longitudinalActuatorDelay = float(self._safe_get(CP, "longitudinalActuatorDelay", self.car_state.longitudinalActuatorDelay))
self.car_state.startAccel = float(self._safe_get(CP, "startAccel", self.car_state.startAccel))
self.car_state.steerActuatorDelay = float(self._safe_get(CP, "steerActuatorDelay", self.car_state.steerActuatorDelay))
vehicle_steer_delay = self._safe_get(CP, "steerActuatorDelay", None)
if vehicle_steer_delay is not None:
self.car_state.steerActuatorDelay = full_lateral_delay(vehicle_steer_delay)
self.car_state.steerRatio = float(self._safe_get(CP, "steerRatio", self.car_state.steerRatio))
self.car_state.stopAccel = float(self._safe_get(CP, "stopAccel", self.car_state.stopAccel))
self.car_state.stoppingDecelRate = float(self._safe_get(CP, "stoppingDecelRate", self.car_state.stoppingDecelRate))
+5
View File
@@ -0,0 +1,5 @@
LATERAL_CONTROL_SOFTWARE_DELAY = 0.2
def full_lateral_delay(vehicle_delay: float) -> float:
return float(vehicle_delay) + LATERAL_CONTROL_SOFTWARE_DELAY
+2
View File
@@ -26,6 +26,7 @@ SAFE_MODE_MANAGED_KEYS = (
"AdvancedLateralTune",
"ForceAutoTune",
"ForceAutoTuneOff",
"UseAutoSteerDelay",
"ForceTorqueController",
"SteerDelay",
"SteerFriction",
@@ -196,6 +197,7 @@ SAFE_MODE_MANAGED_KEYS = (
SAFE_MODE_FIXED_VALUES = {
"ExperimentalMode": False,
"LongitudinalPersonality": int(log.LongitudinalPersonality.relaxed),
"UseAutoSteerDelay": True,
}
SAFE_MODE_STOCK_PARAM_MAP = {
+33 -5
View File
@@ -27,6 +27,7 @@ from openpilot.common.params import Params
from openpilot.selfdrive.controls.lib.latcontrol_torque import KP
from openpilot.selfdrive.modeld.constants import ModelConstants
from openpilot.starpilot.common.model_versions import is_tinygrad_model_version
from openpilot.starpilot.common.lateral_delay import full_lateral_delay
from openpilot.starpilot.common.accel_profile import (
ACCELERATION_PROFILES,
CUSTOM_ACCEL_PROFILE_PARAM_KEYS,
@@ -93,6 +94,8 @@ PRIUS_CLUSTER_OFFSET_CARS = {
str(TOYOTA_CAR.TOYOTA_PRIUS_TSS2),
}
STEER_DELAY_MODE_MIGRATION_KEY = "SteerDelayModeMigrated"
RESOURCES_REPO = os.getenv("STARPILOT_RESOURCES_REPO", "firestar5683/StarPilot-Resources")
ACTIVE_THEME_PATH = Path(BASEDIR) / "starpilot/assets/active_theme"
@@ -555,6 +558,29 @@ class StarPilotVariables:
self.params.put_float(stock_key, live_value)
def _migrate_steer_delay_mode(self, vehicle_delay: float) -> None:
if self.params_raw.get_bool(STEER_DELAY_MODE_MIGRATION_KEY):
return
def parse_value(raw_value):
try:
return float(raw_value)
except (TypeError, ValueError):
return None
current_delay = parse_value(self.params_raw.get("SteerDelay"))
previous_stock = parse_value(self.params_raw.get("SteerDelayStock"))
full_stock_delay = full_lateral_delay(vehicle_delay)
use_auto_delay = current_delay is None or math.isclose(current_delay, 0.0, abs_tol=1e-6)
use_auto_delay |= current_delay is not None and math.isclose(current_delay, vehicle_delay, abs_tol=1e-6)
use_auto_delay |= current_delay is not None and previous_stock is not None and math.isclose(current_delay, previous_stock, abs_tol=1e-6)
self.params.put_bool("UseAutoSteerDelay", use_auto_delay)
if use_auto_delay:
self.params.put_float("SteerDelay", full_stock_delay)
self.params.put_bool(STEER_DELAY_MODE_MIGRATION_KEY, True)
def update(self, holiday_theme="stock", started=False):
toggle = self.starpilot_toggles
toggle.tuning_level = self.params.get("TuningLevel") if self.params.get_bool("TuningLevelConfirmed") else TUNING_LEVELS["ADVANCED"]
@@ -624,15 +650,16 @@ class StarPilotVariables:
startAccel = CP.startAccel
stopAccel = CP.stopAccel
steerActuatorDelay = CP.steerActuatorDelay
fullSteerActuatorDelay = full_lateral_delay(steerActuatorDelay)
steerKp = KP
steerRatio = CP.steerRatio
toggle.stoppingDecelRate = CP.stoppingDecelRate
toggle.vEgoStarting = CP.vEgoStarting
toggle.vEgoStopping = CP.vEgoStopping
# Keep stock tuning params synchronized for all device UIs (Qt + raylib).
# Historically this only ran in Qt settings, which left C4 defaults at 0.
self._sync_stock_param("SteerDelay", "SteerDelayStock", steerActuatorDelay)
# Keep stock tuning params synchronized for all device UIs.
self._migrate_steer_delay_mode(steerActuatorDelay)
self._sync_stock_param("SteerDelay", "SteerDelayStock", fullSteerActuatorDelay)
self._sync_stock_param("SteerFriction", "SteerFrictionStock", friction)
self._sync_stock_param("SteerKP", "SteerKPStock", steerKp)
self._sync_stock_param("SteerLatAccel", "SteerLatAccelStock", latAccelFactor)
@@ -687,8 +714,9 @@ class StarPilotVariables:
toggle.ftm_active_overrides = json.loads(ftm_overrides_raw) if ftm_overrides_raw else {}
except Exception:
toggle.ftm_active_overrides = {}
toggle.steerActuatorDelay = self.get_value("SteerDelay", cast=float, condition=advanced_lateral_tuning, default=steerActuatorDelay, min=0.01, max=1.0)
toggle.use_custom_steerActuatorDelay = bool(round(toggle.steerActuatorDelay, 2) != round(steerActuatorDelay, 2))
toggle.use_auto_steer_delay = self.get_value("UseAutoSteerDelay", condition=advanced_lateral_tuning, default=True)
toggle.steerActuatorDelay = self.get_value("SteerDelay", cast=float, condition=advanced_lateral_tuning, default=fullSteerActuatorDelay, min=0.01, max=1.0)
toggle.use_custom_steerActuatorDelay = advanced_lateral_tuning and not toggle.use_auto_steer_delay
toggle.friction = self.get_value("SteerFriction", cast=float, condition=advanced_lateral_tuning, default=friction, min=0, max=1)
toggle.use_custom_friction = bool(round(toggle.friction, 2) != round(friction, 2)) and is_torque_car and not toggle.force_auto_tune or toggle.force_auto_tune_off
toggle.steerKp = [[0], [self.get_value("SteerKP", cast=float, condition=advanced_lateral_tuning and is_torque_car and not is_angle_car, default=steerKp, min=steerKp * 0.5, max=steerKp * 1.5)]]
@@ -44,6 +44,13 @@ class _FakeParams:
def get_bool(self, key):
return bool(self.bools.get(key, False))
def get(self, key):
if key in self.floats:
return self.floats[key]
if key in self.ints:
return self.ints[key]
return self.bools.get(key)
def put_float(self, key, value):
self.floats[key] = float(value)
@@ -70,6 +77,32 @@ def test_sync_stock_param_does_not_stomp_existing_custom_value_when_stock_missin
assert params.get_float("SteerDelayStock") == 0.10
def test_steer_delay_mode_migration_converts_untouched_stock_value_to_full_auto_delay():
params = _FakeParams({"SteerDelay": 0.11, "SteerDelayStock": 0.11})
variables = object.__new__(spv.StarPilotVariables)
variables.params = params
variables.params_raw = params
variables._migrate_steer_delay_mode(0.11)
assert params.get_bool("UseAutoSteerDelay") is True
assert params.get_float("SteerDelay") == 0.31
assert params.get_bool(spv.STEER_DELAY_MODE_MIGRATION_KEY) is True
def test_steer_delay_mode_migration_preserves_existing_manual_full_delay():
params = _FakeParams({"SteerDelay": 0.35, "SteerDelayStock": 0.11})
variables = object.__new__(spv.StarPilotVariables)
variables.params = params
variables.params_raw = params
variables._migrate_steer_delay_mode(0.11)
assert params.get_bool("UseAutoSteerDelay") is False
assert params.get_float("SteerDelay") == 0.35
assert params.get_bool(spv.STEER_DELAY_MODE_MIGRATION_KEY) is True
def test_cancel_button_migration_copies_distance_actions_once():
params = _FakeParams(
ints={
+19 -3
View File
@@ -40,6 +40,10 @@ class StarPilotCard:
self.hyundai_preserve_aol_across_reverse = getattr(self.CP, "carFingerprint", None) == HYUNDAI_CAR.HYUNDAI_SONATA_HYBRID
self.hyundai_aol_needs_engagement = self.CP.brand == "hyundai" and not (hyundai_flags & HyundaiFlags.CANFD) and not kia_forte_non_scc
self.hyundai_aol_ready = False
# Nissan's angle-control EPS can fault if AOL starts while the wheel is at a
# large parking angle. Require one normal openpilot engagement before AOL.
self.nissan_aol_needs_engagement = self.CP.brand == "nissan"
self.nissan_aol_ready = False
self.prev_active = False
self.prev_cruise_enabled = False
self.decel_pressed = False
@@ -114,6 +118,7 @@ class StarPilotCard:
def update(self, carState, starpilotCarState, sm, starpilot_toggles):
self.switchback_mode_enabled = self.params_memory.get_bool("SwitchbackModeEnabled")
button_event_types = [self._button_type_raw(be) for be in carState.buttonEvents]
button_managed_aol = starpilot_toggles.always_on_lateral_lkas or starpilot_toggles.main_cruise_aol_toggle
if self.hyundai_aol_needs_engagement:
if carState.gearShifter in NON_DRIVING_GEARS:
@@ -124,6 +129,13 @@ class StarPilotCard:
elif sm["selfdriveState"].active or carState.cruiseState.enabled:
self.hyundai_aol_ready = True
if self.nissan_aol_needs_engagement:
if carState.gearShifter in NON_DRIVING_GEARS:
self.nissan_aol_ready = False
self.always_on_lateral_allowed = False
elif sm["selfdriveState"].active:
self.nissan_aol_ready = True
if self.CP.brand == "hyundai" or starpilot_toggles.lkas_allowed_for_aol:
for be, be_type in zip(carState.buttonEvents, button_event_types, strict=False):
if be_type == ButtonType.lkas and be.pressed and starpilot_toggles.always_on_lateral_lkas:
@@ -139,8 +151,10 @@ class StarPilotCard:
self.always_on_lateral_allowed = not self.always_on_lateral_allowed
elif starpilot_toggles.main_cruise_slc_adopt and starpilot_toggles.speed_limit_controller:
self.params_memory.put_bool("SLCAdoptSpeedLimit", True)
elif starpilot_toggles.always_on_lateral_main:
if pacifica_hybrid_aol_requires_set_press(self.CP.carFingerprint, self.CP.pcmCruise):
if starpilot_toggles.always_on_lateral_main and not button_managed_aol:
car_fingerprint = getattr(self.CP, "carFingerprint", None)
pcm_cruise = getattr(self.CP, "pcmCruise", False)
if pacifica_hybrid_aol_requires_set_press(car_fingerprint, pcm_cruise):
# Chrysler Pacifica Hybrid stock ACC can fall back to plain cruise if AOL
# starts steering before the driver presses SET.
if not carState.cruiseState.available:
@@ -163,9 +177,11 @@ class StarPilotCard:
self.always_on_lateral_enabled = self.always_on_lateral_allowed and self.always_on_lateral_set
self.always_on_lateral_enabled &= carState.gearShifter not in NON_DRIVING_GEARS
self.always_on_lateral_enabled &= not self.hyundai_aol_needs_engagement or self.hyundai_aol_ready
self.always_on_lateral_enabled &= not self.nissan_aol_needs_engagement or self.nissan_aol_ready
self.always_on_lateral_enabled &= sm["starpilotPlan"].lateralCheck
self.always_on_lateral_enabled &= sm["liveCalibration"].calPerc >= 1
self.always_on_lateral_enabled &= (ET.IMMEDIATE_DISABLE not in sm["selfdriveState"].alertType + sm["starpilotSelfdriveState"].alertType) or self.frogs_go_moo
alert_types = sm["selfdriveState"].alertType + sm["starpilotSelfdriveState"].alertType
self.always_on_lateral_enabled &= ET.IMMEDIATE_DISABLE not in alert_types or self.frogs_go_moo
self.always_on_lateral_enabled &= not (carState.brakePressed and carState.vEgo < starpilot_toggles.always_on_lateral_pause_speed) or carState.standstill
self.always_on_lateral_enabled &= not self.error_log.is_file() or self.frogs_go_moo
@@ -215,6 +215,63 @@ def test_hyundai_aol_does_not_auto_start_from_cruise_availability(monkeypatch, t
assert ret.alwaysOnLateralEnabled is False
def test_nissan_aol_requires_normal_engagement(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path)
card = spc.StarPilotCard(
SimpleNamespace(brand="nissan"),
SimpleNamespace(alternativeExperience=spc.ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL),
)
assert card.nissan_aol_needs_engagement is True
assert card.nissan_aol_ready is False
starpilot_car_state = SimpleNamespace(distancePressed=False)
sm = make_sm()
toggles = make_toggles(always_on_lateral=True, main_cruise_aol_toggle=True, lkas_allowed_for_aol=True)
car_state = make_car_state(available=True, button_events=[SimpleNamespace(type=spc.ButtonType.mainCruise, pressed=True)])
ret = card.update(car_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is True
assert ret.alwaysOnLateralEnabled is False
sm["selfdriveState"].active = True
ret = card.update(make_car_state(available=True, enabled=True), starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralEnabled is True
sm["selfdriveState"].active = False
ret = card.update(make_car_state(available=True), starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralEnabled is True
def test_nissan_aol_engagement_latch_resets_out_of_drive(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path)
card = spc.StarPilotCard(
SimpleNamespace(brand="nissan"),
SimpleNamespace(alternativeExperience=spc.ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL),
)
starpilot_car_state = SimpleNamespace(distancePressed=False)
sm = make_sm()
sm["selfdriveState"].active = True
toggles = make_toggles(always_on_lateral=True, always_on_lateral_main=True)
ret = card.update(make_car_state(available=True, enabled=True), starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralEnabled is True
sm["selfdriveState"].active = False
park_state = make_car_state(available=True)
park_state.gearShifter = spc.GearShifter.park
ret = card.update(park_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralEnabled is False
assert card.nissan_aol_ready is False
ret = card.update(make_car_state(available=True), starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralEnabled is False
def test_hyundai_canfd_lkas_button_can_toggle_aol_before_engagement(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
@@ -371,6 +428,7 @@ def test_honda_lkas_button_pauses_lateral_when_cruise_is_active(monkeypatch, tmp
sm = make_sm()
sm["selfdriveState"].active = True
toggles = make_toggles(always_on_lateral_lkas=True, lkas_allowed_for_aol=True)
card.prev_active = True
ret = card.update(car_state, starpilot_car_state, sm, toggles)
@@ -383,6 +441,51 @@ def test_honda_lkas_button_pauses_lateral_when_cruise_is_active(monkeypatch, tmp
assert ret.pauseLateral is False
def test_honda_main_aol_follows_cruise_main_without_manual_aol_button_mapping(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path)
card = spc.StarPilotCard(
SimpleNamespace(brand="honda"),
SimpleNamespace(alternativeExperience=spc.ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL),
)
toggles = make_toggles(always_on_lateral_main=True, lkas_allowed_for_aol=True)
ret = card.update(make_car_state(available=True), SimpleNamespace(distancePressed=False), make_sm(), toggles)
assert ret.alwaysOnLateralAllowed is True
assert ret.alwaysOnLateralEnabled is True
def test_hyundai_main_aol_persists_after_brake_disengage_without_manual_aol_button_mapping(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path)
card = spc.StarPilotCard(
SimpleNamespace(brand="hyundai"),
SimpleNamespace(alternativeExperience=spc.ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL),
)
sm = make_sm()
toggles = make_toggles(always_on_lateral_main=True)
starpilot_car_state = SimpleNamespace(distancePressed=False)
sm["selfdriveState"].active = True
enabled_state = make_car_state(available=True, enabled=True)
ret = card.update(enabled_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is True
assert ret.alwaysOnLateralEnabled is True
sm["selfdriveState"].active = False
disengaged_state = make_car_state(available=True, enabled=False)
ret = card.update(disengaged_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is True
assert ret.alwaysOnLateralEnabled is True
def test_main_aol_still_follows_cruise_main_for_other_platforms(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
+11 -2
View File
@@ -200,8 +200,9 @@ DETECTOR_CLASSIFIER_TRUSTED_MODEL_MIN_READ_CONFIDENCE = 0.65
DETECTOR_CLASSIFIER_TRUSTED_MODEL_MIN_SUPPORT = 2
DETECTOR_CLASSIFIER_STRONG_MODEL_MIN_PROPOSAL_CONFIDENCE = 0.60
DETECTOR_CLASSIFIER_STRONG_MODEL_MIN_READ_CONFIDENCE = 0.995
DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_MIN_READ_CONFIDENCE = 0.95
DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_ENABLED = True
DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_MIN_SUPPORT = 3
DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_MIN_SUPPORT = 2
DETECTOR_CLASSIFIER_MODEL_ONLY_CONSENSUS_MIN_CONFIDENCE = 0.90
DETECTOR_CLASSIFIER_MODEL_ONLY_CONSENSUS_MIN_SUPPORT = 2
SCHOOL_ZONE_SPEED_PRIOR = 0.12
@@ -1475,6 +1476,13 @@ class SpeedLimitVisionDaemon:
proposal_confidence >= DETECTOR_CLASSIFIER_STRONG_MODEL_MIN_PROPOSAL_CONFIDENCE and
model_read[1] >= DETECTOR_CLASSIFIER_STRONG_MODEL_MIN_READ_CONFIDENCE
)
strong_model_consensus_read = (
class_id == 0 and
model_read is not None and
not is_small_box and
proposal_confidence >= DETECTOR_CLASSIFIER_STRONG_MODEL_MIN_PROPOSAL_CONFIDENCE and
model_read[1] >= DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_MIN_READ_CONFIDENCE
)
model_only_consensus_read = (
not DETECTOR_CLASSIFIER_CROP_OCR_ENABLED and
class_id == 0 and
@@ -1527,7 +1535,7 @@ class SpeedLimitVisionDaemon:
speed_regulatory_support[speed_limit_mph] = speed_regulatory_support.get(speed_limit_mph, 0) + 1
if trusted_model_read:
speed_trusted_model_support[speed_limit_mph] = speed_trusted_model_support.get(speed_limit_mph, 0) + 1
if strong_model_read:
if strong_model_consensus_read:
speed_strong_model_support[speed_limit_mph] = speed_strong_model_support.get(speed_limit_mph, 0) + 1
if needs_ocr_confirmation and model_only_consensus_read:
speed_model_only_rescue_support[speed_limit_mph] = speed_model_only_rescue_support.get(speed_limit_mph, 0) + 1
@@ -2109,6 +2117,7 @@ class SpeedLimitVisionDaemon:
"candidate",
candidateSpeedLimitMph=detection.speed_limit_mph,
candidateConfidence=round(detection.confidence, 4),
candidateStrongConsensus=detection.strong_consensus,
)
self.history.append(HistoryEntry(detection.speed_limit_mph, detection.confidence, now, detection.strong_consensus))
@@ -15,11 +15,11 @@ def daemon_with_history(current_speed, entries):
def test_speed_change_requires_two_matching_reads():
daemon = daemon_with_history(40, [(55, 0.70)])
daemon = daemon_with_history(40, [(55, 0.95)])
assert daemon._confirm_detection() is None
daemon.history.append(HistoryEntry(55, 0.76, 1.0))
assert daemon._confirm_detection() == pytest.approx((55, 0.76))
assert daemon._confirm_detection() == pytest.approx((55, 0.95))
def test_speed_change_accepts_single_strong_consensus_read():
@@ -69,8 +69,9 @@ def test_detector_classifier_runtime_reads_regulatory_sign_without_ocr(model_onl
assert detection.speed_limit_mph == 55
def test_detector_classifier_marks_three_strong_model_crops_as_consensus(model_only_runtime):
daemon = detector_classifier_daemon(regulatory=True, model_read=(20, 0.999), proposal_confidence=0.80)
def test_detector_classifier_marks_two_strong_model_crops_as_consensus(model_only_runtime):
reads = iter(((20, 0.96), (20, 0.97), None))
daemon = detector_classifier_daemon(regulatory=True, model_read=lambda _crop: next(reads), proposal_confidence=0.80)
detection = daemon._detect_sign_from_detector_classifier(np.zeros((480, 960, 3), dtype=np.uint8))
assert detection is not None
@@ -426,11 +426,13 @@ function renderStorage(storage) {
function renderVitals(device) {
const uptime = device.uptimeSeconds == null ? "unknown" : formatDuration(device.uptimeSeconds);
const cpu = device.cpuTempC == null ? "unknown" : `${formatInt(device.cpuTempC)} C`;
const lanIp = device.lanIp || "unknown";
return `
<section class="dashboard-card dashboard-device-card">
<h2>Vitals</h2>
<div class="dashboard-key-values">
<div><span>Status</span><strong>${escapeHtml(device.status || "Parked")}</strong></div>
<div><span>LAN IP</span><strong>${escapeHtml(lanIp)}</strong></div>
<div><span>Uptime</span><strong>${escapeHtml(uptime)}</strong></div>
<div><span>CPU temp</span><strong>${escapeHtml(cpu)}</strong></div>
</div>
@@ -17,7 +17,7 @@ let lastFtmWorkspaceFetch = 0
const DYNAMIC_DEFAULT_DEP_KEYS = new Set(["AccelerationProfile", "EVTuning", "TruckTuning"])
const PANDA_FIRMWARE_TOGGLE_KEYS = new Set(["IgnoreIgnitionLine", "RemoteStartBootsComma", "HKGRemoteStartBootsComma"])
const FTM_ADVANCED_LATERAL_KEYS = new Set([
"AdvancedLateralTune", "ForceAutoTune", "ForceAutoTuneOff", "SteerDelay",
"AdvancedLateralTune", "ForceAutoTune", "ForceAutoTuneOff", "UseAutoSteerDelay", "SteerDelay",
"SteerFriction", "SteerKP", "SteerLatAccel", "SteerRatio",
])
@@ -1020,6 +1020,9 @@ function clearSearchFilter() {
const cancelButtonKeys = new Set(["CancelButtonControl", "LongCancelButtonControl", "VeryLongCancelButtonControl"])
function getSettingLockReason(param) {
if (param?.disabled_when_key_true && state.values[param.disabled_when_key_true]) {
return param.disabled_reason || "Disabled by another setting."
}
return ""
}
@@ -1256,7 +1259,7 @@ function renderSettingRow(p) {
<div class="ds-stepper">
<button
class="ds-stepper-btn"
disabled="${() => !canDecrease || false}"
disabled="${() => isLocked || !canDecrease || false}"
@click="${() => stepNumericParam(p, -1)}">-</button>
<div class="ds-stepper-meta">
<span>${formatSliderValue(bounds.min, String(bounds.step), p.precision, p.key)} to ${formatSliderValue(bounds.max, String(bounds.step), p.precision, p.key)}</span>
@@ -1270,7 +1273,7 @@ function renderSettingRow(p) {
min="${bounds.min}"
max="${bounds.max}"
step="${bounds.step}"
disabled="${() => updating}"
disabled="${() => isLocked || updating}"
value="${() => formatNumericForInput(resolveCurrentNumericValue(p, numericBounds(p)), precision)}"
@keydown="${(e) => {
if (e.key !== "Enter") return
@@ -1279,17 +1282,17 @@ function renderSettingRow(p) {
}}" />
<button
class="ds-apply-btn"
disabled="${() => updating}"
disabled="${() => isLocked || updating}"
@click="${() => applyManualNumericParam(p)}">Apply</button>
</div>
<button
class="ds-reset-btn"
disabled="${() => !canReset || false}"
disabled="${() => isLocked || !canReset || false}"
@click="${() => resetNumericParam(p)}">Reset to Default</button>
</div>
<button
class="ds-stepper-btn"
disabled="${() => !canIncrease || false}"
disabled="${() => isLocked || !canIncrease || false}"
@click="${() => stepNumericParam(p, 1)}">+</button> </div>
`
})()}
@@ -24,16 +24,26 @@
"ui_type": "toggle",
"is_parent_toggle": true
},
{
"key": "UseAutoSteerDelay",
"label": "Use Auto-Learned Delay",
"description": "Automatically learn the full steering delay. The manual actuator delay is ignored while enabled.",
"data_type": "bool",
"ui_type": "toggle",
"parent_key": "AdvancedLateralTune"
},
{
"key": "SteerDelay",
"label": "Actuator Delay",
"description": "Actuator Delay",
"description": "Exact full delay between steering command and vehicle response.",
"data_type": "float",
"ui_type": "numeric",
"min": 0.01,
"max": 1.0,
"step": 0.01,
"precision": 2,
"disabled_when_key_true": "UseAutoSteerDelay",
"disabled_reason": "Auto-learned delay is enabled.",
"parent_key": "AdvancedLateralTune"
},
{
@@ -3633,9 +3643,9 @@
"icon": "bi-exclamation-triangle",
"params": [
{
"key": "TryRaylibUI",
"label": "Try raylib UI",
"description": "Use the beta raylib UI instead of the default Qt UI on tici/tizi devices. This setting has no effect on C4/mici devices.",
"key": "UseOldUI",
"label": "Use Old UI",
"description": "Use the old Qt UI instead of the default raylib UI on tici/tizi devices. This setting has no effect on C4/mici devices.",
"data_type": "bool",
"ui_type": "toggle"
},
@@ -543,6 +543,7 @@ function mergedFtmOverrides() {
function formatTuneComparisonValue(value) {
if (Array.isArray(value)) return renderCurve(value)
if (typeof value === "boolean") return value ? "On" : "Off"
const numeric = Number(value)
return Number.isFinite(numeric) ? numeric.toFixed(3) : String(value ?? "-")
}
@@ -556,6 +557,7 @@ function tuneComparisonRows() {
const rows = [
["Lat accel", "SteerLatAccel"],
["Friction", "SteerFriction"],
["Auto steer delay", "UseAutoSteerDelay"],
["Steer delay", "SteerDelay"],
["Steer ratio", "SteerRatio"],
["KP", "SteerKP"],
+10 -2
View File
@@ -30,6 +30,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import (
get_standard_friction_threshold,
normalize_ftm_overrides,
)
from openpilot.starpilot.common.lateral_delay import full_lateral_delay
from openpilot.system.hardware import PC
from openpilot.system.hardware.hw import Paths
from openpilot.tools.lib.logreader import LogReader
@@ -47,6 +48,7 @@ TRIAL_PARAM_SPECS = {
"AdvancedLateralTune": "bool",
"ForceAutoTune": "bool",
"ForceAutoTuneOff": "bool",
"UseAutoSteerDelay": "bool",
"SteerDelay": "float",
"SteerFriction": "float",
"SteerKP": "float",
@@ -61,6 +63,7 @@ FTM_ADVANCED_LATERAL_PARAM_KEYS = {
"AdvancedLateralTune",
"ForceAutoTune",
"ForceAutoTuneOff",
"UseAutoSteerDelay",
"SteerDelay",
"SteerFriction",
"SteerKP",
@@ -661,7 +664,7 @@ def _segment_samples(segment_source: RouteSource) -> tuple[list[FTMSample], car.
def _current_param_state(CP, params: Params) -> dict[str, Any]:
advanced_enabled = params.get_bool("AdvancedLateralTune")
torque_tune = CP.lateralTuning.torque if CP.lateralTuning.which() == "torque" else None
stock_delay = float(getattr(CP, "steerActuatorDelay", 0.0) or 0.0)
stock_delay = full_lateral_delay(float(getattr(CP, "steerActuatorDelay", 0.0) or 0.0))
stock_ratio = float(getattr(CP, "steerRatio", 0.0) or 0.0)
stock_friction = float(getattr(torque_tune, "friction", 0.0) or 0.0) if torque_tune is not None else 0.0
stock_lat_accel = float(getattr(torque_tune, "latAccelFactor", 0.0) or 0.0) if torque_tune is not None else 0.0
@@ -669,6 +672,7 @@ def _current_param_state(CP, params: Params) -> dict[str, Any]:
"AdvancedLateralTune": advanced_enabled,
"ForceAutoTune": params.get_bool("ForceAutoTune"),
"ForceAutoTuneOff": params.get_bool("ForceAutoTuneOff"),
"UseAutoSteerDelay": params.get_bool("UseAutoSteerDelay"),
"SteerDelay": params.get_float("SteerDelay", return_default=True, default=stock_delay) if advanced_enabled else stock_delay,
"SteerFriction": params.get_float("SteerFriction", return_default=True, default=stock_friction) if advanced_enabled else stock_friction,
"SteerKP": params.get_float("SteerKP", return_default=True, default=KP) if advanced_enabled else KP,
@@ -690,7 +694,8 @@ def _stock_param_state(CP, capabilities: dict[str, Any]) -> dict[str, Any]:
if rich_profile and meta.get("profile") == rich_profile
}
return {
"SteerDelay": float(getattr(CP, "steerActuatorDelay", 0.0) or 0.0),
"UseAutoSteerDelay": True,
"SteerDelay": full_lateral_delay(float(getattr(CP, "steerActuatorDelay", 0.0) or 0.0)),
"SteerFriction": float(getattr(torque_tune, "friction", 0.0) or 0.0) if torque_tune is not None else 0.0,
"SteerKP": float(KP),
"SteerLatAccel": float(getattr(torque_tune, "latAccelFactor", 0.0) or 0.0) if torque_tune is not None else 0.0,
@@ -1425,6 +1430,9 @@ def _merge_primary_adjustments(suggestions: list[dict[str, Any]], multiplier: fl
if not math.isclose(float(bucket["current"]), next_value, abs_tol=max(precision / 2.0, 1e-6)):
params_delta[param_key] = next_value
if "SteerDelay" in params_delta:
params_delta["UseAutoSteerDelay"] = False
supported_knobs = get_ftm_supported_vehicle_knobs()
for symbol, bucket in vehicle_targets.items():
meta = supported_knobs.get(symbol)
@@ -241,7 +241,8 @@ def test_stock_param_state_captures_generic_and_rich_defaults(tmp_path):
stock = module._stock_param_state(CP, capabilities)
assert stock["SteerLatAccel"] == pytest.approx(3.0)
assert stock["SteerFriction"] == pytest.approx(0.09)
assert stock["SteerDelay"] == pytest.approx(0.1)
assert stock["UseAutoSteerDelay"] is True
assert stock["SteerDelay"] == pytest.approx(0.3)
assert stock["SteerRatio"] == pytest.approx(14.26)
assert len(stock["FTMBaseFrictionThresholds"]["hkg_canfd"]["values"]) == 5
assert stock["FTMVehicleKnobs"]["hyundai_ioniq_6.turn_in_boost_left"] == pytest.approx(1.64)
@@ -570,6 +571,26 @@ def test_merge_primary_adjustments_averages_conflicting_deltas(tmp_path):
assert overrides == {}
def test_merge_primary_adjustments_disables_auto_delay_for_manual_delay_trial(tmp_path):
module, _ = _load_ftm_workspace_module(tmp_path)
suggestions = [{
"severity": 1.0,
"primaryAdjustmentRaw": {
"type": "generic_param",
"paramKey": "SteerDelay",
"current": 0.31,
"suggested": 0.33,
"delta": 0.02,
},
}]
params_delta, overrides, _ = module._merge_primary_adjustments(suggestions, 1.0)
assert params_delta["SteerDelay"] == pytest.approx(0.33)
assert params_delta["UseAutoSteerDelay"] is False
assert overrides == {}
def test_apply_and_revert_trial_profile_round_trip(tmp_path):
module, fake_params_cls = _load_ftm_workspace_module(tmp_path)
workspace = module.ensure_ftm_workspace()
@@ -70,7 +70,11 @@ class WritableFakeParams:
def _params_client(monkeypatch, values, device_type):
fake_params = WritableFakeParams(values)
monkeypatch.setattr(the_galaxy, "params", fake_params)
monkeypatch.setattr(the_galaxy, "_get_param_type_info", lambda: ({"TryRaylibUI"}, {"TryRaylibUI": bool}))
monkeypatch.setattr(
the_galaxy,
"_get_param_type_info",
lambda: ({"UseOldUI", "TryRaylibUI"}, {"UseOldUI": bool, "TryRaylibUI": bool}),
)
monkeypatch.setattr(the_galaxy.HARDWARE, "get_device_type", lambda: device_type)
monkeypatch.setattr(the_galaxy.Paths, "comma_home", lambda: "/tmp/dashboard-test-home", raising=False)
@@ -184,36 +188,51 @@ def test_galaxy_session_value_matches_cookie_format():
) == f"testGalaxySlug01%3A{'a' * 64}"
def test_try_raylib_ui_is_noop_on_c4_mici(monkeypatch):
client, fake_params = _params_client(monkeypatch, {"TryRaylibUI": False, "IsOnroad": False}, "mici")
def test_use_old_ui_is_noop_on_c4_mici(monkeypatch):
client, fake_params = _params_client(monkeypatch, {"UseOldUI": False, "IsOnroad": False}, "mici")
response = client.put("/api/params", json={"key": "TryRaylibUI", "value": True})
response = client.put("/api/params", json={"key": "UseOldUI", "value": True})
payload = response.get_json()
assert response.status_code == 200
assert payload["updated"] == {"TryRaylibUI": False}
assert fake_params.values["TryRaylibUI"] is False
assert payload["updated"] == {"UseOldUI": False, "TryRaylibUI": False}
assert fake_params.values["UseOldUI"] is False
assert fake_params.writes == []
def test_try_raylib_ui_writes_on_big_device_offroad(monkeypatch):
client, fake_params = _params_client(monkeypatch, {"TryRaylibUI": False, "IsOnroad": False}, "tici")
def test_use_old_ui_writes_on_big_device_offroad(monkeypatch):
client, fake_params = _params_client(monkeypatch, {"UseOldUI": False, "TryRaylibUI": True, "IsOnroad": False}, "tici")
response = client.put("/api/params", json={"key": "TryRaylibUI", "value": True})
response = client.put("/api/params", json={"key": "UseOldUI", "value": True})
payload = response.get_json()
assert response.status_code == 200
assert payload["updated"] == {"TryRaylibUI": True}
assert fake_params.values["TryRaylibUI"] is True
assert fake_params.writes == [("TryRaylibUI", True)]
assert payload["updated"] == {"UseOldUI": True, "TryRaylibUI": False}
assert fake_params.values["UseOldUI"] is True
assert fake_params.values["TryRaylibUI"] is False
assert fake_params.writes == [("UseOldUI", True), ("TryRaylibUI", False)]
def test_try_raylib_ui_rejects_big_device_onroad_change(monkeypatch):
client, fake_params = _params_client(monkeypatch, {"TryRaylibUI": False, "IsOnroad": True}, "tici")
def test_use_old_ui_rejects_big_device_onroad_change(monkeypatch):
client, fake_params = _params_client(monkeypatch, {"UseOldUI": False, "TryRaylibUI": True, "IsOnroad": True}, "tici")
response = client.put("/api/params", json={"key": "TryRaylibUI", "value": True})
response = client.put("/api/params", json={"key": "UseOldUI", "value": True})
assert response.status_code == 403
assert response.get_json()["error"] == "Cannot change Try raylib UI while driving."
assert fake_params.values["TryRaylibUI"] is False
assert response.get_json()["error"] == "Cannot change Use Old UI while driving."
assert fake_params.values["UseOldUI"] is False
assert fake_params.values["TryRaylibUI"] is True
assert fake_params.writes == []
def test_legacy_try_raylib_ui_payload_updates_use_old_ui(monkeypatch):
client, fake_params = _params_client(monkeypatch, {"UseOldUI": True, "TryRaylibUI": False, "IsOnroad": False}, "tici")
response = client.put("/api/params", json={"key": "TryRaylibUI", "value": True})
payload = response.get_json()
assert response.status_code == 200
assert payload["updated"] == {"UseOldUI": False, "TryRaylibUI": True}
assert fake_params.values["UseOldUI"] is False
assert fake_params.values["TryRaylibUI"] is True
assert fake_params.writes == [("UseOldUI", False), ("TryRaylibUI", True)]
+47 -10
View File
@@ -63,6 +63,7 @@ from openpilot.starpilot.common.maps_catalog import (
)
from openpilot.starpilot.common.experimental_state import sync_persist_chill_state, sync_persist_experimental_state
from openpilot.starpilot.common.favorite_slots import FAVORITE_SLOTS_PARAM, normalize_favorite_slots
from openpilot.starpilot.common.lateral_delay import full_lateral_delay
from openpilot.starpilot.common.starpilot_utilities import delete_file, get_lock_status, run_cmd
from openpilot.starpilot.common.starpilot_variables import ACTIVE_THEME_PATH, ERROR_LOGS_PATH, EXCLUDED_KEYS, LEGACY_STARPILOT_PARAM_RENAMES, MAPS_PATH, MODELS_PATH, RESOURCES_REPO, SCREEN_RECORDINGS_PATH, STOCK_THEME_PATH, THEME_SAVE_PATH,\
default_ev_tuning_enabled, migrate_cancel_button_controls, update_starpilot_toggles
@@ -845,6 +846,7 @@ _TROUBLESHOOT_CEM_KEYS = [
_TROUBLESHOOT_ADVANCED_LATERAL_KEYS = [
"AdvancedLateralTune",
"UseAutoSteerDelay",
"SteerDelay",
"SteerFriction",
"SteerOffset",
@@ -2450,6 +2452,18 @@ def _is_blank_param_raw(raw_value):
return len(raw_value.strip()) == 0
return False
def _get_use_old_ui_enabled():
if not _raylib_ui_toggle_affects_device():
return False
raw_value = _safe_params_get_live_raw("UseOldUI")
if _is_blank_param_raw(raw_value):
legacy_raw_value = _safe_params_get_live_raw("TryRaylibUI")
if not _is_blank_param_raw(legacy_raw_value):
return not _coerce_param_value(legacy_raw_value, bool)
return _coerce_param_value(raw_value, bool)
def _has_runtime_default_value(key, raw_value):
if _is_blank_param_raw(raw_value):
return False
@@ -2483,7 +2497,7 @@ def _get_runtime_default_param_overrides():
overrides["EVTuning"] = default_ev_tuning_enabled(cp)
car_param_defaults = {
"SteerDelay": getattr(cp, "steerActuatorDelay", None),
"SteerDelay": full_lateral_delay(getattr(cp, "steerActuatorDelay", 0.0)),
"SteerRatio": getattr(cp, "steerRatio", None),
"LongitudinalActuatorDelay": getattr(cp, "longitudinalActuatorDelay", None),
"StartAccel": getattr(cp, "startAccel", None),
@@ -2528,6 +2542,11 @@ def _get_runtime_default_param_overrides():
return overrides
def _get_current_param_value(key, value_type, defaults_lookup=None):
if key == "UseOldUI":
return _get_use_old_ui_enabled()
if key == "TryRaylibUI":
return _raylib_ui_toggle_affects_device() and not _get_use_old_ui_enabled()
if key == CUSTOM_ACCEL_PROFILE_INITIALIZED_KEY:
return _get_custom_accel_profile_initialized()
@@ -3102,6 +3121,12 @@ def _build_troubleshoot_payload():
"value": _get_fingerprint_snapshot_text(),
"resettable": False,
},
{
"id": "lan_ip",
"label": "LAN IP",
"value": utilities.get_current_lan_ip() or "Unavailable",
"resettable": False,
},
*_get_hardware_snapshot_items(),
{
"id": "driving_model",
@@ -3761,6 +3786,8 @@ def setup(app):
def disable_device_settings_asset_cache(response):
if request.path in {
"/assets/components/router.js",
"/assets/components/home/home.js",
"/assets/components/home/home.css",
"/assets/components/tools/device_settings.js",
"/assets/components/tools/device_settings.css",
"/assets/components/tools/device_settings_layout.json",
@@ -4166,22 +4193,24 @@ def setup(app):
if key not in allowed_keys:
return jsonify({"error": f"Parameter '{key}' is not editable."}), 403
if key == "TryRaylibUI":
if key in {"UseOldUI", "TryRaylibUI"}:
enabled = str_val.strip() in ("1", "true", "True")
use_old_ui = enabled if key == "UseOldUI" else not enabled
updated = {"UseOldUI": use_old_ui, "TryRaylibUI": not use_old_ui}
if not _raylib_ui_toggle_affects_device():
current_enabled = params.get_bool("TryRaylibUI")
return jsonify({
"message": "Try raylib UI is only available on tici/tizi devices.",
"updated": {"TryRaylibUI": current_enabled},
"message": "Use Old UI is only available on tici/tizi devices.",
"updated": {"UseOldUI": False, "TryRaylibUI": False},
}), 200
if params.get_bool("IsOnroad"):
return jsonify({"error": "Cannot change Try raylib UI while driving."}), 403
return jsonify({"error": "Cannot change Use Old UI while driving."}), 403
params.put_bool("TryRaylibUI", enabled)
params.put_bool("UseOldUI", use_old_ui)
params.put_bool("TryRaylibUI", not use_old_ui)
return jsonify({
"message": f"{'Raylib' if enabled else 'Qt'} UI selected. UI will restart shortly.",
"updated": {"TryRaylibUI": enabled},
"message": f"{'Old' if use_old_ui else 'Raylib'} UI selected. UI will restart shortly.",
"updated": updated,
}), 200
# 1. Prevent changing the model or reboot-required toggles while the car is actively driving
@@ -4499,6 +4528,10 @@ def setup(app):
return _serialize_param_write_value(_get_custom_accel_profile_initialized()), 200
if request_key == "LeadIndicator":
return _serialize_param_write_value(_get_lead_indicator_enabled()), 200
if request_key == "UseOldUI":
return ("1" if _get_use_old_ui_enabled() else "0"), 200
if request_key == "TryRaylibUI":
return ("1" if _raylib_ui_toggle_affects_device() and not _get_use_old_ui_enabled() else "0"), 200
if request_key == "IsRHD" and not params.get_bool("IsRHDOverride"):
return ("1" if params.get_bool("IsRhdDetected") else "0"), 200
value = params.get(request_key) or ""
@@ -4535,7 +4568,11 @@ def setup(app):
default_val = defaults_lookup.get(key)
try:
if t == bool:
if key == "UseOldUI":
result[key] = False
elif key == "TryRaylibUI":
result[key] = _raylib_ui_toggle_affects_device()
elif t == bool:
if isinstance(default_val, bytes):
default_str = default_val.decode("utf-8", errors="replace")
else:
+99
View File
@@ -2,11 +2,13 @@
import base64
import copy
import hashlib
import ipaddress
import json
import os
import re
import secrets
import shutil
import socket
import subprocess
import sys
import threading
@@ -75,6 +77,7 @@ DASHBOARD_ANALYZER_LOG_PATH = "/tmp/galaxy_dashboard_analyzer.log"
DASHBOARD_ANALYZER_STATUS_PATH = Path("/tmp/galaxy_dashboard_analyzer_status.json")
DASHBOARD_ANALYZER_STATUS_MAX_AGE_SECONDS = 30 * 60
DASHBOARD_TOP_MODEL_LIMIT = 3
LAN_IP_CACHE_TTL_SECONDS = 10.0
DASHBOARD_EVENT_DISTRACTED = "driverDistracted2"
DASHBOARD_EVENT_UNRESPONSIVE = "driverUnresponsive3"
DASHBOARD_TIME_SOURCE_LOG = "log"
@@ -104,9 +107,103 @@ _DASHBOARD_CACHE = {
"updated_at": 0.0,
"value": None,
}
_LAN_IP_CACHE = {
"updated_at": 0.0,
"value": None,
}
_DASHBOARD_ANALYZER_LOCK = threading.Lock()
_DASHBOARD_ANALYZER_PROCESS = None
params = Params(return_defaults=True)
_CGNAT_NETWORK = ipaddress.ip_network("100.64.0.0/10")
def _format_lan_ip(ip_text):
try:
address = ipaddress.ip_address(str(ip_text).strip())
except ValueError:
return ""
if (
address.version != 4
or address.is_loopback
or address.is_link_local
or address.is_multicast
or address.is_unspecified
or address in _CGNAT_NETWORK
):
return ""
return str(address)
def _candidate_lan_ips_from_ip_addr():
try:
result = subprocess.run(
["ip", "-o", "-4", "addr", "show", "scope", "global", "up"],
check=False,
capture_output=True,
text=True,
timeout=0.5,
)
except Exception:
return []
candidates = []
ignored_prefixes = ("lo", "tailscale", "tun", "docker", "br-", "veth", "zt", "wg")
for line in result.stdout.splitlines():
parts = line.split()
if len(parts) < 4:
continue
interface = parts[1].rstrip(":")
if interface.startswith(ignored_prefixes):
continue
try:
inet_index = parts.index("inet")
except ValueError:
continue
candidate = _format_lan_ip(parts[inet_index + 1].split("/", 1)[0] if inet_index + 1 < len(parts) else "")
if candidate:
candidates.append(candidate)
return candidates
def get_current_lan_ip():
now = time.monotonic()
if now - _LAN_IP_CACHE["updated_at"] < LAN_IP_CACHE_TTL_SECONDS:
return _LAN_IP_CACHE["value"]
candidates = []
try:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.settimeout(0.2)
sock.connect(("8.8.8.8", 80))
candidates.append(sock.getsockname()[0])
except Exception:
pass
candidates.extend(_candidate_lan_ips_from_ip_addr())
try:
result = subprocess.run(["hostname", "-I"], check=False, capture_output=True, text=True, timeout=0.5)
candidates.extend(result.stdout.split())
except Exception:
pass
for candidate in candidates:
ip = _format_lan_ip(candidate)
if ip:
_LAN_IP_CACHE["updated_at"] = time.monotonic()
_LAN_IP_CACHE["value"] = ip
return ip
_LAN_IP_CACHE["updated_at"] = time.monotonic()
_LAN_IP_CACHE["value"] = None
return None
def secure_filename(filename):
@@ -2410,11 +2507,13 @@ def _build_device_summary(params_obj):
is_onroad = _params_get_bool(params_obj, "IsOnroad")
uptime_seconds = _read_uptime_seconds()
cpu_temp_c = _read_cpu_temp_c()
lan_ip = get_current_lan_ip()
return {
"status": "Driving" if is_onroad else "Parked",
"online": True,
"uptimeSeconds": uptime_seconds,
"cpuTempC": cpu_temp_c,
"lanIp": lan_ip,
}
+22 -1
View File
@@ -9,6 +9,8 @@ from typing import Protocol
LONG_PITCH_KEY = "LongPitch"
STEER_KP_KEY = "SteerKP"
STEER_KP_STOCK_KEY = "SteerKPStock"
TRY_RAYLIB_UI_KEY = "TryRaylibUI"
USE_OLD_UI_KEY = "UseOldUI"
DEFAULT_STEER_KP = 0.6
LEGACY_STEER_KP = 0.7
@@ -17,6 +19,7 @@ QT_STEER_KP_PLACEHOLDER = 1.0
LAUNCH_PARAM_MIGRATION_MARKER = ".starpilot_launch_param_migrations_v2"
BRANCH_DEFAULTS_MIGRATION_MARKER = ".starpilot_branch_defaults_migrations_v1"
ACCELERATION_PROFILE_MIGRATION_MARKER = ".starpilot_acceleration_profile_default_v1"
USE_OLD_UI_MIGRATION_MARKER = ".starpilot_use_old_ui_migration_v1"
MARKER_DIRNAME = ".starpilot_param_migrations"
LEGACY_CE_STOPPED_LEAD_DEFAULT = True
@@ -81,6 +84,10 @@ def _acceleration_profile_marker_path(params: ParamsLike) -> Path:
return _marker_dir_path(params) / ACCELERATION_PROFILE_MIGRATION_MARKER
def _use_old_ui_marker_path(params: ParamsLike) -> Path:
return _marker_dir_path(params) / USE_OLD_UI_MIGRATION_MARKER
def _marker_dir_path(params: ParamsLike) -> Path:
params_path = Path(params.get_param_path())
# Params.clear_all() removes unknown files inside the params directory, so
@@ -161,9 +168,22 @@ def _apply_acceleration_profile_default_migration(params: ParamsLike, marker: Pa
marker.touch()
def _apply_use_old_ui_migration(params: ParamsLike, marker: Path) -> None:
if marker.exists():
return
marker.parent.mkdir(parents=True, exist_ok=True)
if _param_file_exists(params, TRY_RAYLIB_UI_KEY) and not _param_file_exists(params, USE_OLD_UI_KEY):
params.put_bool(USE_OLD_UI_KEY, not params.get_bool(TRY_RAYLIB_UI_KEY))
marker.touch()
def apply_launch_param_migrations(params: ParamsLike, marker_path: Path | None = None,
branch_defaults_marker_path: Path | None = None,
acceleration_profile_marker_path: Path | None = None) -> None:
acceleration_profile_marker_path: Path | None = None,
use_old_ui_marker_path: Path | None = None) -> None:
_apply_legacy_launch_param_migrations(params, marker_path or _default_marker_path(params))
# Keep branch-default rollout on its own marker so older installs that already
# have the legacy marker still receive this one-time param reset.
@@ -171,6 +191,7 @@ def apply_launch_param_migrations(params: ParamsLike, marker_path: Path | None =
_apply_acceleration_profile_default_migration(
params, acceleration_profile_marker_path or _acceleration_profile_marker_path(params)
)
_apply_use_old_ui_migration(params, use_old_ui_marker_path or _use_old_ui_marker_path(params))
def main() -> int:
+2 -2
View File
@@ -123,7 +123,7 @@ class BigDeviceUIProcess:
return self.should_run_fn(started, params, CP, starpilot_toggles)
def _desired_process(self):
return self._raylib_process if self._params is not None and self._params.get_bool("TryRaylibUI") else self._qt_process
return self._qt_process if self._params is not None and self._params.get_bool("UseOldUI") else self._raylib_process
def start(self) -> None:
desired_process = self._desired_process()
@@ -228,7 +228,7 @@ device_type = HARDWARE.get_device_type()
if device_type in ("tici", "tizi"):
procs.append(BigDeviceUIProcess(always_run, watchdog_max_dt=UI_WATCHDOG_MAX_DT))
else:
# C4 (mici) already runs the Python raylib UI path; TryRaylibUI must not affect it.
# C4 (mici) already runs the Python raylib UI path; UseOldUI must not affect it.
procs.append(PythonProcess("ui", "selfdrive.ui.ui", always_run, watchdog_max_dt=UI_WATCHDOG_MAX_DT))
procs += [
@@ -7,6 +7,7 @@ from openpilot.system.manager.launch_param_migrations import (
LAUNCH_PARAM_MIGRATION_MARKER,
MARKER_DIRNAME,
STANDARD_ACCELERATION_PROFILE,
USE_OLD_UI_MIGRATION_MARKER,
apply_launch_param_migrations,
)
@@ -50,7 +51,9 @@ class FileBackedFakeParams:
def marker_path(tmp_path: Path, marker_name: str) -> Path:
return tmp_path / MARKER_DIRNAME / "params" / marker_name
path = tmp_path / MARKER_DIRNAME / "params" / marker_name
path.parent.mkdir(parents=True, exist_ok=True)
return path
def test_apply_launch_param_migrations_sets_branch_defaults_once(tmp_path):
@@ -202,3 +205,33 @@ def test_apply_launch_param_migrations_preserves_custom_acceleration_profile_wit
apply_launch_param_migrations(params)
assert params.get_int("AccelerationProfile") == 1
def test_apply_launch_param_migrations_inverts_try_raylib_ui_enabled(tmp_path):
params = FileBackedFakeParams(tmp_path / "params")
params.put_bool("TryRaylibUI", True)
apply_launch_param_migrations(params)
assert not params.get_bool("UseOldUI")
assert marker_path(tmp_path, USE_OLD_UI_MIGRATION_MARKER).is_file()
def test_apply_launch_param_migrations_inverts_try_raylib_ui_disabled(tmp_path):
params = FileBackedFakeParams(tmp_path / "params")
params.put_bool("TryRaylibUI", False)
apply_launch_param_migrations(params)
assert params.get_bool("UseOldUI")
assert marker_path(tmp_path, USE_OLD_UI_MIGRATION_MARKER).is_file()
def test_apply_launch_param_migrations_does_not_overwrite_use_old_ui(tmp_path):
params = FileBackedFakeParams(tmp_path / "params")
params.put_bool("TryRaylibUI", False)
params.put_bool("UseOldUI", False)
apply_launch_param_migrations(params)
assert not params.get_bool("UseOldUI")
+10 -10
View File
@@ -138,26 +138,26 @@ class TestManager:
ui_process._qt_process = qt_process
ui_process._raylib_process = raylib_process
params = FileBackedFakeParams(tmp_path / "params", {"TryRaylibUI": False})
params = FileBackedFakeParams(tmp_path / "params", {"UseOldUI": False})
assert ui_process.should_run(False, params, car.CarParams.new_message(), SimpleNamespace())
ui_process.start()
assert ui_process.proc is qt_process.proc
assert qt_process.starts == 1
assert raylib_process.starts == 0
assert ui_process.proc is raylib_process.proc
assert qt_process.starts == 0
assert raylib_process.starts == 1
params.put_bool("TryRaylibUI", True)
params.put_bool("UseOldUI", True)
assert ui_process.should_run(True, params, car.CarParams.new_message(), SimpleNamespace())
ui_process.start()
assert ui_process.proc is qt_process.proc
assert ui_process.proc is raylib_process.proc
assert qt_process.stops == 0
assert raylib_process.starts == 0
assert qt_process.starts == 0
assert ui_process.should_run(False, params, car.CarParams.new_message(), SimpleNamespace())
ui_process.start()
assert qt_process.stops == 1
assert raylib_process.starts == 1
assert ui_process.proc is raylib_process.proc
assert raylib_process.stops == 1
assert qt_process.starts == 1
assert ui_process.proc is qt_process.proc
def test_blacklisted_procs(self):
# TODO: ensure there are blacklisted procs until we have a dedicated test
+2
View File
@@ -331,6 +331,7 @@ StartupMessageBottom
StartupMessageTop
StaticPedalsOnUI
SteerDelay
SteerDelayModeMigrated
SteerDelayStock
SteerFriction
SteerFrictionStock
@@ -367,6 +368,7 @@ TuningLevelConfirmed
TurnDesires
UnlockDoors
UpdaterAvailableBranches
UseAutoSteerDelay
UseKonikServer
UsePrebuilt
UseSI