Files
StarPilot/openpilot/selfdrive/controls/lib/longcontrol.py
T
firestar5683 80fbb4f6db Feature registration
Register vehicle controls, planner modes, UI actions and Galaxy services after their providers exist. Wire the shared runtime entrypoints and cover their combined behavior.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Kacper Rączy <gfw.kra@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: haraschax <6804392+haraschax@users.noreply.github.com>
Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

122 lines
6.0 KiB
Python

import numpy as np
from opendbc.car.structs import car
from openpilot.common.realtime import DT_CTRL
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N
from openpilot.common.pid import PIDController
from openpilot.selfdrive.modeld.constants import ModelConstants
from openpilot.starpilot.longitudinal.extension import LongitudinalContext, create_extension
from openpilot.starpilot.longitudinal.takeoffs import TakeoffStart, eligible as takeoff_eligible
CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
LongCtrlState = car.CarControl.Actuators.LongControlState
def long_control_state_trans(active, long_control_state, should_stop, brake_pressed, cruise_standstill):
starting_condition = (not should_stop and
not cruise_standstill and
not brake_pressed)
if not active:
long_control_state = LongCtrlState.off
else:
if long_control_state == LongCtrlState.off:
if not starting_condition:
long_control_state = LongCtrlState.stopping
else:
long_control_state = LongCtrlState.pid
elif long_control_state == LongCtrlState.stopping:
if starting_condition:
long_control_state = LongCtrlState.pid
elif long_control_state == LongCtrlState.pid:
if should_stop:
long_control_state = LongCtrlState.stopping
return long_control_state
class LongControl:
def __init__(self, CP, startup_preferences=None):
self.CP = CP
self.long_control_state = LongCtrlState.off
self.extension = create_extension(CP, startup_preferences)
self.stopping_decel_rate = self.extension.stopping_decel_rate if self.extension is not None else 1.0
self.pid = PIDController(self.extension.kp if self.extension is not None else 0.0,
(self.extension.ki if self.extension is not None and self.extension.ki is not None
else (CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV)),
rate=1 / DT_CTRL)
self.last_output_accel = 0.0
self.takeoff_start = TakeoffStart() if takeoff_eligible(CP) else None
self.takeoff_starting = False
def reset(self, *, reset_start: bool = True):
self.pid.reset()
if self.extension is not None:
self.extension.reset(reset_start=reset_start)
def update(self, active, CS, a_target, should_stop, accel_limits, *, context=None):
"""Update longitudinal control. This updates the state machine and runs a PID loop"""
context = context if context is not None else LongitudinalContext()
self.pid.neg_limit = accel_limits[0]
self.pid.pos_limit = accel_limits[1]
planner_target = a_target
previous_state = self.long_control_state
enabled_takeoff = bool(context.faster_takeoffs and self.takeoff_start is not None)
ioniq_start = self.extension is not None and self.extension.ioniq6_start is not None
native_previous = (LongCtrlState.pid if previous_state == LongCtrlState.starting and
(self.takeoff_starting or enabled_takeoff and ioniq_start) else previous_state)
native_state = long_control_state_trans(active, native_previous, should_stop,
CS.brakePressed, CS.cruiseState.standstill and
not (self.extension is not None and self.extension.ignore_cruise_standstill))
owned_start = self.extension is not None and any((self.extension.gm_start, self.extension.vehicle_stop,
self.extension.stopping_policy))
selected = bool(enabled_takeoff and not owned_start and self.takeoff_start.select(
active=active, CS=CS, target=a_target, should_stop=should_stop, limits=accel_limits,
evidence=context.start_evidence, stock_hold_allowed=ioniq_start))
if selected:
native_state = LongCtrlState.starting
self.long_control_state = native_state
if self.extension is not None:
self.long_control_state = self.extension.transition(
native_state, previous_state, active, CS, a_target, should_stop, accel_limits,
context=context)
self.takeoff_starting = selected and self.long_control_state == LongCtrlState.starting
if self.long_control_state == LongCtrlState.off:
self.reset()
output_accel = 0.
elif self.long_control_state == LongCtrlState.stopping:
output_accel = self.last_output_accel
if output_accel > self.CP.stopAccel:
output_accel = min(output_accel, 0.0)
# TODO: can we just go straight to stopAccel?
output_accel -= self.stopping_decel_rate * DT_CTRL # m/s^2/s while trying to stop
if self.extension is not None:
output_accel = self.extension.stopping_output(output_accel, a_target, should_stop, CS)
self.reset(reset_start=False)
elif self.long_control_state == LongCtrlState.starting and self.extension is not None and self.extension.starting:
output_accel = self.extension.starting_output(self.pid, a_target, accel_limits, context)
else: # LongCtrlState.pid
if self.extension is not None:
a_target = self.extension.target(a_target, CS, should_stop, self.last_output_accel, context)
error = a_target - CS.aEgo
feedforward, freeze_integrator = a_target, False
if self.extension is not None:
feedforward, freeze_integrator = self.extension.prepare_pid(
self.pid, a_target, error, CS, self.last_output_accel, accel_limits, should_stop, context)
output_accel = self.pid.update(error, speed=CS.vEgo, feedforward=feedforward,
freeze_integrator=freeze_integrator)
if self.extension is not None:
output_accel = self.extension.shape_output(output_accel, a_target, error, CS, self.last_output_accel,
previous_state, should_stop, context)
if self.takeoff_starting:
output_accel = min(output_accel, planner_target)
self.last_output_accel = np.clip(output_accel, accel_limits[0], accel_limits[1])
return self.last_output_accel