IQ.Pilot Release Commit @ b79954e

This commit is contained in:
IQ.Lvbs CI [bot]
2026-07-25 18:44:03 -05:00
parent f94087822f
commit 563022daa3
179 changed files with 696 additions and 610 deletions

View File

@@ -365,8 +365,12 @@ class NeuralNetworkFeedForward(PilotLateralBrain):
super().__init__(lac_torque, CP, CP_IQ, CI)
self.params = Params()
self.enabled = self.params.get_bool("NeuralNetworkFeedForward")
self.has_nn_model = CP_IQ.iqLateralNet.model.path != MOCK_MODEL_PATH
self.model = NNTorqueModel(CP_IQ.iqLateralNet.model.path)
# NNFF applies only when a real trained model for this car is present on disk.
# No models shipped (or no match / MOCK) -> skip NNFF entirely and fall back to
# the stock torque feed-forward. Models are re-added as they are retrained.
self.has_nn_model = (CP_IQ.iqLateralNet.model.path != MOCK_MODEL_PATH
and os.path.isfile(CP_IQ.iqLateralNet.model.path))
self.model = NNTorqueModel(CP_IQ.iqLateralNet.model.path) if self.has_nn_model else None
self.pitch = FirstOrderFilter(0.0, 0.5, 0.01)
self.pitch_last = 0.0

View File

@@ -6,6 +6,7 @@ import cereal.messaging as messaging
from iqdbc.car.interfaces import ACCEL_MIN, ACCEL_MAX
from openpilot.common.constants import CV
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params, UnknownKeyName
from openpilot.common.realtime import DT_MDL
from openpilot.selfdrive.modeld.constants import ModelConstants
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
@@ -92,6 +93,18 @@ def get_e2e_accel(v_ego, v_cruise, model_v, a_target, should_stop):
return float(np.interp(min(accel_intent, speed_intent), [0.0, 1.0], [a_target, convergence_accel]))
def get_accel_candidates(e2e, has_lead, mpc_candidate, cruise_candidate, e2e_candidate):
candidates = []
# With no lead, the MPC follows a synthetic fast lead. It remains the ACC
# policy, but must not limit the model policy in full E2E.
if not e2e or has_lead:
candidates.append(mpc_candidate)
candidates.append(cruise_candidate)
if e2e:
candidates.append(e2e_candidate)
return candidates
class LongitudinalPlanner(LongitudinalPlannerIQ):
def __init__(self, CP, CP_IQ, init_v=0.0, init_a=0.0, dt=DT_MDL):
self.CP = CP
@@ -108,6 +121,10 @@ class LongitudinalPlanner(LongitudinalPlannerIQ):
self.output_a_target = 0.0
self.output_should_stop = False
self.launch_armed = False
try:
self.exp_speed_conv = Params().get_bool("expSpeedConv")
except UnknownKeyName:
self.exp_speed_conv = False
self.v_desired_trajectory = np.zeros(CONTROL_N)
self.a_desired_trajectory = np.zeros(CONTROL_N)
@@ -196,7 +213,7 @@ class LongitudinalPlanner(LongitudinalPlannerIQ):
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
output_should_stop_e2e = sm['modelV2'].action.shouldStop
output_a_target_e2e, output_should_stop_e2e = self.apply_e2e_stop_distance(sm, v_ego, output_a_target_e2e, output_should_stop_e2e)
if self.is_e2e(sm):
if self.is_e2e(sm) and self.exp_speed_conv and not self.mpc.status:
output_a_target_e2e = get_e2e_accel(v_ego, v_cruise, model_v, output_a_target_e2e, output_should_stop_e2e)
if sm['carState'].standstill:
@@ -218,10 +235,13 @@ class LongitudinalPlanner(LongitudinalPlannerIQ):
steer_angle_without_offset, self.CP, self.dt,
accel_coast, self.allow_throttle)
candidates = [(output_a_target_mpc, self.mpc.source, output_should_stop_mpc),
(self.a_cruise, LongitudinalPlanSource.cruise, cruise_should_stop)]
if e2e:
candidates.append((output_a_target_e2e, LongitudinalPlanSource.e2e, output_should_stop_e2e))
candidates = get_accel_candidates(
e2e,
self.mpc.status,
(output_a_target_mpc, self.mpc.source, output_should_stop_mpc),
(self.a_cruise, LongitudinalPlanSource.cruise, cruise_should_stop),
(output_a_target_e2e, LongitudinalPlanSource.e2e, output_should_stop_e2e),
)
output_a_target, self.mpc.source, _ = min(candidates, key=lambda c: c[0])
self.output_should_stop = any(should_stop for _, _, should_stop in candidates)

