Skip to content

Commit

Permalink
Move vCruise to card (#33439)
Browse files Browse the repository at this point in the history
* Move vCruise to card

* cleanup drive_helpers

* works in sim

* update refs

* update that

* too slow :(
  • Loading branch information
adeebshihadeh authored Sep 3, 2024
1 parent 9d5eab9 commit 2f3256e
Show file tree
Hide file tree
Showing 15 changed files with 198 additions and 182 deletions.
11 changes: 7 additions & 4 deletions cereal/car.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,13 @@ struct CarState {
canErrorCounter @48 :UInt32;

# car speed
vEgo @1 :Float32; # best estimate of speed
aEgo @16 :Float32; # best estimate of acceleration
vEgoRaw @17 :Float32; # unfiltered speed from CAN sensors
vEgoCluster @44 :Float32; # best estimate of speed shown on car's instrument cluster, used for UI
vEgo @1 :Float32; # best estimate of speed
aEgo @16 :Float32; # best estimate of aCAN cceleration
vEgoRaw @17 :Float32; # unfiltered speed from wheel speed sensors
vEgoCluster @44 :Float32; # best estimate of speed shown on car's instrument cluster, used for UI

vCruise @53 :Float32; # actual set speed
vCruiseCluster @54 :Float32; # set speed to display in the UI

yawRate @22 :Float32; # best estimate of yaw rate
standstill @18 :Bool;
Expand Down
4 changes: 2 additions & 2 deletions cereal/log.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -733,8 +733,6 @@ struct ControlsState @0x97ff69c53601abf1 {

longControlState @30 :Car.CarControl.Actuators.LongControlState;
vTargetLead @3 :Float32;
vCruise @22 :Float32; # actual set speed
vCruiseCluster @63 :Float32; # set speed to display in the UI
upAccelCmd @4 :Float32;
uiAccelCmd @5 :Float32;
ufAccelCmd @33 :Float32;
Expand Down Expand Up @@ -882,6 +880,8 @@ struct ControlsState @0x97ff69c53601abf1 {
canErrorCounterDEPRECATED @57 :UInt32;
vPidDEPRECATED @2 :Float32;
alertBlinkingRateDEPRECATED @42 :Float32;
vCruiseDEPRECATED @22 :Float32; # actual set speed
vCruiseClusterDEPRECATED @63 :Float32; # set speed to display in the UI
}

struct DrivingModelData {
Expand Down
1 change: 0 additions & 1 deletion common/params.cc
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@ std::unordered_map<std::string, uint32_t> keys = {
{"PrimeType", PERSISTENT},
{"RecordFront", PERSISTENT},
{"RecordFrontLock", PERSISTENT}, // for the internal fleet
{"ReplayControlsState", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
{"RouteCount", PERSISTENT},
{"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"SshEnabled", PERSISTENT},
Expand Down
35 changes: 31 additions & 4 deletions selfdrive/car/card.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import os
import time
import threading

import cereal.messaging as messaging

Expand All @@ -18,9 +19,10 @@
from opendbc.car.car_helpers import get_car
from opendbc.car.interfaces import CarInterfaceBase
from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp
from openpilot.selfdrive.car.cruise import VCruiseHelper
from openpilot.selfdrive.car.car_specific import CarSpecificEvents, MockCarState
from openpilot.selfdrive.car.helpers import convert_carControl, convert_to_capnp
from openpilot.selfdrive.controls.lib.events import Events
from openpilot.selfdrive.controls.lib.events import Events, ET

REPLAY = "REPLAY" in os.environ

Expand Down Expand Up @@ -139,6 +141,10 @@ def __init__(self, CI=None) -> None:

self.car_events = CarSpecificEvents(self.CP)
self.mock_carstate = MockCarState()
self.v_cruise_helper = VCruiseHelper(self.CP)

self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode")

# card is driven by can recv, expected at 100Hz
self.rk = Ratekeeper(100, print_delay_threshold=None)
Expand All @@ -164,6 +170,11 @@ def state_update(self) -> car.CarState:
if can_rcv_valid and REPLAY:
self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime

# TODO: mirror the carState.cruiseState struct?
self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric)
CS.vCruise = float(self.v_cruise_helper.v_cruise_kph)
CS.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph)

return CS

def update_events(self, CS: car.CarState):
Expand Down Expand Up @@ -234,6 +245,9 @@ def step(self):

self.update_events(CS)

if not self.sm['carControl'].enabled and self.events.contains(ET.ENABLE):
self.v_cruise_helper.initialize_v_cruise(CS, self.experimental_mode)

self.state_publish(CS)

initialized = (not any(e.name == EventName.controlsInitializing for e in self.sm['onroadEvents']) and
Expand All @@ -244,10 +258,23 @@ def step(self):
self.initialized_prev = initialized
self.CS_prev = CS.as_reader()

def params_thread(self, evt):
while not evt.is_set():
self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
time.sleep(0.1)

def card_thread(self):
while True:
self.step()
self.rk.monitor_time()
e = threading.Event()
t = threading.Thread(target=self.params_thread, args=(e, ))
try:
t.start()
while True:
self.step()
self.rk.monitor_time()
finally:
e.set()
t.join()


def main():
Expand Down
132 changes: 132 additions & 0 deletions selfdrive/car/cruise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import math

from cereal import car
from openpilot.common.conversions import Conversions as CV
from openpilot.common.numpy_fast import clip


# WARNING: this value was determined based on the model's training distribution,
# model predictions above this speed can be unpredictable
# V_CRUISE's are in kph
V_CRUISE_MIN = 8
V_CRUISE_MAX = 145
V_CRUISE_UNSET = 255
V_CRUISE_INITIAL = 40
V_CRUISE_INITIAL_EXPERIMENTAL_MODE = 105
IMPERIAL_INCREMENT = round(CV.MPH_TO_KPH, 1) # round here to avoid rounding errors incrementing set speed

ButtonEvent = car.CarState.ButtonEvent
ButtonType = car.CarState.ButtonEvent.Type
CRUISE_LONG_PRESS = 50
CRUISE_NEAREST_FUNC = {
ButtonType.accelCruise: math.ceil,
ButtonType.decelCruise: math.floor,
}
CRUISE_INTERVAL_SIGN = {
ButtonType.accelCruise: +1,
ButtonType.decelCruise: -1,
}


class VCruiseHelper:
def __init__(self, CP):
self.CP = CP
self.v_cruise_kph = V_CRUISE_UNSET
self.v_cruise_cluster_kph = V_CRUISE_UNSET
self.v_cruise_kph_last = 0
self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0}
self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers}

@property
def v_cruise_initialized(self):
return self.v_cruise_kph != V_CRUISE_UNSET

def update_v_cruise(self, CS, enabled, is_metric):
self.v_cruise_kph_last = self.v_cruise_kph

if CS.cruiseState.available:
if not self.CP.pcmCruise:
# if stock cruise is completely disabled, then we can use our own set speed logic
self._update_v_cruise_non_pcm(CS, enabled, is_metric)
self.v_cruise_cluster_kph = self.v_cruise_kph
self.update_button_timers(CS, enabled)
else:
self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH
self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH
else:
self.v_cruise_kph = V_CRUISE_UNSET
self.v_cruise_cluster_kph = V_CRUISE_UNSET

def _update_v_cruise_non_pcm(self, CS, enabled, is_metric):
# handle button presses. TODO: this should be in state_control, but a decelCruise press
# would have the effect of both enabling and changing speed is checked after the state transition
if not enabled:
return

long_press = False
button_type = None

v_cruise_delta = 1. if is_metric else IMPERIAL_INCREMENT

for b in CS.buttonEvents:
if b.type.raw in self.button_timers and not b.pressed:
if self.button_timers[b.type.raw] > CRUISE_LONG_PRESS:
return # end long press
button_type = b.type.raw
break
else:
for k, timer in self.button_timers.items():
if timer and timer % CRUISE_LONG_PRESS == 0:
button_type = k
long_press = True
break

if button_type is None:
return

# Don't adjust speed when pressing resume to exit standstill
cruise_standstill = self.button_change_states[button_type]["standstill"] or CS.cruiseState.standstill
if button_type == ButtonType.accelCruise and cruise_standstill:
return

# Don't adjust speed if we've enabled since the button was depressed (some ports enable on rising edge)
if not self.button_change_states[button_type]["enabled"]:
return

v_cruise_delta = v_cruise_delta * (5 if long_press else 1)
if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
else:
self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type]

# If set is pressed while overriding, clip cruise speed to minimum of vEgo
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH)

self.v_cruise_kph = clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)

def update_button_timers(self, CS, enabled):
# increment timer for buttons still pressed
for k in self.button_timers:
if self.button_timers[k] > 0:
self.button_timers[k] += 1

for b in CS.buttonEvents:
if b.type.raw in self.button_timers:
# Start/end timer and store current state on change of button pressed
self.button_timers[b.type.raw] = 1 if b.pressed else 0
self.button_change_states[b.type.raw] = {"standstill": CS.cruiseState.standstill, "enabled": enabled}

def initialize_v_cruise(self, CS, experimental_mode: bool) -> None:
# initializing is handled by the PCM
if self.CP.pcmCruise:
return

initial = V_CRUISE_INITIAL_EXPERIMENTAL_MODE if experimental_mode else V_CRUISE_INITIAL

if any(b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for b in CS.buttonEvents) and self.v_cruise_initialized:
self.v_cruise_kph = self.v_cruise_kph_last
else:
self.v_cruise_kph = int(round(clip(CS.vEgo * CV.MS_TO_KPH, initial, V_CRUISE_MAX)))

self.v_cruise_cluster_kph = self.v_cruise_kph
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from parameterized import parameterized_class
from cereal import log
from openpilot.selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT
from openpilot.selfdrive.car.cruise import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT
from cereal import car
from openpilot.common.conversions import Conversions as CV
from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
Expand Down
22 changes: 5 additions & 17 deletions selfdrive/controls/controlsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from opendbc.car.car_helpers import get_car_interface
from openpilot.selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert
from openpilot.selfdrive.controls.lib.drive_helpers import VCruiseHelper, clip_curvature, get_startup_event
from openpilot.selfdrive.controls.lib.drive_helpers import clip_curvature, get_startup_event
from openpilot.selfdrive.controls.lib.events import Events, ET
from openpilot.selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED
from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID
Expand Down Expand Up @@ -157,7 +157,6 @@ def __init__(self, CI=None):
self.desired_curvature = 0.0
self.experimental_mode = False
self.personality = self.read_personality_param()
self.v_cruise_helper = VCruiseHelper(self.CP)
self.recalibrating_seen = False

self.can_log_mono_time = 0
Expand All @@ -179,13 +178,7 @@ def __init__(self, CI=None):
self.rk = Ratekeeper(100, print_delay_threshold=None)

def set_initial_state(self):
if REPLAY:
controls_state = self.params.get("ReplayControlsState")
if controls_state is not None:
with log.ControlsState.from_bytes(controls_state) as controls_state:
self.v_cruise_helper.v_cruise_kph = controls_state.vCruise

if any(ps.controlsAllowed for ps in self.sm['pandaStates']):
if REPLAY and any(ps.controlsAllowed for ps in self.sm['pandaStates']):
self.state = State.enabled

def update_events(self, CS):
Expand Down Expand Up @@ -214,7 +207,7 @@ def update_events(self, CS):

# Block resume if cruise never previously enabled
resume_pressed = any(be.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be in CS.buttonEvents)
if not self.CP.pcmCruise and not self.v_cruise_helper.v_cruise_initialized and resume_pressed:
if not self.CP.pcmCruise and CS.vCruise > 250 and resume_pressed:
self.events.add(EventName.resumeBlocked)

if not self.CP.notCar:
Expand Down Expand Up @@ -447,8 +440,6 @@ def data_sample(self):
def state_transition(self, CS):
"""Compute conditional state transitions and execute actions on state transitions"""

self.v_cruise_helper.update_v_cruise(CS, self.enabled, self.is_metric)

# decrement the soft disable timer at every step, as it's reset on
# entrance in SOFT_DISABLING state
self.soft_disable_timer = max(0, self.soft_disable_timer - 1)
Expand Down Expand Up @@ -522,7 +513,6 @@ def state_transition(self, CS):
else:
self.state = State.enabled
self.current_alert_types.append(ET.ENABLE)
self.v_cruise_helper.initialize_v_cruise(CS, self.experimental_mode)

# Check if openpilot is engaged and actuators are enabled
self.enabled = self.state in ENABLED_STATES
Expand Down Expand Up @@ -578,7 +568,7 @@ def state_control(self, CS):

if not self.joystick_mode:
# accel PID loop
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, self.v_cruise_helper.v_cruise_kph * CV.KPH_TO_MS)
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS)
actuators.accel = self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits)

