diff --git a/common/params_keys.h b/common/params_keys.h index 9190d821b..24257ab9c 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -199,6 +199,7 @@ inline static std::unordered_map keys = { {"CESpeed", {PERSISTENT, FLOAT, "0.0", "0.0", 1}}, {"CESpeedLead", {PERSISTENT, FLOAT, "0.0", "0.0", 1}}, {"CCMLead", {PERSISTENT, BOOL, "1", "0", 1}}, + {"CCMLaunchAssist", {PERSISTENT, BOOL, "0", "0", 1}}, {"CCMSetSpeedMargin", {PERSISTENT, FLOAT, "3.0", "0.0", 1}}, {"CCMSpeed", {PERSISTENT, FLOAT, "45.0", "0.0", 1}}, {"CCMSpeedLead", {PERSISTENT, FLOAT, "35.0", "0.0", 1}}, @@ -283,6 +284,7 @@ inline static std::unordered_map keys = { {"FlashPanda", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"GMDashSpoofOffsets", {PERSISTENT, BOOL, "0", "0", 2}}, {"GMPedalLongitudinal", {PERSISTENT, BOOL, "1", "1", 2}}, + {"GMStockDashWhenNotEngaged", {PERSISTENT, BOOL, "0", "0", 2}}, {"LongPitch", {PERSISTENT, BOOL, "1", "0", 2}}, {"RemoteStartBootsComma", {PERSISTENT, BOOL, "0", "0"}}, {"RemapCancelToDistance", {PERSISTENT, BOOL, "0", "0"}}, @@ -376,6 +378,7 @@ inline static std::unordered_map keys = { {"LongitudinalManeuverStatus", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, JSON, "{}", "{}"}}, {"LongitudinalTune", {PERSISTENT, BOOL, "1", "0", 0}}, {"LoudBlindspotAlert", {PERSISTENT, BOOL, "0", "0", 0}}, + {"LoudBlindspotAlertWhenDisengaged", {PERSISTENT, BOOL, "0", "0", 0}}, {"LowVoltageShutdown", {PERSISTENT, FLOAT, "11.8", "11.8", 3}}, {"MainCruiseButtonControl", {PERSISTENT, INT, "9", "9", 2}}, {"ManualUpdateInitiated", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, diff --git a/opendbc_repo/opendbc/car/gm/carcontroller.py b/opendbc_repo/opendbc/car/gm/carcontroller.py index 86f9a6562..fa9837c6e 100644 --- a/opendbc_repo/opendbc/car/gm/carcontroller.py +++ b/opendbc_repo/opendbc/car/gm/carcontroller.py @@ -68,13 +68,14 @@ def should_spoof_dash_speed(CP, starpilot_toggles): return True -def should_send_acc_dashboard_status(CP, dash_speed_spoof_active): +def should_send_acc_dashboard_status(CP, dash_speed_spoof_active, enabled=True, stock_dash_when_not_engaged=False): status_car = CP.carFingerprint not in CC_ONLY_CAR or CP.carFingerprint == CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL volt_camera_no_camera = ( CP.carFingerprint == CAR.CHEVROLET_VOLT_CAMERA and bool(getattr(CP, "flags", 0) & GMFlags.NO_CAMERA.value) ) - return status_car and (dash_speed_spoof_active or volt_camera_no_camera) + should_spoof = dash_speed_spoof_active and (enabled or not stock_dash_when_not_engaged) + return status_car and (should_spoof or volt_camera_no_camera) def get_acc_dashboard_fcw_alert(hud_alert, CS): @@ -725,7 +726,8 @@ class CarController(CarControllerBase): idx, CC.enabled, near_stop, at_full_stop, self.CP)) CS.auto_hold_engaged = False - if should_send_acc_dashboard_status(self.CP, dash_speed_spoof_active): + stock_dash_when_not_engaged = getattr(starpilot_toggles, "gm_stock_dash_when_not_engaged", False) + if should_send_acc_dashboard_status(self.CP, dash_speed_spoof_active, CC.enabled, stock_dash_when_not_engaged): acc_dashboard_status = get_acc_dashboard_status_values(CC.enabled, hud_v_cruise * CV.MS_TO_KPH, hud_control, CS) fcw_alert = get_acc_dashboard_fcw_alert(hud_alert, CS) can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, diff --git a/opendbc_repo/opendbc/car/gm/tests/test_gm.py b/opendbc_repo/opendbc/car/gm/tests/test_gm.py index e57406f01..07804dbe2 100644 --- a/opendbc_repo/opendbc/car/gm/tests/test_gm.py +++ b/opendbc_repo/opendbc/car/gm/tests/test_gm.py @@ -265,6 +265,33 @@ class TestGMCarController: assert should_send_acc_dashboard_status(cp, dash_speed_spoof_active=False) + def test_stock_dash_toggle_suppresses_disabled_dash_spoof(self): + cp = SimpleNamespace(carFingerprint=CAR.CADILLAC_XT4, flags=0) + + assert should_send_acc_dashboard_status(cp, dash_speed_spoof_active=True, enabled=False) + assert not should_send_acc_dashboard_status( + cp, + dash_speed_spoof_active=True, + enabled=False, + stock_dash_when_not_engaged=True, + ) + assert should_send_acc_dashboard_status( + cp, + dash_speed_spoof_active=True, + enabled=True, + stock_dash_when_not_engaged=True, + ) + + def test_stock_dash_toggle_keeps_no_camera_exception(self): + cp = SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_CAMERA, flags=GMFlags.NO_CAMERA.value) + + assert should_send_acc_dashboard_status( + cp, + dash_speed_spoof_active=True, + enabled=False, + stock_dash_when_not_engaged=True, + ) + def test_acc_dashboard_no_camera_exception_is_volt_camera_only(self): assert not should_send_acc_dashboard_status( SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_CAMERA, flags=0), diff --git a/opendbc_repo/opendbc/car/subaru/carstate.py b/opendbc_repo/opendbc/car/subaru/carstate.py index 33b9d3eac..ed300c534 100644 --- a/opendbc_repo/opendbc/car/subaru/carstate.py +++ b/opendbc_repo/opendbc/car/subaru/carstate.py @@ -15,6 +15,7 @@ class CarState(CarStateBase): self.shifter_values = can_define.dv["Transmission"]["Gear"] self.angle_rate_calulator = CanSignalRateCalculator(50) + self.sng_cruise_enabled = False def update(self, can_parsers, starpilot_toggles) -> structs.CarState: cp = can_parsers[Bus.pt] @@ -133,6 +134,22 @@ class CarState(CarStateBase): self.cruise_state = cp_cam.vl["ES_DashStatus"]["Cruise_State"] self.throttle_msg = copy.copy(cp.vl["Throttle"]) + sng_standstill_hold = ( + ret.cruiseState.available and + ret.standstill and + self.car_follow == 1 and + self.cruise_state == 3 and + not ret.gasPressed + ) + if ret.cruiseState.enabled: + self.sng_cruise_enabled = True + elif self.sng_cruise_enabled and sng_standstill_hold: + ret.cruiseState.enabled = True + else: + self.sng_cruise_enabled = False + else: + self.sng_cruise_enabled = False + return ret, fp_ret @staticmethod diff --git a/selfdrive/car/redneck_cruise.py b/selfdrive/car/redneck_cruise.py index 09eed0153..d1750bcf5 100644 --- a/selfdrive/car/redneck_cruise.py +++ b/selfdrive/car/redneck_cruise.py @@ -37,7 +37,7 @@ def select_redneck_target_speed(v_cruise_kph: float, speed_cluster_ms: float, target_speed_ms = float(starpilot_target_speed_ms) if allow_plan_decrease and len(plan_speeds_ms) > 0: - if lead_present and plan_speeds_ms[0] > speed_cluster_ms: + if lead_present and target_speed_ms > speed_cluster_ms and plan_speeds_ms[0] > speed_cluster_ms: recovery_lookahead_points = min(len(plan_speeds_ms), LEAD_RECOVERY_LOOKAHEAD_POINTS) recovery_target_speed_ms = max(speed_cluster_ms, min(plan_speeds_ms[:recovery_lookahead_points])) return min(target_speed_ms, recovery_target_speed_ms) diff --git a/selfdrive/car/tests/test_redneck_cruise.py b/selfdrive/car/tests/test_redneck_cruise.py index d9371d1c4..efef2024f 100644 --- a/selfdrive/car/tests/test_redneck_cruise.py +++ b/selfdrive/car/tests/test_redneck_cruise.py @@ -182,6 +182,19 @@ class TestRedneckCruise(unittest.TestCase): ) self.assertAlmostEqual(55.8 * CV.MPH_TO_MS, target_speed) + def test_target_speed_does_not_use_recovery_branch_when_cluster_is_above_internal_max(self): + target_speed = select_redneck_target_speed( + 45.0, + 46.0 * CV.KPH_TO_MS, + 0.0, + [46.6 * CV.KPH_TO_MS, 46.4 * CV.KPH_TO_MS, 46.2 * CV.KPH_TO_MS, 46.0 * CV.KPH_TO_MS, + 44.0 * CV.KPH_TO_MS, 42.0 * CV.KPH_TO_MS, 39.0 * CV.KPH_TO_MS], + 7, + allow_plan_decrease=True, + lead_present=True, + ) + self.assertLess(target_speed * CV.MS_TO_KPH, 45.0) + def test_target_speed_stays_on_lead_target_when_cluster_drops_below_it(self): target_speed = select_redneck_target_speed( 76.9, diff --git a/selfdrive/controls/tests/test_conditional_chill_mode.py b/selfdrive/controls/tests/test_conditional_chill_mode.py index 2d32702bb..5508ccbb1 100644 --- a/selfdrive/controls/tests/test_conditional_chill_mode.py +++ b/selfdrive/controls/tests/test_conditional_chill_mode.py @@ -51,6 +51,8 @@ class FakeSubMaster: def make_sm(): return { "carState": SimpleNamespace(standstill=False, leftBlinker=False, rightBlinker=False), + "selfdriveState": SimpleNamespace(enabled=True), + "longitudinalPlan": SimpleNamespace(allowThrottle=True, shouldStop=False), "starpilotCarState": SimpleNamespace(trafficModeEnabled=False), "starpilotRadarState": SimpleNamespace( leadLeft=SimpleNamespace(status=False, dRel=float("inf"), vLead=0.0), @@ -69,6 +71,7 @@ def make_toggles(): conditional_chill_speed_lead=35 * CV.MPH_TO_MS, conditional_chill_speed_margin=3 * CV.MPH_TO_MS, conditional_chill_lead=True, + conditional_chill_launch_assist=False, ) @@ -130,7 +133,7 @@ def test_ccm_enters_chill_for_stable_lead_cruising(monkeypatch): planner, _detector, ccm = make_ccm() sm = make_sm() toggles = make_toggles() - monotonic_values = iter([10.0, 10.5]) + monotonic_values = iter([10.0, 11.1]) monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: next(monotonic_values)) planner.tracking_lead = True @@ -147,6 +150,27 @@ def test_ccm_enters_chill_for_stable_lead_cruising(monkeypatch): assert ccm.status_value == CCStatus["LEAD"] +def test_ccm_stable_lead_requires_longer_entry_debounce(monkeypatch): + planner, _detector, ccm = make_ccm() + sm = make_sm() + toggles = make_toggles() + monotonic_values = iter([10.0, 10.6]) + monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: next(monotonic_values)) + + planner.tracking_lead = True + planner.lead_one.status = True + planner.lead_one.dRel = 45.0 + planner.lead_one.vLead = 24.8 + planner.lead_one.radar = True + + v_ego = 58 * CV.MPH_TO_MS + ccm.update(v_ego, v_ego, sm, toggles) + ccm.update(v_ego, v_ego, sm, toggles) + + assert ccm.experimental_mode + assert ccm.status_value == CCStatus["OFF"] + + def test_ccm_hard_vetoes_force_experimental(monkeypatch): planner, detector, ccm = make_ccm() sm = make_sm() @@ -210,6 +234,88 @@ def test_ccm_immediately_exits_chill_when_scene_turns_into_slow_lead(monkeypatch assert ccm.status_value == CCStatus["OFF"] +def test_ccm_launch_assist_enters_chill_from_standstill_when_planner_wants_to_go(monkeypatch): + planner, _detector, ccm = make_ccm() + sm = make_sm() + sm["carState"].standstill = True + toggles = make_toggles() + toggles.conditional_chill_launch_assist = True + + monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 20.0) + ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles) + + assert not ccm.experimental_mode + assert ccm.status_value == CCStatus["SPEED"] + + +def test_ccm_launch_assist_does_not_bypass_real_stop_scene(monkeypatch): + planner, detector, ccm = make_ccm() + sm = make_sm() + sm["carState"].standstill = True + sm["longitudinalPlan"].shouldStop = True + toggles = make_toggles() + toggles.conditional_chill_launch_assist = True + + monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 21.0) + ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles) + + assert ccm.experimental_mode + assert ccm.status_value == CCStatus["OFF"] + + detector.stop_light_detected = True + sm["longitudinalPlan"].shouldStop = False + ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles) + assert ccm.experimental_mode + assert ccm.status_value == CCStatus["OFF"] + + +def test_ccm_launch_assist_exits_once_launch_speed_is_reached(monkeypatch): + planner, _detector, ccm = make_ccm() + sm = make_sm() + sm["carState"].standstill = True + toggles = make_toggles() + toggles.conditional_chill_launch_assist = True + monotonic_values = iter([30.0, 30.2]) + monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: next(monotonic_values)) + + ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles) + assert not ccm.experimental_mode + + sm["carState"].standstill = False + ccm.update(16 * CV.MPH_TO_MS, 25 * CV.MPH_TO_MS, sm, toggles) + + assert ccm.experimental_mode + assert ccm.status_value == CCStatus["OFF"] + + +def test_ccm_launch_assist_exits_immediately_if_lead_slows_again(monkeypatch): + planner, _detector, ccm = make_ccm() + sm = make_sm() + sm["carState"].standstill = True + toggles = make_toggles() + toggles.conditional_chill_launch_assist = True + monotonic_values = iter([40.0, 40.1]) + monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: next(monotonic_values)) + + planner.tracking_lead = True + planner.lead_one.status = True + planner.lead_one.dRel = 18.0 + planner.lead_one.vLead = 2.0 + planner.lead_one.radar = True + + ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles) + assert not ccm.experimental_mode + assert ccm.status_value == CCStatus["LEAD"] + + sm["carState"].standstill = False + planner.lead_one.vLead = 0.2 + planner.lead_one.aLeadK = -0.4 + ccm.update(3 * CV.MPH_TO_MS, 25 * CV.MPH_TO_MS, sm, toggles) + + assert ccm.experimental_mode + assert ccm.status_value == CCStatus["OFF"] + + def test_ccm_respects_manual_chill_override(monkeypatch): planner, _detector, ccm = make_ccm() sm = make_sm() @@ -223,6 +329,19 @@ def test_ccm_respects_manual_chill_override(monkeypatch): assert ccm.status_value == CCStatus["USER_CHILL"] +def test_ccm_launch_assist_is_disabled_by_default(monkeypatch): + planner, _detector, ccm = make_ccm() + sm = make_sm() + sm["carState"].standstill = True + toggles = make_toggles() + + monkeypatch.setattr("openpilot.starpilot.controls.lib.conditional_chill_mode.time.monotonic", lambda: 50.0) + ccm.update(0.0, 25 * CV.MPH_TO_MS, sm, toggles) + + assert ccm.experimental_mode + assert ccm.status_value == CCStatus["OFF"] + + def test_ccm_restores_persisted_manual_experimental_override(monkeypatch): planner, _detector, ccm = make_ccm() sm = make_sm() diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 502a26bd2..0d1da39df 100644 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -52,6 +52,27 @@ StarPilotEventName = custom.StarPilotOnroadEvent.EventName IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) +def should_loud_blindspot_alert_without_lateral(CS, sm, starpilot_toggles) -> bool: + if not (getattr(starpilot_toggles, "loud_blindspot_alert", False) and + getattr(starpilot_toggles, "loud_blindspot_alert_when_disengaged", False)): + return False + + if sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange: + return False + + left_signal_blocked = bool(CS.leftBlinker and CS.leftBlindspot) + right_signal_blocked = bool(CS.rightBlinker and CS.rightBlindspot) + one_blinker = bool(CS.leftBlinker) != bool(CS.rightBlinker) + if not (one_blinker and (left_signal_blocked or right_signal_blocked)): + return False + + return ( + not sm['carControl'].latActive or + not sm['starpilotPlan'].lateralCheck or + sm['starpilotCarState'].pauseLateral + ) + + class SelfdriveD: def __init__(self, CP=None): self.params = Params() @@ -374,10 +395,12 @@ class SelfdriveD: # ****************************************************************************************** # Handle lane change + blindspot_alert_added = False if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange: direction = self.sm['modelV2'].meta.laneChangeDirection if (CS.leftBlindspot and direction == LaneChangeDirection.left) or \ (CS.rightBlindspot and direction == LaneChangeDirection.right): + blindspot_alert_added = True if self.starpilot_toggles.loud_blindspot_alert: self.starpilot_events.add(StarPilotEventName.laneChangeBlockedLoud) else: @@ -398,6 +421,9 @@ class SelfdriveD: LaneChangeState.laneChangeFinishing): self.events.add(EventName.laneChange) + if not blindspot_alert_added and should_loud_blindspot_alert_without_lateral(CS, self.sm, self.starpilot_toggles): + self.starpilot_events.add(StarPilotEventName.laneChangeBlockedLoud) + for i, pandaState in enumerate(self.sm['pandaStates']): # All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput if i < len(self.CP.safetyConfigs): diff --git a/selfdrive/selfdrived/tests/test_blindspot_alerts.py b/selfdrive/selfdrived/tests/test_blindspot_alerts.py new file mode 100644 index 000000000..c9456754e --- /dev/null +++ b/selfdrive/selfdrived/tests/test_blindspot_alerts.py @@ -0,0 +1,58 @@ +from types import SimpleNamespace + +from cereal import log +from openpilot.selfdrive.selfdrived.selfdrived import should_loud_blindspot_alert_without_lateral + + +LaneChangeState = log.LaneChangeState + + +def _car_state(left_blinker=False, right_blinker=False, left_blindspot=False, right_blindspot=False): + return SimpleNamespace( + leftBlinker=left_blinker, + rightBlinker=right_blinker, + leftBlindspot=left_blindspot, + rightBlindspot=right_blindspot, + ) + + +def _sm(lane_change_state=LaneChangeState.off, lat_active=False, lateral_check=False, pause_lateral=False): + return { + "modelV2": SimpleNamespace(meta=SimpleNamespace(laneChangeState=lane_change_state)), + "carControl": SimpleNamespace(latActive=lat_active), + "starpilotPlan": SimpleNamespace(lateralCheck=lateral_check), + "starpilotCarState": SimpleNamespace(pauseLateral=pause_lateral), + } + + +def _toggles(enabled=True): + return SimpleNamespace( + loud_blindspot_alert=True, + loud_blindspot_alert_when_disengaged=enabled, + ) + + +def test_loud_blindspot_alert_without_lateral_for_matching_signal(): + CS = _car_state(left_blinker=True, left_blindspot=True) + + assert should_loud_blindspot_alert_without_lateral(CS, _sm(lat_active=False), _toggles()) + assert should_loud_blindspot_alert_without_lateral(CS, _sm(lat_active=True, lateral_check=False), _toggles()) + assert should_loud_blindspot_alert_without_lateral(CS, _sm(lat_active=True, lateral_check=True, pause_lateral=True), _toggles()) + + +def test_loud_blindspot_alert_without_lateral_ignores_active_lateral(): + CS = _car_state(right_blinker=True, right_blindspot=True) + + assert not should_loud_blindspot_alert_without_lateral(CS, _sm(lat_active=True, lateral_check=True), _toggles()) + + +def test_loud_blindspot_alert_without_lateral_requires_matching_side_and_toggle(): + assert not should_loud_blindspot_alert_without_lateral(_car_state(left_blinker=True, right_blindspot=True), _sm(), _toggles()) + assert not should_loud_blindspot_alert_without_lateral(_car_state(left_blinker=True, right_blinker=True, left_blindspot=True), _sm(), _toggles()) + assert not should_loud_blindspot_alert_without_lateral(_car_state(left_blinker=True, left_blindspot=True), _sm(), _toggles(enabled=False)) + + +def test_loud_blindspot_alert_without_lateral_skips_normal_lane_change_alert_path(): + CS = _car_state(left_blinker=True, left_blindspot=True) + + assert not should_loud_blindspot_alert_without_lateral(CS, _sm(lane_change_state=LaneChangeState.preLaneChange), _toggles()) diff --git a/selfdrive/ui/layouts/settings/starpilot/sounds.py b/selfdrive/ui/layouts/settings/starpilot/sounds.py index eadc8fec9..f6162bac2 100644 --- a/selfdrive/ui/layouts/settings/starpilot/sounds.py +++ b/selfdrive/ui/layouts/settings/starpilot/sounds.py @@ -404,6 +404,7 @@ class StarPilotSoundsLayout(_SettingsPage): "GreenLightAlert", "LeadDepartingAlert", "LoudBlindspotAlert", + "LoudBlindspotAlertWhenDisengaged", "SpeedLimitChangedAlert", ] @@ -445,6 +446,12 @@ class StarPilotSoundsLayout(_SettingsPage): "is_enabled": lambda: starpilot_state.car_state.hasBSM, "disabled_label": tr_noop("Needs BSM") }, + "LoudBlindspotAlertWhenDisengaged": { + "title": tr_noop("Loud While Paused"), + "subtitle": tr_noop("When lateral is off or paused"), + "is_enabled": lambda: starpilot_state.car_state.hasBSM and self._params.get_bool("LoudBlindspotAlert"), + "disabled_label": tr_noop("Enable Loud Blindspot") + }, "SpeedLimitChangedAlert": { "title": tr_noop("Speed Limit"), "subtitle": tr_noop("When posted speed limit changes"), diff --git a/selfdrive/ui/layouts/settings/starpilot/vehicle.py b/selfdrive/ui/layouts/settings/starpilot/vehicle.py index 42a1f6037..4f077dc87 100644 --- a/selfdrive/ui/layouts/settings/starpilot/vehicle.py +++ b/selfdrive/ui/layouts/settings/starpilot/vehicle.py @@ -144,7 +144,13 @@ class VehicleSettingsManagerView(AetherInteractiveMixin, Widget): "get_state": lambda: self._controller._params.get_bool("GMDashSpoofOffsets"), "set_state": lambda s: self._controller._on_toggle("GMDashSpoofOffsets"), }) - if cs.isGM: + if cs.isGM and cs.hasOpenpilotLongitudinal: + toggles.append({ + "title": tr("Stock Dash Disengaged"), + "subtitle": tr("Use the stock GM dash set speed while openpilot is not engaged."), + "get_state": lambda: self._controller._params.get_bool("GMStockDashWhenNotEngaged"), + "set_state": lambda s: self._controller._on_toggle("GMStockDashWhenNotEngaged"), + }) toggles.append({ "title": tr("Remote Start Panda"), "get_state": lambda: self._controller._params.get_bool("RemoteStartBootsComma"), diff --git a/starpilot/common/safe_mode.py b/starpilot/common/safe_mode.py index 6f4eafdc3..18b9d87ec 100644 --- a/starpilot/common/safe_mode.py +++ b/starpilot/common/safe_mode.py @@ -113,6 +113,7 @@ SAFE_MODE_MANAGED_KEYS = ( "CESpeed", "CESpeedLead", "CCMLead", + "CCMLaunchAssist", "CCMSetSpeedMargin", "CCMSpeed", "CCMSpeedLead", @@ -183,6 +184,7 @@ SAFE_MODE_MANAGED_KEYS = ( "GMAutoHold", "GMPedalLongitudinal", "GMDashSpoofOffsets", + "GMStockDashWhenNotEngaged", "LongPitch", ) diff --git a/starpilot/common/starpilot_variables.py b/starpilot/common/starpilot_variables.py index 6d2f36e18..cfaf41d0d 100644 --- a/starpilot/common/starpilot_variables.py +++ b/starpilot/common/starpilot_variables.py @@ -723,6 +723,7 @@ class StarPilotVariables: toggle.conditional_chill_speed_lead = self.get_value("CCMSpeedLead", cast=float, condition=toggle.conditional_chill_mode, conversion=speed_conversion) toggle.conditional_chill_speed_margin = self.get_value("CCMSetSpeedMargin", cast=float, condition=toggle.conditional_chill_mode, conversion=speed_conversion) toggle.conditional_chill_lead = self.get_value("CCMLead", condition=toggle.conditional_chill_mode) + toggle.conditional_chill_launch_assist = self.get_value("CCMLaunchAssist", condition=toggle.conditional_chill_mode) toggle.cem_status = ( self.get_value("ShowCEMStatus", condition=toggle.conditional_experimental_mode) or self.get_value("ShowCCMStatus", condition=toggle.conditional_chill_mode) or @@ -738,6 +739,7 @@ class StarPilotVariables: toggle.green_light_alert = self.get_value("GreenLightAlert", condition=custom_alerts) toggle.lead_departing_alert = self.get_value("LeadDepartingAlert", condition=custom_alerts) toggle.loud_blindspot_alert = self.get_value("LoudBlindspotAlert", condition=custom_alerts and has_bsm) + toggle.loud_blindspot_alert_when_disengaged = self.get_value("LoudBlindspotAlertWhenDisengaged", condition=toggle.loud_blindspot_alert) toggle.speed_limit_changed_alert = self.get_value("SpeedLimitChangedAlert", condition=custom_alerts) toggle.custom_personalities = toggle.openpilot_longitudinal and self.get_value("CustomPersonalities") @@ -1305,6 +1307,7 @@ class StarPilotVariables: toggle.green_light_alert = False toggle.lead_departing_alert = False toggle.loud_blindspot_alert = False + toggle.loud_blindspot_alert_when_disengaged = False toggle.speed_limit_changed_alert = False toggle.startup_alert_top = "Be ready to take over at any time" @@ -1326,6 +1329,10 @@ class StarPilotVariables: "GMDashSpoofOffsets", condition=toggle.car_make == "gm" and toggle.has_pedal, ) + toggle.gm_stock_dash_when_not_engaged = self.get_value( + "GMStockDashWhenNotEngaged", + condition=toggle.car_make == "gm" and toggle.openpilot_longitudinal, + ) toggle.long_pitch = self.get_value( "LongPitch", condition=toggle.openpilot_longitudinal and toggle.car_make == "gm", diff --git a/starpilot/controls/lib/conditional_chill_mode.py b/starpilot/controls/lib/conditional_chill_mode.py index bd8aaa7a0..19bf2436e 100644 --- a/starpilot/controls/lib/conditional_chill_mode.py +++ b/starpilot/controls/lib/conditional_chill_mode.py @@ -12,17 +12,24 @@ from openpilot.starpilot.common.experimental_state import ( class ConditionalChillMode: CCM_STOP_MODEL_TIME = 7.0 - CHILL_ENTRY_CONFIRM_TIME = 0.35 + CHILL_SPEED_ENTRY_CONFIRM_TIME = 0.35 + CHILL_LEAD_ENTRY_CONFIRM_TIME = 1.0 + CHILL_LAUNCH_ENTRY_CONFIRM_TIME = 0.0 CHILL_EXIT_BUFFER_TIME = 0.35 CHILL_MIN_DWELL_TIME = 1.2 + CHILL_LAUNCH_EXIT_SPEED = 15 * CV.MPH_TO_MS + CHILL_LAUNCH_MAX_ENTRY_SPEED = 1.0 + CHILL_LAUNCH_MAX_BRAKE = 0.2 + CHILL_LAUNCH_MAX_CLOSING_SPEED = 0.75 + STABLE_LEAD_MIN_MODEL_PROB = 0.9 STABLE_LEAD_MAX_BRAKE = 0.2 STABLE_LEAD_MIN_SPEED = 1.5 STABLE_LEAD_MAX_DISTANCE = 90.0 STABLE_LEAD_MAX_DISTANCE_TIME = 4.5 - STABLE_LEAD_MAX_CLOSING_SPEED = 0.75 - STABLE_LEAD_MAX_CLOSING_RATIO = 0.03 + STABLE_LEAD_MAX_CLOSING_SPEED = 1.25 + STABLE_LEAD_MAX_CLOSING_RATIO = 0.05 ADJACENT_LEAD_VETO_MIN_SPEED = 1.0 ADJACENT_LEAD_VETO_MAX_DISTANCE = 65.0 @@ -43,6 +50,7 @@ class ConditionalChillMode: self._soft_exit_since = 0.0 self._chill_hold_until = 0.0 self._prev_cc_status = None + self._launch_active = False def update(self, v_ego, v_cruise, sm, starpilot_toggles): now = time.monotonic() @@ -57,23 +65,24 @@ class ConditionalChillMode: return self._refresh_detector(v_ego, sm) + auto_status, launch_candidate = self._get_chill_status(v_ego, v_cruise, sm, starpilot_toggles) - if safe_mode or self._has_hard_veto(v_ego, sm): + if safe_mode or self._has_hard_veto(v_ego, sm, allow_launch=launch_candidate): self._reset_timers() self.experimental_mode = False if safe_mode else True self.status_value = CCStatus["OFF"] self._write_status(CCStatus["OFF"]) return - auto_status = self._get_chill_status(v_ego, v_cruise, sm, starpilot_toggles) chill_candidate = auto_status != CCStatus["OFF"] + entry_confirm_time = self._get_entry_confirm_time(auto_status, launch_candidate) if chill_candidate: if self._candidate_since == 0.0: self._candidate_since = now self._soft_exit_since = 0.0 - if not self.experimental_mode or (now - self._candidate_since) >= self.CHILL_ENTRY_CONFIRM_TIME: + if not self.experimental_mode or (now - self._candidate_since) >= entry_confirm_time: self.experimental_mode = False self._active_auto_status = auto_status self._chill_hold_until = max(self._chill_hold_until, now + self.CHILL_MIN_DWELL_TIME) @@ -103,6 +112,7 @@ class ConditionalChillMode: self._candidate_since = 0.0 self._soft_exit_since = 0.0 self._chill_hold_until = 0.0 + self._launch_active = False def _refresh_detector(self, v_ego, sm): detector_toggles = type("DetectorToggles", (), { @@ -116,8 +126,8 @@ class ConditionalChillMode: self.detector.slow_lead(detector_toggles, v_ego) self.detector.stop_sign_and_light(v_ego, sm, self.CCM_STOP_MODEL_TIME) - def _has_hard_veto(self, v_ego, sm): - if sm["carState"].standstill: + def _has_hard_veto(self, v_ego, sm, allow_launch=False): + if sm["carState"].standstill and not allow_launch: return True if sm["carState"].leftBlinker or sm["carState"].rightBlinker: @@ -138,7 +148,7 @@ class ConditionalChillMode: if self._adjacent_lead_ambiguous(sm, v_ego): return True - return self._low_speed_stop_scene(v_ego) + return self._low_speed_stop_scene(v_ego) and not allow_launch def _low_speed_stop_scene(self, v_ego): if v_ego >= self.LOW_SPEED_STOP_SCENE_MAX_SPEED: @@ -157,6 +167,11 @@ class ConditionalChillMode: return lead_distance < lead_distance_limit and lead_speed < max(6.0, v_ego + 0.5) def _get_chill_status(self, v_ego, v_cruise, sm, starpilot_toggles): + if getattr(starpilot_toggles, "conditional_chill_launch_assist", False): + launch_status = self._get_launch_status(v_ego, sm) + if launch_status != CCStatus["OFF"]: + return launch_status, True + lead = self.starpilot_planner.lead_one lead_status = bool(getattr(lead, "status", False)) tracking_lead = bool(getattr(self.starpilot_planner, "tracking_lead", False)) @@ -165,13 +180,13 @@ class ConditionalChillMode: if (not lead_status and not tracking_lead and v_ego >= starpilot_toggles.conditional_chill_speed and set_speed_error >= starpilot_toggles.conditional_chill_speed_margin): - return CCStatus["SPEED"] + return CCStatus["SPEED"], False if not starpilot_toggles.conditional_chill_lead: - return CCStatus["OFF"] + return CCStatus["OFF"], False if v_ego < starpilot_toggles.conditional_chill_speed_lead or not lead_status or not tracking_lead: - return CCStatus["OFF"] + return CCStatus["OFF"], False lead_distance = float(getattr(lead, "dRel", float("inf"))) lead_speed = float(getattr(lead, "vLead", 0.0)) @@ -183,15 +198,80 @@ class ConditionalChillMode: lead_confident = bool(getattr(lead, "radar", False)) or lead_prob >= self.STABLE_LEAD_MIN_MODEL_PROB if not lead_confident: - return CCStatus["OFF"] + return CCStatus["OFF"], False if lead_distance >= max_distance or lead_speed <= self.STABLE_LEAD_MIN_SPEED: - return CCStatus["OFF"] + return CCStatus["OFF"], False if lead_brake > self.STABLE_LEAD_MAX_BRAKE or closing_speed > max_closing_speed: + return CCStatus["OFF"], False + + return CCStatus["LEAD"], False + + def _get_launch_status(self, v_ego, sm): + if self._launch_active and self._launch_exit_required(v_ego, sm): + self._launch_active = False return CCStatus["OFF"] - return CCStatus["LEAD"] + if self._launch_active: + return self._get_launch_cc_status() + + if not self._launch_scene_eligible(v_ego, sm): + return CCStatus["OFF"] + + self._launch_active = True + return self._get_launch_cc_status() + + def _launch_scene_eligible(self, v_ego, sm): + if v_ego > self.CHILL_LAUNCH_MAX_ENTRY_SPEED and not self._launch_active: + return False + + selfdrive_state = self._get_sm_service(sm, "selfdriveState") + longitudinal_plan = self._get_sm_service(sm, "longitudinalPlan") + if selfdrive_state is None or longitudinal_plan is None: + return False + + if not bool(getattr(selfdrive_state, "enabled", False)): + return False + + if bool(getattr(longitudinal_plan, "shouldStop", False)) or not bool(getattr(longitudinal_plan, "allowThrottle", False)): + return False + + lead = getattr(self.starpilot_planner, "lead_one", None) + lead_status = bool(getattr(lead, "status", False)) + tracking_lead = bool(getattr(self.starpilot_planner, "tracking_lead", False)) + if not lead_status and not tracking_lead: + return True + + lead_speed = float(getattr(lead, "vLead", 0.0)) + lead_brake = max(0.0, -float(getattr(lead, "aLeadK", 0.0))) + closing_speed = max(0.0, v_ego - lead_speed) + + return lead_speed > self.STABLE_LEAD_MIN_SPEED and lead_brake <= self.CHILL_LAUNCH_MAX_BRAKE and closing_speed <= self.CHILL_LAUNCH_MAX_CLOSING_SPEED + + def _launch_exit_required(self, v_ego, sm): + if v_ego >= self.CHILL_LAUNCH_EXIT_SPEED: + return True + + if not self._launch_scene_eligible(v_ego, sm): + return True + + return False + + def _get_launch_cc_status(self): + lead = getattr(self.starpilot_planner, "lead_one", None) + lead_status = bool(getattr(lead, "status", False)) + tracking_lead = bool(getattr(self.starpilot_planner, "tracking_lead", False)) + return CCStatus["LEAD"] if lead_status or tracking_lead else CCStatus["SPEED"] + + def _get_entry_confirm_time(self, auto_status, launch_candidate): + if launch_candidate: + return self.CHILL_LAUNCH_ENTRY_CONFIRM_TIME + + if auto_status == CCStatus["LEAD"]: + return self.CHILL_LEAD_ENTRY_CONFIRM_TIME + + return self.CHILL_SPEED_ENTRY_CONFIRM_TIME def _adjacent_lead_ambiguous(self, sm, v_ego): radar_state = self._get_sm_service(sm, "starpilotRadarState") diff --git a/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json b/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json index 54eff578a..8d7210ea8 100644 --- a/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json +++ b/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json @@ -1665,6 +1665,14 @@ "ui_type": "toggle", "parent_key": "ConditionalChill" }, + { + "key": "CCMLaunchAssist", + "label": "Launch Assist", + "description": "Temporarily switch to \"Chill Mode\" when starting from a stop if planner is already allowing throttle. Useful if your car launches too slowly from lights or stop signs.", + "data_type": "bool", + "ui_type": "toggle", + "parent_key": "ConditionalChill" + }, { "key": "CCMSetSpeedMargin", "label": "Set Speed Margin", @@ -2242,6 +2250,14 @@ "ui_type": "toggle", "parent_key": "CustomAlerts" }, + { + "key": "LoudBlindspotAlertWhenDisengaged", + "label": "Blind Spot Alert When Disengaged", + "description": "Play the loud blind spot alert while lateral control is off or paused. Useful when steering pauses on turn signal, since the lane-change state machine is inactive then.", + "data_type": "bool", + "ui_type": "toggle", + "parent_key": "CustomAlerts" + }, { "key": "SpeedLimitChangedAlert", "label": "Speed Limit Changed Alert", @@ -2401,6 +2417,13 @@ "data_type": "bool", "ui_type": "toggle" }, + { + "key": "GMStockDashWhenNotEngaged", + "label": "Stock Dash When Disengaged", + "description": "Use the stock GM dashboard set speed while openpilot is not engaged. When off, StarPilot keeps the existing dashboard spoof behavior.", + "data_type": "bool", + "ui_type": "toggle" + }, { "key": "LongPitch", "label": "Smooth Pedal Response on Hills", @@ -3331,4 +3354,4 @@ } ] } -] \ No newline at end of file +] diff --git a/starpilot/ui/qt/offroad/longitudinal_settings.cc b/starpilot/ui/qt/offroad/longitudinal_settings.cc index 4585ec614..91607b308 100644 --- a/starpilot/ui/qt/offroad/longitudinal_settings.cc +++ b/starpilot/ui/qt/offroad/longitudinal_settings.cc @@ -102,6 +102,7 @@ StarPilotLongitudinalPanel::StarPilotLongitudinalPanel(StarPilotSettingsWindow * {"PersistChillState", tr("Persist Chill State"), tr("Keep your manual Conditional Chill override through reboots until you manually clear it."), ""}, {"CCMSpeed", tr("Above"), tr("Switch to \"Chill Mode\" on open roads above this speed when no lead is detected and the car is still below the set speed."), ""}, {"CCMLead", tr("Stable Lead Ahead"), tr("Switch to \"Chill Mode\" when following a steady, well-tracked lead vehicle at cruising speeds."), ""}, + {"CCMLaunchAssist", tr("Launch Assist"), tr("Temporarily switch to \"Chill Mode\" when starting from a stop if planner is already allowing throttle. Useful if your car launches too slowly from lights or stop signs."), ""}, {"CCMSetSpeedMargin", tr("Set Speed Margin"), tr("How far below the set speed the car must be before open-road Conditional Chill can engage."), ""}, {"ShowCCMStatus", tr("Status Widget"), tr("Show which condition triggered \"Chill Mode\" on the driving screen."), ""}, diff --git a/starpilot/ui/qt/offroad/longitudinal_settings.h b/starpilot/ui/qt/offroad/longitudinal_settings.h index 5719b5a1e..3060861d8 100644 --- a/starpilot/ui/qt/offroad/longitudinal_settings.h +++ b/starpilot/ui/qt/offroad/longitudinal_settings.h @@ -30,7 +30,7 @@ private: QSet advancedLongitudinalTuneKeys = {"EVTuning", "TruckTuning", "LongitudinalActuatorDelay", "MaxDesiredAcceleration", "StartAccel", "StopAccel", "StoppingDecelRate", "VEgoStarting", "VEgoStopping"}; QSet aggressivePersonalityKeys = {"AggressiveFollow", "AggressiveFollowHigh", "AggressiveJerkAcceleration", "AggressiveJerkDeceleration", "AggressiveJerkDanger", "AggressiveJerkSpeed", "AggressiveJerkSpeedDecrease", "ResetAggressivePersonality"}; - QSet conditionalChillKeys = {"PersistChillState", "CCMSpeed", "CCMSpeedLead", "CCMLead", "CCMSetSpeedMargin", "ShowCCMStatus"}; + QSet conditionalChillKeys = {"PersistChillState", "CCMSpeed", "CCMSpeedLead", "CCMLead", "CCMLaunchAssist", "CCMSetSpeedMargin", "ShowCCMStatus"}; QSet conditionalExperimentalKeys = {"PersistExperimentalState", "CESpeed", "CESpeedLead", "CECurves", "CELead", "CEModelStopTime", "CESignalSpeed", "CEStopLights", "ShowCEMStatus"}; QSet curveSpeedKeys = {"CalibratedLateralAcceleration", "CalibrationProgress", "ResetCurveData", "ShowCSCStatus"}; QSet customDrivingPersonalityKeys = {"AggressivePersonalityProfile", "RelaxedPersonalityProfile", "StandardPersonalityProfile", "TrafficPersonalityProfile"}; diff --git a/starpilot/ui/qt/offroad/sounds_settings.cc b/starpilot/ui/qt/offroad/sounds_settings.cc index f204c5349..08052a384 100644 --- a/starpilot/ui/qt/offroad/sounds_settings.cc +++ b/starpilot/ui/qt/offroad/sounds_settings.cc @@ -39,6 +39,7 @@ StarPilotSoundsPanel::StarPilotSoundsPanel(StarPilotSettingsWindow *parent, bool {"GreenLightAlert", tr("Green Light Alert"), tr("Play an alert when the model predicts a red light has turned green.