View File

@@ -13,8 +13,7 @@ from openpilot.common.swaglog import cloudlog
from openpilot.common.simple_kalman import KF1D
from iqdbc.car import structs
from iqdbc.car.hyundai.values import HyundaiFlags
from iqdbc.iqpilot.car.hyundai.values import HyundaiFlagsIQ
from iqdbc.car.hyundai.values import HyundaiFlags, HyundaiFlagsIQ
from openpilot.iqpilot.selfdrive.controls.lib.custom_stop_distance import CustomStopDistance

View File

@@ -2,7 +2,8 @@ import numpy as np
import pytest
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS
from openpilot.selfdrive.controls.lib.longitudinal_planner import get_e2e_accel
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalPlanSource
from openpilot.selfdrive.controls.lib.longitudinal_planner import get_accel_candidates, get_e2e_accel
def model_velocity(v_ego, v_future):
@@ -29,3 +30,25 @@ class TestE2eCruiseConvergence:
])
def test_never_overrides_cruise_or_stop(self, v_ego, v_cruise, should_stop):
assert get_e2e_accel(v_ego, v_cruise, model_velocity(v_ego, v_ego + 5.0), -0.2, should_stop) == pytest.approx(-0.2)
class TestAccelCandidates:
MPC = (-0.2, LongitudinalPlanSource.lead0, True)
CRUISE = (0.5, LongitudinalPlanSource.cruise, False)
E2E = (0.1, LongitudinalPlanSource.e2e, False)
def test_e2e_without_lead_frees_model_from_mpc(self):
candidates = get_accel_candidates(True, False, self.MPC, self.CRUISE, self.E2E)
assert candidates == [self.CRUISE, self.E2E]
assert min(candidates, key=lambda c: c[0])[1] == LongitudinalPlanSource.e2e
assert not any(should_stop for _, _, should_stop in candidates)
def test_e2e_with_lead_keeps_mpc_safety_constraint(self):
candidates = get_accel_candidates(True, True, self.MPC, self.CRUISE, self.E2E)
assert candidates == [self.MPC, self.CRUISE, self.E2E]
assert min(candidates, key=lambda c: c[0])[1] == LongitudinalPlanSource.lead0
assert any(should_stop for _, _, should_stop in candidates)
def test_acc_without_lead_keeps_mpc_policy(self):
candidates = get_accel_candidates(False, False, self.MPC, self.CRUISE, self.E2E)
assert candidates == [self.MPC, self.CRUISE]

View File

@@ -90,6 +90,6 @@ void PandaSafety::setSafetyMode(const std::vector<std::string> &params_string) {
}
bool PandaSafety::getOffroadMode() {
auto offroad_mode = params_.getBool("OffroadMode");
auto offroad_mode = params_.getBool("IQAlwaysOffroad");
return offroad_mode;
}

View File

