Skip to content

Commit

Permalink
dec v 2.0 (commaai#434)
Browse files Browse the repository at this point in the history
* dec update

* date
  • Loading branch information
rav4kumar authored Sep 29, 2024
1 parent 9feffca commit 674670d
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 57 deletions.
9 changes: 8 additions & 1 deletion cereal/custom.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ enum ModelGeneration {
five @5;
}

enum MpcSource {
acc @0;
blended @1;
}

struct ControlsStateSP @0x81c2f05a394cf4af {
lateralState @0 :Text;
personality @8 :LongitudinalPersonalitySP;
Expand Down Expand Up @@ -92,8 +97,10 @@ struct LongitudinalPlanSP @0xaedffd8f31e7b55d {
desiredTF @13 :Float32;
notSpeedLimit @14 :Int16;
e2eX @15 :List(Float32);
e2eBlended @18 :Text;
e2eBlendedDEPRECATED @18 :Text;
e2eStatus @22 :Bool;
mpcSource @23 :MpcSource;
dynamicExperimentalControl @24 :Bool;

distToTurn @7 :Float32;
turnSpeed @8 :Float32;
Expand Down
9 changes: 7 additions & 2 deletions selfdrive/controls/lib/longitudinal_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
_A_TOTAL_MAX_BP = [20., 40.]


MpcSource = custom.MpcSource
EventName = car.CarEvent.EventName


Expand Down Expand Up @@ -131,10 +132,13 @@ def update(self, sm):
self.read_param()
self.param_read_counter += 1
if self.dynamic_experimental_controller.is_enabled() and sm['controlsState'].experimentalMode:
self.mpc.mode = self.dynamic_experimental_controller.get_mpc_mode(self.CP.radarUnavailable, sm['carState'], sm['radarState'].leadOne, sm['modelV2'], sm['controlsState'], sm['navInstruction'].maneuverDistance)
self.dynamic_experimental_controller.set_mpc_fcw_crash_cnt(self.mpc.crash_cnt)
self.dynamic_experimental_controller.update(self.CP.radarUnavailable, sm['carState'], sm['radarState'].leadOne, sm['modelV2'], sm['controlsState'], sm['navInstruction'].maneuverDistance)
self.mpc.mode = self.dynamic_experimental_controller.get_mpc_mode()
else:
self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc'


v_ego = sm['carState'].vEgo
v_cruise_kph = min(sm['controlsState'].vCruise, V_CRUISE_MAX)
v_cruise = v_cruise_kph * CV.KPH_TO_MS
Expand Down Expand Up @@ -267,7 +271,8 @@ def publish(self, sm, pm):
longitudinalPlanSP.turnSpeedControlState = self.turn_speed_controller.state
longitudinalPlanSP.turnSpeed = float(self.turn_speed_controller.v_target)

longitudinalPlanSP.e2eBlended = self.mpc.mode
longitudinalPlanSP.mpcSource = MpcSource.blended if self.mpc.mode == 'blended' else MpcSource.acc
longitudinalPlanSP.dynamicExperimentalControl = self.dynamic_experimental_controller.is_enabled()

pm.send('longitudinalPlanSP', plan_sp_send)

Expand Down
177 changes: 128 additions & 49 deletions selfdrive/controls/lib/sunnypilot/dynamic_experimental_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,39 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Version = 2024-1-29
# Version = 2024-9-28
from common.numpy_fast import interp
from openpilot.selfdrive.controls.lib.lateral_planner import TRAJECTORY_SIZE
import numpy as np

LEAD_WINDOW_SIZE = 5
# d-e2e, from modeldata.h
TRAJECTORY_SIZE = 33

LEAD_WINDOW_SIZE = 4
LEAD_PROB = 0.6

SLOW_DOWN_WINDOW_SIZE = 5
SLOW_DOWN_WINDOW_SIZE = 4
SLOW_DOWN_PROB = 0.6

SLOW_DOWN_BP = [0., 10., 20., 30., 40., 50., 55., 60.]
SLOW_DOWN_DIST = [20, 30., 50., 70., 80., 90., 105., 120.]
SLOW_DOWN_DIST = [25., 38., 55., 75., 95., 115., 130., 150.]

SLOWNESS_WINDOW_SIZE = 20
SLOWNESS_PROB = 0.6
SLOWNESS_WINDOW_SIZE = 12
SLOWNESS_PROB = 0.5
SLOWNESS_CRUISE_OFFSET = 1.05

DANGEROUS_TTC_WINDOW_SIZE = 5
DANGEROUS_TTC = 2.0
DANGEROUS_TTC_WINDOW_SIZE = 3
DANGEROUS_TTC = 2.3

HIGHWAY_CRUISE_KPH = 75
HIGHWAY_CRUISE_KPH = 70

STOP_AND_GO_FRAME = 60

SET_MODE_TIMEOUT = 10

MPC_FCW_WINDOW_SIZE = 5
MPC_FCW_PROB = 0.6
MPC_FCW_WINDOW_SIZE = 10
MPC_FCW_PROB = 0.5

V_ACC_MIN = 9.72


class SNG_State:
Expand Down Expand Up @@ -77,29 +83,51 @@ def reset_data(self):
self.data = []
self.total = 0

class WeightedMovingAverageCalculator:
def __init__(self, window_size):
self.window_size = window_size
self.data = []
self.weights = np.linspace(1, 3, window_size) # Linear weights, adjust as needed

def add_data(self, value):
if len(self.data) == self.window_size:
self.data.pop(0)
self.data.append(value)

def get_weighted_average(self):
if len(self.data) == 0:
return None
weighted_sum = np.dot(self.data, self.weights[-len(self.data):])
weight_total = np.sum(self.weights[-len(self.data):])
return weighted_sum / weight_total

def reset_data(self):
self.data = []

class DynamicExperimentalController:
def __init__(self):
self._is_enabled = False
self._mode = 'acc'
self._mode_prev = 'acc'
self._mode_changed = False
self._frame = 0

self._lead_gmac = GenericMovingAverageCalculator(window_size=LEAD_WINDOW_SIZE)
# Use weighted moving average for filtering leads
self._lead_gmac = WeightedMovingAverageCalculator(window_size=LEAD_WINDOW_SIZE)
self._has_lead_filtered = False
self._has_lead_filtered_prev = False

self._slow_down_gmac = GenericMovingAverageCalculator(window_size=SLOW_DOWN_WINDOW_SIZE)
self._slow_down_gmac = WeightedMovingAverageCalculator(window_size=SLOW_DOWN_WINDOW_SIZE)
self._has_slow_down = False

self._has_blinkers = False

self._slowness_gmac = GenericMovingAverageCalculator(window_size=SLOWNESS_WINDOW_SIZE)
self._slowness_gmac = WeightedMovingAverageCalculator(window_size=SLOWNESS_WINDOW_SIZE)
self._has_slowness = False

self._has_nav_instruction = False

self._dangerous_ttc_gmac = GenericMovingAverageCalculator(window_size=DANGEROUS_TTC_WINDOW_SIZE)
self._dangerous_ttc_gmac = WeightedMovingAverageCalculator(window_size=DANGEROUS_TTC_WINDOW_SIZE)
self._has_dangerous_ttc = False

self._v_ego_kph = 0.
Expand All @@ -113,13 +141,50 @@ def __init__(self):
self._sng_transit_frame = 0
self._sng_state = SNG_State.off

self._mpc_fcw_gmac = GenericMovingAverageCalculator(window_size=MPC_FCW_WINDOW_SIZE)
self._mpc_fcw_gmac = WeightedMovingAverageCalculator(window_size=MPC_FCW_WINDOW_SIZE)
self._has_mpc_fcw = False
self._mpc_fcw_crash_cnt = 0

self._set_mode_timeout = 0
pass


def _adaptive_slowdown_threshold(self):
"""
Adapts the slow down threshold based on vehicle speed and recent behavior.
"""
return interp(self._v_ego_kph, SLOW_DOWN_BP, SLOW_DOWN_DIST) * (1.0 + 0.03 * np.log(1 + len(self._slow_down_gmac.data)))

def _anomaly_detection(self, recent_data, threshold=2.0, context_check=True):
"""
Basic anomaly detection using standard deviation.
"""
if len(recent_data) < 5:
return False
mean = np.mean(recent_data)
std_dev = np.std(recent_data)
anomaly = recent_data[-1] > mean + threshold * std_dev

# Context check to ensure repeated anomaly
if context_check:
return np.count_nonzero(np.array(recent_data) > mean + threshold * std_dev) > 1
return anomaly

def _smoothed_lead_detection(self, lead_prob, smoothing_factor=0.2):
"""
Smoothing the lead detection to avoid erratic behavior.
"""
self._has_lead_filtered = (1 - smoothing_factor) * self._has_lead_filtered + smoothing_factor * lead_prob
return self._has_lead_filtered > LEAD_PROB

def _adaptive_lead_prob_threshold(self):
"""
Adapts lead probability threshold based on driving conditions.
"""
if self._v_ego_kph > HIGHWAY_CRUISE_KPH:
return LEAD_PROB + 0.1 # Increase the threshold on highways
return LEAD_PROB

def _update(self, car_state, lead_one, md, controls_state, maneuver_distance):
self._v_ego_kph = car_state.vEgo * 3.6
self._v_cruise_kph = controls_state.vCruise
Expand All @@ -128,18 +193,27 @@ def _update(self, car_state, lead_one, md, controls_state, maneuver_distance):

# fcw detection
self._mpc_fcw_gmac.add_data(self._mpc_fcw_crash_cnt > 0)
self._has_mpc_fcw = self._mpc_fcw_gmac.get_moving_average() >= MPC_FCW_PROB
self._has_mpc_fcw = self._mpc_fcw_gmac.get_weighted_average() > MPC_FCW_PROB

# nav enable detection
self._has_nav_instruction = md.navEnabledDEPRECATED and maneuver_distance / max(car_state.vEgo, 1) < 13

# lead detection
# lead detection with smoothing
self._lead_gmac.add_data(lead_one.status)
self._has_lead_filtered = self._lead_gmac.get_moving_average() >= LEAD_PROB
self._has_lead_filtered = self._lead_gmac.get_weighted_average() > LEAD_PROB
#lead_prob = self._lead_gmac.get_weighted_average() or 0
#self._has_lead_filtered = self._smoothed_lead_detection(lead_prob)

# slow down detection
self._slow_down_gmac.add_data(len(md.orientation.x) == len(md.position.x) == TRAJECTORY_SIZE and md.position.x[TRAJECTORY_SIZE - 1] < interp(self._v_ego_kph, SLOW_DOWN_BP, SLOW_DOWN_DIST))
self._has_slow_down = self._slow_down_gmac.get_moving_average() >= SLOW_DOWN_PROB
# adaptive slow down detection
adaptive_threshold = self._adaptive_slowdown_threshold()
slow_down_trigger = len(md.orientation.x) == len(md.position.x) == TRAJECTORY_SIZE and md.position.x[TRAJECTORY_SIZE - 1] < adaptive_threshold
self._slow_down_gmac.add_data(slow_down_trigger)
self._has_slow_down = self._slow_down_gmac.get_weighted_average() > SLOW_DOWN_PROB

# anomaly detection for slow down events
if self._anomaly_detection(self._slow_down_gmac.data):
# Handle anomaly: potentially log it, adjust behavior, or issue a warning
self._has_slow_down = False # Reset slow down if anomaly detected

# blinker detection
self._has_blinkers = car_state.leftBlinker or car_state.rightBlinker
Expand All @@ -161,7 +235,7 @@ def _update(self, car_state, lead_one, md, controls_state, maneuver_distance):
# slowness detection
if not self._has_standstill:
self._slowness_gmac.add_data(self._v_ego_kph <= (self._v_cruise_kph*SLOWNESS_CRUISE_OFFSET))
self._has_slowness = self._slowness_gmac.get_moving_average() >= SLOWNESS_PROB
self._has_slowness = self._slowness_gmac.get_weighted_average() > SLOWNESS_PROB

# dangerous TTC detection
if not self._has_lead_filtered and self._has_lead_filtered_prev:
Expand All @@ -171,14 +245,14 @@ def _update(self, car_state, lead_one, md, controls_state, maneuver_distance):
if self._has_lead and car_state.vEgo >= 0.01:
self._dangerous_ttc_gmac.add_data(lead_one.dRel/car_state.vEgo)

self._has_dangerous_ttc = self._dangerous_ttc_gmac.get_moving_average() is not None and self._dangerous_ttc_gmac.get_moving_average() <= DANGEROUS_TTC
self._has_dangerous_ttc = self._dangerous_ttc_gmac.get_weighted_average() is not None and self._dangerous_ttc_gmac.get_weighted_average() <= DANGEROUS_TTC

# keep prev values
self._has_standstill_prev = self._has_standstill
self._has_lead_filtered_prev = self._has_lead_filtered
self._frame += 1

def _blended_priority_mode(self):
def _radarless_mode(self):
# when mpc fcw crash prob is high
# use blended to slow down quickly
if self._has_mpc_fcw:
Expand All @@ -190,21 +264,21 @@ def _blended_priority_mode(self):
self._set_mode('blended')
return

# when blinker is on and speed is driving below highway cruise speed: blended
# when blinker is on and speed is driving below V_ACC_MIN: blended
# we dont want it to switch mode at higher speed, blended may trigger hard brake
if self._has_blinkers and self._v_ego_kph < HIGHWAY_CRUISE_KPH:
self._set_mode('blended')
return
#if self._has_blinkers and self._v_ego_kph < V_ACC_MIN:
# self._set_mode('blended')
# return

# when at highway cruise and SNG: blended
# ensuring blended mode is used because acc is bad at catching SNG lead car
# especially those who accel very fast and then brake very hard.
if self._sng_state == SNG_State.going and self._v_cruise_kph >= HIGHWAY_CRUISE_KPH:
self._set_mode('blended')
return
#if self._sng_state == SNG_State.going and self._v_cruise_kph >= V_ACC_MIN:
# self._set_mode('blended')
# return

# when standstill: blended
# in case of lead car suddenly move away under traffic light, acc mode wont brake at traffic light.
# in case of lead car suddenly move away under traffic light, acc mode won't brake at traffic light.
if self._has_standstill:
self._set_mode('blended')
return
Expand All @@ -226,28 +300,28 @@ def _blended_priority_mode(self):
self._set_mode('acc')
return

self._set_mode('blended')
self._set_mode('acc')

def _acc_priority_mode(self):
def _radar_mode(self):
# when mpc fcw crash prob is high
# use blended to slow down quickly
if self._has_mpc_fcw:
self._set_mode('blended')
return

# If there is a filtered lead, the vehicle is not in standstill, and the lead vehicle's yRel meets the condition,
#if self._has_lead_filtered and not self._has_standstill:
# self._set_mode('acc')
# return
if self._has_lead_filtered and not self._has_standstill:
self._set_mode('acc')
return

# when blinker is on and speed is driving below highway cruise speed: blended
# when blinker is on and speed is driving below V_ACC_MIN: blended
# we dont want it to switch mode at higher speed, blended may trigger hard brake
if self._has_blinkers and self._v_ego_kph < HIGHWAY_CRUISE_KPH:
self._set_mode('blended')
return
#if self._has_blinkers and self._v_ego_kph < V_ACC_MIN:
# self._set_mode('blended')
# return

# when standstill: blended
# in case of lead car suddenly move away under traffic light, acc mode wont brake at traffic light.
# in case of lead car suddenly move away under traffic light, acc mode won't brake at traffic light.
if self._has_standstill:
self._set_mode('blended')
return
Expand All @@ -270,17 +344,22 @@ def _acc_priority_mode(self):

self._set_mode('acc')

def get_mpc_mode(self, radar_unavailable, car_state, lead_one, md, controls_state, maneuver_distance):
def update(self, radar_unavailable, car_state, lead_one, md, controls_state, maneuver_distance):
if self._is_enabled:
self._update(car_state, lead_one, md, controls_state, maneuver_distance)
if radar_unavailable:
self._blended_priority_mode()
self._radarless_mode()
else:
self._acc_priority_mode()

self._radar_mode()
self._mode_changed = self._mode != self._mode_prev
self._mode_prev = self._mode

def get_mpc_mode(self):
return self._mode

def has_changed(self):
return self._mode_changed

def set_enabled(self, enabled):
self._is_enabled = enabled

Expand All @@ -297,4 +376,4 @@ def _set_mode(self, mode):
self._set_mode_timeout = SET_MODE_TIMEOUT

if self._set_mode_timeout > 0:
self._set_mode_timeout -= 1
self._set_mode_timeout -= 1
Loading

0 comments on commit 674670d

Please sign in to comment.