Files
StarPilot/openpilot/starpilot/lateral/lane_change_smoothing.py
T
2026-10-19 09:24:04 -05:00

66 lines
2.8 KiB
Python

"""Jerk-only lane-change shaping with a gradual return to native limits."""
import math
from openpilot.cereal import log
from openpilot.common.realtime import DT_CTRL
from openpilot.selfdrive.controls.lib.desire_helper import LANE_CHANGE_SPEED_MIN
from openpilot.selfdrive.controls.lib.drive_helpers import MAX_LATERAL_JERK, MIN_SPEED
class LaneChangeSmoother:
def __init__(self):
self.reset()
def reset(self):
self.release = 0.0
self.entry_sign = 0.0
self.previous_factor = 1.0
self.intersection_maneuver = False
def factor(self, *, active, lane_change_state, speed, minimum_speed, duration, previous, desired, turn_assist=False):
if (not active or not all(math.isfinite(value) for value in (speed, minimum_speed, duration, previous, desired)) or
not 3.0 < duration <= 8.0):
self.reset()
return 1.0
if not turn_assist:
self.intersection_maneuver = False
maneuver = lane_change_state in (log.LaneChangeState.laneChangeStarting, log.LaneChangeState.laneChangeFinishing)
# A model lane-change state can outlive an intersection turn while the car
# accelerates across the highway lane-change floor. Finish that exemption
# before admitting pacing for a new maneuver.
intersection = bool(turn_assist and maneuver and
(speed < LANE_CHANGE_SPEED_MIN or self.intersection_maneuver))
if turn_assist and (speed < LANE_CHANGE_SPEED_MIN or self.intersection_maneuver):
self.reset()
self.intersection_maneuver = intersection
return 1.0
base = min(1., math.pi ** 3 * 3.5 / duration ** 3 * 1.3 / MAX_LATERAL_JERK)
if maneuver and speed >= minimum_speed:
self.release = 2.0
if self.entry_sign == 0.0 and abs(desired - previous) > 2e-4:
self.entry_sign = math.copysign(1., desired - previous)
else:
self.release = max(0., self.release - DT_CTRL)
if self.release <= 0.:
self.entry_sign = 0.
factor = 1.0
if self.release > 0.:
release = 1. - self.release / 2.
factor = base + (1. - base) * release
step = desired - previous
if self.entry_sign and abs(step) > 5e-5 and math.copysign(1., step) == -self.entry_sign:
pursuit = (max(abs(step) - 5e-5, 0.) / .2) * max(speed, 1.) ** 2 / MAX_LATERAL_JERK
factor = max(factor, min(.6 + .4 * release, factor + pursuit))
if factor > self.previous_factor:
factor = self.previous_factor + (1. - math.exp(-DT_CTRL / .2)) * (factor - self.previous_factor)
self.previous_factor = factor
return max(0., min(1., factor))
def limit_rate(speed, previous, desired, factor):
if factor >= 1. or previous * (desired - previous) < 0.:
return desired
step = MAX_LATERAL_JERK / max(speed, MIN_SPEED) ** 2 * DT_CTRL * factor
return max(previous - step, min(previous + step, desired))