@@ -162,7 +162,7 @@ class SettingsHubLayout(Widget):
self._power_rect = rl.Rectangle(0, 0, 0, 0)
self._offroad_rect = rl.Rectangle(0, 0, 0, 0)
self._night_rect = rl.Rectangle(0, 0, 0, 0)
self._quiet_rect = rl.Rectangle(0, 0, 0, 0)
self._silent_rect = rl.Rectangle(0, 0, 0, 0)
# Build the grid from the settings panels, skipping ones hidden from navigation (e.g. Cruise).
panels = self._settings._panels
@@ -442,14 +442,14 @@ class SettingsHubLayout(Widget):
self._power_rect = rl.Rectangle(bx + BUBBLE_SIZE + 18, cy - BUBBLE_SIZE / 2, BUBBLE_SIZE, BUBBLE_SIZE)
self._offroad_rect = rl.Rectangle(bx + 2 * (BUBBLE_SIZE + 18), cy - BUBBLE_SIZE / 2, BUBBLE_SIZE, BUBBLE_SIZE)
self._night_rect = rl.Rectangle(bx + 3 * (BUBBLE_SIZE + 18), cy - BUBBLE_SIZE / 2, BUBBLE_SIZE, BUBBLE_SIZE)
self._quiet_rect = rl.Rectangle(bx + 4 * (BUBBLE_SIZE + 18), cy - BUBBLE_SIZE / 2, BUBBLE_SIZE, BUBBLE_SIZE)
self._silent_rect = rl.Rectangle(bx + 4 * (BUBBLE_SIZE + 18), cy - BUBBLE_SIZE / 2, BUBBLE_SIZE, BUBBLE_SIZE)
mouse = rl.get_mouse_position()
for r, icon in ((self._restart_rect, self._restart_icon), (self._power_rect, self._power_icon)):
pressed = self.is_pressed and rl.check_collision_point_rec(mouse, r)
rl.draw_circle(int(r.x + BUBBLE_SIZE / 2), int(cy), BUBBLE_SIZE / 2, BUBBLE_RED_PRESSED if pressed else BUBBLE_RED)
rl.draw_texture(icon, int(r.x + (BUBBLE_SIZE - icon.width) / 2), int(cy - icon.height / 2), rl.WHITE)
# Always Offroad bubble (grey when off, teal when on)
offroad_active = ui_state.params.get_bool("OffroadMode")
offroad_active = ui_state.params.get_bool("IQAlwaysOffroad")
pressed = self.is_pressed and rl.check_collision_point_rec(mouse, self._offroad_rect)
offroad_color = (BUBBLE_TEAL_PRESSED if pressed else BUBBLE_TEAL) if offroad_active else (BUBBLE_GREY_PRESSED if pressed else BUBBLE_GREY)
rl.draw_circle(int(self._offroad_rect.x + BUBBLE_SIZE / 2), int(cy), BUBBLE_SIZE / 2, offroad_color)
@@ -465,15 +465,15 @@ class SettingsHubLayout(Widget):
int(self._night_rect.x + (BUBBLE_SIZE - self._night_icon.width) / 2),
int(cy - self._night_icon.height / 2), rl.WHITE)
# Quiet Mode bubble (grey bell when off, red bell-with-slash when on)
quiet_active = ui_state.params.get_bool("IQAlertSilence")
pressed = self.is_pressed and rl.check_collision_point_rec(mouse, self._quiet_rect)
quiet_color = (BUBBLE_RED_PRESSED if pressed else BUBBLE_RED) if quiet_active else (BUBBLE_GREY_PRESSED if pressed else BUBBLE_GREY)
quiet_icon = self._bell_slash_icon if quiet_active else self._bell_icon
rl.draw_circle(int(self._quiet_rect.x + BUBBLE_SIZE / 2), int(cy), BUBBLE_SIZE / 2, quiet_color)
rl.draw_texture(quiet_icon,
int(self._quiet_rect.x + (BUBBLE_SIZE - quiet_icon.width) / 2),
int(cy - quiet_icon.height / 2), rl.WHITE)
# Silent Mode bubble (grey bell when off, red bell-with-slash when on)
silent_active = ui_state.params.get_bool("IQAlertSilence")
pressed = self.is_pressed and rl.check_collision_point_rec(mouse, self._silent_rect)
silent_color = (BUBBLE_RED_PRESSED if pressed else BUBBLE_RED) if silent_active else (BUBBLE_GREY_PRESSED if pressed else BUBBLE_GREY)
silent_icon = self._bell_slash_icon if silent_active else self._bell_icon
rl.draw_circle(int(self._silent_rect.x + BUBBLE_SIZE / 2), int(cy), BUBBLE_SIZE / 2, silent_color)
rl.draw_texture(silent_icon,
int(self._silent_rect.x + (BUBBLE_SIZE - silent_icon.width) / 2),
int(cy - silent_icon.height / 2), rl.WHITE)
# Small version label, top-right of the header row. Its scroll viewport
# starts after the title, so long branch text does not clip at the bubbles.
@@ -517,7 +517,7 @@ class SettingsHubLayout(Widget):
self._toggle_offroad_prompt()
elif rl.check_collision_point_rec(mouse_pos, self._night_rect):
ui_state.params.put_bool("NightMode", not ui_state.params.get_bool("NightMode"))
elif rl.check_collision_point_rec(mouse_pos, self._quiet_rect):
elif rl.check_collision_point_rec(mouse_pos, self._silent_rect):
ui_state.params.put_bool("IQAlertSilence", not ui_state.params.get_bool("IQAlertSilence"))
def _reboot_prompt(self):
@@ -546,11 +546,11 @@ class SettingsHubLayout(Widget):
if ui_state.engaged:
gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Enter Always Offroad Mode")))
return
active = ui_state.params.get_bool("OffroadMode")
active = ui_state.params.get_bool("IQAlwaysOffroad")
msg = tr("Are you sure you want to exit Always Offroad mode?") if active else tr("Are you sure you want to enter Always Offroad mode?")
def _confirm(result: int):
if result == DialogResult.CONFIRM and not ui_state.engaged:
ui_state.params.put_bool("OffroadMode", not active)
ui_state.params.put_bool("IQAlwaysOffroad", not active)
gui_app.set_modal_overlay(ConfirmDialog(msg, tr("Confirm")), callback=_confirm)