Disclaimer: openpilot does not explicitly detect traffic lights. This alert is based on end-to-end model predictions from camera input and may trigger even when the light has not changed."), ""}, {"LeadDepartingAlert", tr("Lead Departing Alert"), tr("Play an alert when the lead vehicle departs from a stop."), ""}, {"LoudBlindspotAlert", tr("Loud \"Car Detected in Blindspot\" Alert"), tr("Play a louder alert if a vehicle is in the blind spot when attempting to change lanes. Based on the \"Car Detected in Blindspot\" event."), ""}, + {"LoudBlindspotAlertWhenDisengaged", tr("Blind Spot Alert When Disengaged"), tr("Play the loud blind spot alert while lateral control is off or paused.

Useful when steering pauses on turn signal, since the lane-change state machine is inactive then."), ""}, {"SpeedLimitChangedAlert", tr("Speed Limit Changed Alert"), tr("Play an alert when the posted speed limit changes."), ""} }; @@ -192,6 +193,10 @@ void StarPilotSoundsPanel::updateToggles() { setVisible &= parent->hasBSM; } + else if (key == "LoudBlindspotAlertWhenDisengaged") { + setVisible &= parent->hasBSM && params.getBool("LoudBlindspotAlert"); + } + else if (key == "SpeedLimitChangedAlert") { setVisible &= params.getBool("ShowSpeedLimits") || (parent->hasOpenpilotLongitudinal && params.getBool("SpeedLimitController")); } diff --git a/starpilot/ui/qt/offroad/sounds_settings.h b/starpilot/ui/qt/offroad/sounds_settings.h index a6b63db9b..f8d448e4d 100644 --- a/starpilot/ui/qt/offroad/sounds_settings.h +++ b/starpilot/ui/qt/offroad/sounds_settings.h @@ -27,7 +27,7 @@ private: QSet alertCooldownKeys {"SwitchbackModeCooldown"}; QSet alertVolumeControlKeys {"BelowSteerSpeedVolume", "DisengageVolume", "EngageVolume", "PromptDistractedVolume", "PromptVolume", "RefuseVolume", "WarningImmediateVolume", "WarningSoftVolume"}; - QSet customAlertsKeys {"GoatScream", "GoatScreamCriticalAlerts", "GreenLightAlert", "LeadDepartingAlert", "LoudBlindspotAlert", "SpeedLimitChangedAlert"}; + QSet customAlertsKeys {"GoatScream", "GoatScreamCriticalAlerts", "GreenLightAlert", "LeadDepartingAlert", "LoudBlindspotAlert", "LoudBlindspotAlertWhenDisengaged", "SpeedLimitChangedAlert"}; QSet parentKeys; diff --git a/starpilot/ui/qt/offroad/vehicle_settings.cc b/starpilot/ui/qt/offroad/vehicle_settings.cc index e094cdfa3..0d829143e 100644 --- a/starpilot/ui/qt/offroad/vehicle_settings.cc +++ b/starpilot/ui/qt/offroad/vehicle_settings.cc @@ -173,6 +173,7 @@ StarPilotVehiclesPanel::StarPilotVehiclesPanel(StarPilotSettingsWindow *parent, {"GMToggles", tr("General Motors Settings"), tr("StarPilot features for General Motors vehicles."), ""}, {"GMPedalLongitudinal", tr("Use Pedal For Longitudinal"), tr("Use the pedal interceptor for full longitudinal control on supported GM vehicles."), ""}, {"GMDashSpoofOffsets", tr("Apply Offsets To Dash Spoof"), tr("On GM pedal-long cars, add the configured set-speed offset to the spoofed dash set speed so it matches the on-screen set speed."), ""}, + {"GMStockDashWhenNotEngaged", tr("Stock Dash When Disengaged"), tr("Use the stock GM dashboard set speed while openpilot is not engaged.

When off, StarPilot keeps the existing dashboard spoof behavior."), ""}, {"LongPitch", tr("Smooth Pedal Response on Hills"), tr("Smoothen acceleration and braking when driving downhill/uphill."), ""}, {"RemoteStartBootsComma", tr("Remote Start Boots comma"), tr("Use the remote-start GM panda firmware at boot.

Required for GM remote-start startup signal behavior."), ""}, {"RemapCancelToDistance", tr("Remap Cancel Button"), tr("On pedal-interceptor Bolts, treat the steering-wheel CANCEL button as an extra mappable button."), ""}, diff --git a/starpilot/ui/qt/offroad/vehicle_settings.h b/starpilot/ui/qt/offroad/vehicle_settings.h index 1485b6e99..b8a7dbab7 100644 --- a/starpilot/ui/qt/offroad/vehicle_settings.h +++ b/starpilot/ui/qt/offroad/vehicle_settings.h @@ -23,8 +23,8 @@ private: std::map toggles; - QSet gmKeys = {"GMPedalLongitudinal", "GMDashSpoofOffsets", "LongPitch", "RemoteStartBootsComma", "RemapCancelToDistance", "VoltSNG"}; - QSet longitudinalKeys = {"FrogsGoMoosTweak", "GMDashSpoofOffsets", "LongPitch", "RemapCancelToDistance", "SNGHack", "VoltSNG"}; + QSet gmKeys = {"GMPedalLongitudinal", "GMDashSpoofOffsets", "GMStockDashWhenNotEngaged", "LongPitch", "RemoteStartBootsComma", "RemapCancelToDistance", "VoltSNG"}; + QSet longitudinalKeys = {"FrogsGoMoosTweak", "GMDashSpoofOffsets", "GMStockDashWhenNotEngaged", "LongPitch", "RemapCancelToDistance", "SNGHack", "VoltSNG"}; QSet subaruKeys = {"SubaruSNG"}; QSet toyotaKeys = {"ClusterOffset", "FrogsGoMoosTweak", "LockDoorsTimer", "SNGHack", "ToyotaDoors"}; QSet vehicleInfoKeys = {"BlindSpotSupport", "HardwareDetected", "OpenpilotLongitudinal", "PedalSupport", "RadarSupport", "SDSUSupport", "SNGSupport"};