# Steering PID loop and lateral MPC
Expand Down Expand Up @@ -678,7 +668,7 @@ def publish_logs(self, CS, start_time, CC, lac_log):
CC.cruiseControl.resume = self.enabled and CS.cruiseState.standstill and speeds[-1] > 0.1

hudControl = CC.hudControl
hudControl.setSpeed = float(self.v_cruise_helper.v_cruise_cluster_kph * CV.KPH_TO_MS)
hudControl.setSpeed = float(CS.vCruiseCluster * CV.KPH_TO_MS)
hudControl.speedVisible = self.enabled
hudControl.lanesVisible = self.enabled
hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead
Expand Down Expand Up @@ -760,8 +750,6 @@ def publish_logs(self, CS, start_time, CC, lac_log):
controlsState.state = self.state
controlsState.engageable = not self.events.contains(ET.NO_ENTRY)
controlsState.longControlState = self.LoC.long_control_state
controlsState.vCruise = float(self.v_cruise_helper.v_cruise_kph)
controlsState.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph)
controlsState.upAccelCmd = float(self.LoC.pid.p)
controlsState.uiAccelCmd = float(self.LoC.pid.i)
controlsState.ufAccelCmd = float(self.LoC.pid.f)
Expand Down
Loading

0 comments on commit 2f3256e

Please sign in to comment.