View File

@@ -121,16 +121,16 @@ class ModelsLayoutMici(NavScroller):
self._clear.set_click_callback(self._confirm_clear_cache)
self._clear.set_enabled(lambda: ui_state.is_offroad())
self._lagd = BigParamControl("live learning steer delay", "LagdToggle")
self._sw_delay = MappedParamToggle("software delay", "LagdToggleDelay", _DELAY_OPTIONS, _DELAY_VALUES)
self._sw_delay.set_visible(lambda: not self._lagd._checked)
self._steer_delay = BigParamControl("self-tuning steer delay", "IQLiveSteerDelay")
self._sw_delay = MappedParamToggle("manual delay offset", "IQSoftwareSteerDelay", _DELAY_OPTIONS, _DELAY_VALUES)
self._sw_delay.set_visible(lambda: not self._steer_delay._checked)
self._lane_turn = BigParamControl("use lane turn desires", "IQLaneTurnDesire")
self._lane_speed = MappedParamToggle("lane turn speed", "IQLaneTurnValue", _LANE_TURN_OPTIONS, _LANE_TURN_VALUES)
self._lane_speed.set_visible(lambda: self._lane_turn._checked)
self._main_items = [self._current, self._cancel, self._supercombo, self._vision, self._policy, self._redownload, self._refresh, self._clear,
self._lagd, self._sw_delay, self._lane_turn, self._lane_speed]
self._steer_delay, self._sw_delay, self._lane_turn, self._lane_speed]
self._scroller.add_widgets(self._main_items)
@property
@@ -368,7 +368,7 @@ class ModelsLayoutMici(NavScroller):
self._last_cache_t = now
self._clear.set_value(f"{self._calculate_cache_size():.1f} MB")
self._update_lagd_subtext()
self._update_steer_delay_subtext()
def _progress_target_bundle(self):
try:
@@ -445,21 +445,21 @@ class ModelsLayoutMici(NavScroller):
return f"failed: {_display_model_name(bundle)}"
return self._active_model_name()
def _update_lagd_subtext(self):
if self._lagd._checked:
def _update_steer_delay_subtext(self):
if self._steer_delay._checked:
try:
self._lagd.set_value(f"live {ui_state.sm['liveDelay'].lateralDelay:.3f} s")
self._steer_delay.set_value(f"measured {ui_state.sm['liveDelay'].lateralDelay:.3f} s")
except Exception:
self._lagd.set_value("")
self._steer_delay.set_value("")
return
try:
sw = float(ui_state.params.get("LagdToggleDelay", return_default=True))
sw = float(ui_state.params.get("IQSoftwareSteerDelay", return_default=True))
except (TypeError, ValueError):
sw = 0.2
if ui_state.CP is not None:
self._lagd.set_value(f"total {ui_state.CP.steerActuatorDelay + sw:.2f} s")
self._steer_delay.set_value(f"total {ui_state.CP.steerActuatorDelay + sw:.2f} s")
else:
self._lagd.set_value(f"+{sw:.2f} s software")
self._steer_delay.set_value(f"+{sw:.2f} s offset")
def _active_model_name(self) -> str:
if not self._has_active_bundle_param():
@@ -495,5 +495,5 @@ class ModelsLayoutMici(NavScroller):
def show_event(self):
super().show_event()
for w in (self._lagd, self._sw_delay, self._lane_turn, self._lane_speed):
for w in (self._steer_delay, self._sw_delay, self._lane_turn, self._lane_speed):
w.refresh()

