diff --git a/opendbc_repo/opendbc/car/gm/carcontroller.py b/opendbc_repo/opendbc/car/gm/carcontroller.py index 6bf6714ff..3288e5bf0 100644 --- a/opendbc_repo/opendbc/car/gm/carcontroller.py +++ b/opendbc_repo/opendbc/car/gm/carcontroller.py @@ -54,6 +54,10 @@ VOLT_ONE_PEDAL_DECEL_RATE_LIMIT_DOWN = 0.8 * DT_CTRL * 4 VOLT_ONE_PEDAL_ACCEL_PITCH_FACTOR_BP = [4.0, 8.0] VOLT_ONE_PEDAL_ACCEL_PITCH_FACTOR_V = [0.4, 1.0] VOLT_ONE_PEDAL_ACCEL_PITCH_FACTOR_INCLINE_V = [0.2, 1.0] +TRUCK_LONG_SMOOTH_CARS = { + CAR.CHEVROLET_SILVERADO, + CAR.CHEVROLET_SILVERADO_CC, +} def get_stock_cc_active_for_cancel(CP, CS): @@ -149,6 +153,22 @@ def get_testing_ground_1_brake_switch_bias(v_ego: float) -> int: return int(round(np.interp(v_ego, [0.0, 6.0, 15.0, 30.0], [40.0, 85.0, 130.0, 170.0]))) +def shape_truck_positive_accel(accel: float, v_ego: float, enabled: bool) -> float: + if not enabled or accel <= 0.0 or v_ego < 8.0: + return accel + + low_scale = float(np.interp(v_ego, [8.0, 15.0, 25.0, 35.0], [0.60, 0.45, 0.32, 0.25])) + mid_scale = float(np.interp(v_ego, [8.0, 15.0, 25.0, 35.0], [0.82, 0.72, 0.60, 0.52])) + + if accel <= 0.18: + return accel * low_scale + if accel <= 0.45: + return float(np.interp(accel, [0.18, 0.45], [0.18 * low_scale, 0.45 * mid_scale])) + if accel <= 0.8: + return float(np.interp(accel, [0.45, 0.8], [0.45 * mid_scale, 0.8])) + return accel + + def get_lka_steering_cmd_counter(next_counter: int, CS) -> int: if getattr(CS, "loopback_lka_steering_cmd_updated", False): return (getattr(CS, "loopback_lka_steering_cmd_counter", next_counter) + 1) % 4 @@ -197,17 +217,18 @@ def get_volt_one_pedal_target_decel(v_ego: float) -> float: def should_activate_volt_one_pedal(one_pedal_ready: bool, cruise_main: bool, long_active: bool, gas_pressed: bool, brake_pressed: bool, regen_braking: bool, - single_pedal_mode: bool, gear_shifter, moving_backward: bool) -> bool: + single_pedal_mode: bool, gear_shifter, drive_time_s: float) -> bool: + # Volt rear wheel direction bits can falsely report reverse while stopping in L. return ( one_pedal_ready and cruise_main and single_pedal_mode and gear_shifter in AUTO_HOLD_DRIVE_GEARS and + drive_time_s >= AUTO_HOLD_MIN_DRIVE_TIME_S and not long_active and not gas_pressed and not brake_pressed and - not regen_braking and - not moving_backward + not regen_braking ) @@ -505,7 +526,7 @@ class CarController(CarControllerBase): CS.out.regenBraking, bool(getattr(CS, "single_pedal_mode", False)), CS.out.gearShifter, - bool(getattr(CS, "moving_backward", False)), + float(getattr(CS, "one_pedal_drive_time", 0.0)), ) if self.frame % 4 == 0: @@ -638,7 +659,7 @@ class CarController(CarControllerBase): volt_one_pedal_hold_active = ( volt_one_pedal_braking and not auto_hold_active and - CS.auto_hold_drive_time >= AUTO_HOLD_MIN_DRIVE_TIME_S and + CS.one_pedal_drive_time >= AUTO_HOLD_MIN_DRIVE_TIME_S and (CS.out.standstill or CS.out.vEgo < 0.02) ) @@ -745,7 +766,16 @@ class CarController(CarControllerBase): if testing_ground.use_1: accel_max = min(accel_max, np.interp(CS.out.vEgo, [0.0, 4.0, 12.0], [1.25, 1.6, self.params.ACCEL_MAX])) - accel_cmd = float(np.clip(actuators.accel + accel_due_to_pitch, self.params.ACCEL_MIN, accel_max)) + accel_input = actuators.accel + accel_due_to_pitch + if ( + getattr(starpilot_toggles, "truck_tuning", False) and + self.CP.carFingerprint in TRUCK_LONG_SMOOTH_CARS and + getattr(self.CP, "transmissionType", None) == TransmissionType.automatic and + not self.CP.enableGasInterceptorDEPRECATED + ): + accel_input = shape_truck_positive_accel(accel_input, CS.out.vEgo, True) + + accel_cmd = float(np.clip(accel_input, self.params.ACCEL_MIN, accel_max)) torque = self.tireRadius * ((self.mass * accel_cmd) + (0.5 * self.coeffDrag * self.frontalArea * self.airDensity * CS.out.vEgo ** 2)) scaled_torque = torque + self.params.ZERO_GAS apply_gas_torque = np.clip(scaled_torque, self.params.MAX_ACC_REGEN, gas_max) diff --git a/opendbc_repo/opendbc/car/gm/carstate.py b/opendbc_repo/opendbc/car/gm/carstate.py index ae3e57f2a..843b7746b 100644 --- a/opendbc_repo/opendbc/car/gm/carstate.py +++ b/opendbc_repo/opendbc/car/gm/carstate.py @@ -78,6 +78,7 @@ class CarState(CarStateBase): self.auto_hold_armed = False self.auto_hold_engaged = False self.auto_hold_drive_time = 0.0 + self.one_pedal_drive_time = 0.0 self.auto_hold_fault_suppression_timer = 0.0 self.regen_release_timer = 0.0 self.user_regen_paddle_pressed = False @@ -215,8 +216,10 @@ class CarState(CarStateBase): self.auto_hold_drive_time = AUTO_HOLD_MIN_DRIVE_TIME_S else: self.auto_hold_drive_time = min(self.auto_hold_drive_time + DT_CTRL, AUTO_HOLD_MIN_DRIVE_TIME_S) + self.one_pedal_drive_time = min(self.one_pedal_drive_time + DT_CTRL, AUTO_HOLD_MIN_DRIVE_TIME_S) else: self.auto_hold_drive_time = 0.0 + self.one_pedal_drive_time = 0.0 self.auto_hold_armed = False self.auto_hold_engaged = False diff --git a/opendbc_repo/opendbc/car/gm/interface.py b/opendbc_repo/opendbc/car/gm/interface.py index b8155bf8d..502d1637b 100755 --- a/opendbc_repo/opendbc/car/gm/interface.py +++ b/opendbc_repo/opendbc/car/gm/interface.py @@ -217,6 +217,10 @@ class CarInterface(CarInterfaceBase): gm_auto_hold = params.get_bool("GMAutoHold") except UnknownKeyName: gm_auto_hold = False + try: + volt_one_pedal_mode = params.get_bool("VoltOnePedalMode") + except UnknownKeyName: + volt_one_pedal_mode = False ret.brand = "gm" ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.gm)] @@ -671,8 +675,8 @@ class CarInterface(CarInterfaceBase): if remote_start_boots_comma: ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_REMOTE_START_BOOTS_COMMA.value - volt_stock_auto_hold_safety = ( - gm_auto_hold and + volt_stock_friction_brake_safety = ( + (gm_auto_hold or volt_one_pedal_mode) and candidate in { CAR.CHEVROLET_VOLT, CAR.CHEVROLET_VOLT_2019, @@ -680,11 +684,11 @@ class CarInterface(CarInterfaceBase): CAR.CHEVROLET_VOLT_CAMERA, } ) - if volt_stock_auto_hold_safety: - # Reuse the paddle-scheduler safety bit as a Volt auto-hold marker on - # non-pedal paths. Hold can run while OP longitudinal is configured but - # not currently active, so the bit must be present regardless of the - # current long-control mode. + if volt_stock_friction_brake_safety: + # Reuse the paddle-scheduler safety bit as a Volt stock friction-brake + # marker on non-pedal paths. Both auto hold and one-pedal can run while + # OP longitudinal is configured but not currently active, so the bit must + # be present regardless of the current long-control mode. ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value use_panda_3d1_sched = ( diff --git a/opendbc_repo/opendbc/car/gm/tests/test_carcontroller.py b/opendbc_repo/opendbc/car/gm/tests/test_carcontroller.py index 20b4d4eae..84441397c 100644 --- a/opendbc_repo/opendbc/car/gm/tests/test_carcontroller.py +++ b/opendbc_repo/opendbc/car/gm/tests/test_carcontroller.py @@ -43,6 +43,7 @@ from opendbc.car.gm.carcontroller import ( get_volt_one_pedal_target_decel, get_testing_ground_1_brake_switch_bias, get_stock_cc_active_for_cancel, + shape_truck_positive_accel, should_activate_auto_hold, should_activate_volt_one_pedal, should_send_stock_long_cancel, @@ -109,7 +110,7 @@ def test_stock_cancel_is_suppressed_when_acc_is_faulted(): cs = _cs(True, AccState.FAULTED) cs.out.accFaulted = True - assert not get_stock_cc_active_for_cancel(CP, cs) + assert get_stock_cc_active_for_cancel(CP, cs) assert not should_send_stock_long_cancel(11, cs) @@ -328,6 +329,7 @@ def test_auto_hold_activation_blocks_when_long_is_active_or_motion_is_above_thre False, False, False, + False, 0.03, ) @@ -370,7 +372,7 @@ def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input(): False, True, structs.CarState.GearShifter.low, - False, + 3.0, ) assert not should_activate_volt_one_pedal( True, @@ -381,7 +383,7 @@ def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input(): False, True, structs.CarState.GearShifter.low, - False, + 3.0, ) assert not should_activate_volt_one_pedal( True, @@ -392,7 +394,7 @@ def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input(): False, True, structs.CarState.GearShifter.low, - False, + 3.0, ) assert not should_activate_volt_one_pedal( True, @@ -403,7 +405,7 @@ def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input(): False, True, structs.CarState.GearShifter.low, - False, + 3.0, ) assert not should_activate_volt_one_pedal( True, @@ -414,7 +416,7 @@ def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input(): True, True, structs.CarState.GearShifter.low, - False, + 3.0, ) assert not should_activate_volt_one_pedal( True, @@ -425,7 +427,7 @@ def test_volt_one_pedal_activation_requires_main_l_mode_and_no_driver_input(): False, False, structs.CarState.GearShifter.drive, - False, + 3.0, ) @@ -435,6 +437,34 @@ def test_volt_one_pedal_target_decel_stays_active_above_low_speed_band(): assert get_volt_one_pedal_target_decel(20.0 * CV.MPH_TO_MS) == -1.1 +def test_volt_one_pedal_regression_ignores_noisy_wheel_direction_bits(): + assert should_activate_volt_one_pedal( + True, + True, + False, + False, + False, + False, + True, + structs.CarState.GearShifter.low, + 3.0, + ) + + +def test_volt_one_pedal_requires_time_in_drive_before_arming(): + assert not should_activate_volt_one_pedal( + True, + True, + False, + False, + False, + False, + True, + structs.CarState.GearShifter.low, + 2.5, + ) + + def test_friction_brake_mode_keeps_near_stop_disabled_for_regular_long_braking(): CP = SimpleNamespace(carFingerprint=CAR.CHEVROLET_VOLT_ASCM) @@ -494,6 +524,27 @@ def test_calc_pedal_command_keeps_strong_positive_requests_responsive(): assert pedal_gas - 0.18 > 0.04 +def test_shape_truck_positive_accel_softens_small_highway_requests(): + shaped = shape_truck_positive_accel(0.12, 26.0, True) + + assert shaped < 0.05 + + +def test_shape_truck_positive_accel_keeps_mid_follow_requests_available(): + shaped = shape_truck_positive_accel(0.45, 13.5, True) + + assert 0.30 < shaped < 0.35 + + +def test_shape_truck_positive_accel_leaves_large_requests_alone(): + assert shape_truck_positive_accel(1.0, 26.0, True) == 1.0 + + +def test_shape_truck_positive_accel_is_inactive_when_disabled_or_low_speed(): + assert shape_truck_positive_accel(0.12, 26.0, False) == 0.12 + assert shape_truck_positive_accel(0.12, 6.0, True) == 0.12 + + def test_use_interceptor_sng_launch_requires_actual_near_stop(): CP = SimpleNamespace(vEgoStarting=0.25) diff --git a/opendbc_repo/opendbc/car/gm/tests/test_gm.py b/opendbc_repo/opendbc/car/gm/tests/test_gm.py index 743f14e56..da31a018b 100644 --- a/opendbc_repo/opendbc/car/gm/tests/test_gm.py +++ b/opendbc_repo/opendbc/car/gm/tests/test_gm.py @@ -186,6 +186,24 @@ class TestGMInterface: assert car_params.openpilotLongitudinalControl assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value + def test_volt_one_pedal_sets_stock_hold_safety_bit_without_auto_hold(self): + CarInterface = interfaces[CAR.CHEVROLET_VOLT_ASCM] + fingerprint = _empty_fingerprint() + fingerprint[0][0x2FF] = 8 + + params = Params() + try: + params.put_bool("GMAutoHold", False) + params.put_bool("VoltOnePedalMode", True) + car_params = CarInterface.get_params(CAR.CHEVROLET_VOLT_ASCM, fingerprint, [], alpha_long=True, is_release=False, + docs=False, starpilot_toggles=_test_starpilot_toggles()) + finally: + params.remove("GMAutoHold") + params.remove("VoltOnePedalMode") + + assert car_params.openpilotLongitudinalControl + assert car_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_PANDA_PADDLE_SCHED.value + @parameterized.expand(VOLT_CARS) def test_volt_bsm_is_enabled_without_fingerprint_match(self, car_model): CarInterface = interfaces[car_model] diff --git a/starpilot/system/the_pond/assets/components/navigation/navigation_destination.css b/starpilot/system/the_pond/assets/components/navigation/navigation_destination.css index ff816fc15..9ce50aaf6 100644 --- a/starpilot/system/the_pond/assets/components/navigation/navigation_destination.css +++ b/starpilot/system/the_pond/assets/components/navigation/navigation_destination.css @@ -28,15 +28,6 @@ color: var(--text-muted); } -#searchSuggestions p { - font-size: var(--font-size-sm); - margin: 0; - max-width: calc(100% - 8rem); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - #searchSuggestions .suggestion-item { align-items: center; background-color: var(--input-bg); @@ -50,6 +41,32 @@ width: 100%; } +#searchSuggestions .suggestion-copy { + flex: 1; + min-width: 0; + padding-right: 1rem; +} + +#searchSuggestions .suggestion-primary, +#searchSuggestions .suggestion-secondary { + margin: 0; + overflow: hidden; + text-overflow: ellipsis; +} + +#searchSuggestions .suggestion-primary { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-demi-bold); + white-space: nowrap; +} + +#searchSuggestions .suggestion-secondary { + color: var(--text-muted); + font-size: var(--font-size-xs); + margin-top: 0.25rem; + white-space: nowrap; +} + #searchSuggestions .suggestion-item:first-child { border-top-left-radius: var(--border-radius-xl); border-top-right-radius: var(--border-radius-xl); @@ -533,4 +550,4 @@ min-width: 0; width: calc(100% - var(--padding-xl)); } -} \ No newline at end of file +} diff --git a/starpilot/system/the_pond/assets/components/navigation/navigation_destination.js b/starpilot/system/the_pond/assets/components/navigation/navigation_destination.js index aa0f097ae..576a2cfb2 100644 --- a/starpilot/system/the_pond/assets/components/navigation/navigation_destination.js +++ b/starpilot/system/the_pond/assets/components/navigation/navigation_destination.js @@ -140,6 +140,76 @@ function parseCoordinatePair(value) { return { latitude, longitude }; } +function cleanSuggestionText(value) { + if (typeof value !== "string") return ""; + return value.replace(/\s+/g, " ").trim(); +} + +function joinSuggestionParts(parts) { + const seen = new Set(); + const unique = []; + + for (const part of parts) { + const text = cleanSuggestionText(part); + if (!text) continue; + + const key = text.toLowerCase(); + if (seen.has(key)) continue; + seen.add(key); + unique.push(text); + } + + return unique.join(", "); +} + +function splitSuggestionLabel(value) { + const cleaned = cleanSuggestionText(value); + if (!cleaned) { + return { primary: "", secondary: "" }; + } + + const [primary, ...rest] = cleaned.split(",").map(part => part.trim()).filter(Boolean); + return { + primary: primary || cleaned, + secondary: rest.join(", "), + }; +} + +function getSuggestionText(suggestion) { + const rawName = cleanSuggestionText(suggestion.name || suggestion.title || ""); + const rawPlaceName = cleanSuggestionText(suggestion.place_name || ""); + const explicitAddress = joinSuggestionParts([ + suggestion.full_address, + suggestion.address, + suggestion.district, + suggestion.city, + suggestion.cityname, + suggestion.adcode && suggestion.address ? "" : suggestion.adname, + suggestion.pname, + ]); + + if (rawName) { + const splitPlaceName = rawPlaceName && rawPlaceName.toLowerCase().startsWith(`${rawName.toLowerCase()},`) + ? splitSuggestionLabel(rawPlaceName) + : { primary: rawName, secondary: "" }; + const secondary = explicitAddress || splitPlaceName.secondary; + return { + primary: splitPlaceName.primary || rawName, + secondary: secondary && secondary.toLowerCase() !== rawName.toLowerCase() ? secondary : "", + }; + } + + if (rawPlaceName) { + return splitSuggestionLabel(rawPlaceName); + } + + if (explicitAddress) { + return splitSuggestionLabel(explicitAddress); + } + + return { primary: "Unnamed Location", secondary: "" }; +} + let map; let destinationMarker; let favoriteMarkers = []; @@ -711,7 +781,7 @@ export function NavDestination() { ${() => (state.favoritesCount > 0 ? html`` : "")} ${() => (state.canToggleProvider ? html`
- ${s.is_home ? "🏠 " : ""} - ${s.is_work ? "💼 " : ""} - ${s.name || s.address} -
+ ${(() => { + const text = getSuggestionText(s); + return html` ++ ${s.is_home ? "🏠 " : ""} + ${s.is_work ? "💼 " : ""} + ${text.primary} +
+ ${text.secondary ? html`${text.secondary}
` : ""} +