IQ.Pilot Release Commit @ b79954e
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -90,6 +90,6 @@ void PandaSafety::setSafetyMode(const std::vector<std::string> ¶ms_string) {
|
||||
}
|
||||
|
||||
bool PandaSafety::getOffroadMode() {
|
||||
auto offroad_mode = params_.getBool("OffroadMode");
|
||||
auto offroad_mode = params_.getBool("IQAlwaysOffroad");
|
||||
return offroad_mode;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user