View File

@@ -66,10 +66,10 @@ class SabSettingsPanel(NavScroller):
class LaneChangePanel(NavScroller):
def __init__(self):
super().__init__()
self._timer = MappedParamToggle("Auto Lane Change", "AutoLaneChangeTimer",
self._timer = MappedParamToggle("Auto Lane Change", "IQLaneChangeTimer",
["off", "nudge", "nudgeless", "0.5 s", "1 s", "2 s", "3 s"],
[-1, 0, 1, 2, 3, 4, 5])
self._bsm_delay = BigParamControl("Delay with Blind Spot", "AutoLaneChangeBsmDelay")
self._bsm_delay = BigParamControl("Delay with Blind Spot", "IQLaneChangeBsmDelay")
self._continuous = BigParamControl("Continuous Changes", "LaneChangeContinuous")
self._scroller.add_widgets([self._timer, self._bsm_delay, self._continuous])
@@ -77,11 +77,11 @@ class LaneChangePanel(NavScroller):
super().show_event()
self._timer.refresh()
enable_bsm = bool(ui_state.CP and ui_state.CP.enableBsm)
if not enable_bsm and ui_state.params.get_bool("AutoLaneChangeBsmDelay"):
ui_state.params.remove("AutoLaneChangeBsmDelay")
if not enable_bsm and ui_state.params.get_bool("IQLaneChangeBsmDelay"):
ui_state.params.remove("IQLaneChangeBsmDelay")
self._bsm_delay.refresh()
self._bsm_delay.set_enabled(
enable_bsm and int(ui_state.params.get("AutoLaneChangeTimer", return_default=True)) > AutoLaneChangeMode.NUDGE
enable_bsm and int(ui_state.params.get("IQLaneChangeTimer", return_default=True)) > AutoLaneChangeMode.NUDGE
)
self._continuous.refresh()

View File

@@ -189,7 +189,7 @@ class IQDevice:
def _set_awake(self, on: bool):
if on and self._params.get("DeviceBootMode", return_default=True) == 1:
self._params.put_bool("OffroadMode", True)
self._params.put_bool("IQAlwaysOffroad", True)
@staticmethod
def set_onroad_brightness(_ui_state, awake: bool, cur_brightness: float) -> float: