Skip to content

Commit

Permalink
📏 Enable Toyota Distance Button to Change Distance Lines
Browse files Browse the repository at this point in the history
__Additions__

* Forward distance button presses to car can
* Read distance_lines can message from car and use to set distance
profile.
* Alter T_Follow based on distance profile
  * Stock = 1.45
  * Relaxed = 1.25
  * Traffic ~= 1.05 (at low speeds closer to 1.25)
* Adjust J_Cost, A_Change_Cost, and D_Cost based on distance profile
  * Weights for Costs need to be adjusted when T_Follow is decreased.

__Tests__
* Add Longitudinal Tests for Additional Following Profiles
  * Copy the comma tests, with appropriate following distance for
each profile.
* Tweak Process_Replay Action - Ignores the new carState.distanceLines
message, so that replay refs still pass.

Thanks to sshane for help with this. See the tree ending with
commit 73237de
  • Loading branch information
krkeegan committed Feb 23, 2022
1 parent 702658e commit fce1586
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 19 deletions.
3 changes: 3 additions & 0 deletions cereal/car.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ struct CarState {
leftBlindspot @33 :Bool; # Is there something blocking the left lane change
rightBlindspot @34 :Bool; # Is there something blocking the right lane change

# KRKeegan toyota distance lines
distanceLines @39 :UInt8;

struct WheelSpeeds {
# optional wheel speeds
fl @0 :Float32;
Expand Down
4 changes: 2 additions & 2 deletions selfdrive/car/toyota/carcontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, hud_aler
if pcm_cancel_cmd and CS.CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC):
can_sends.append(create_acc_cancel_command(self.packer))
elif CS.CP.openpilotLongitudinalControl:
can_sends.append(create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type))
can_sends.append(create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type, CS.distance_btn))
self.accel = pcm_accel_cmd
else:
can_sends.append(create_accel_command(self.packer, 0, pcm_cancel_cmd, False, lead, CS.acc_type))
can_sends.append(create_accel_command(self.packer, 0, pcm_cancel_cmd, False, lead, CS.acc_type, CS.distance_btn))

if frame % 2 == 0 and CS.CP.enableGasInterceptor and CS.CP.openpilotLongitudinalControl:
# send exactly zero if gas cmd is zero. Interceptor will send the max between read value and gas cmd.
Expand Down
15 changes: 15 additions & 0 deletions selfdrive/car/toyota/carstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ def __init__(self, CP):
self.low_speed_lockout = False
self.acc_type = 1

# KRKeegan - Add support for toyota distance button
self.distance_btn = 0

def update(self, cp, cp_cam):
ret = car.CarState.new_message()

Expand Down Expand Up @@ -94,6 +97,10 @@ def update(self, cp, cp_cam):
if self.CP.carFingerprint in TSS2_CAR:
self.acc_type = cp_cam.vl["ACC_CONTROL"]["ACC_TYPE"]

# KRKeegan - Add support for toyota distance button
self.distance_btn = 1 if cp_cam.vl["ACC_CONTROL"]["DISTANCE"] == 1 else 0
ret.distanceLines = cp.vl["PCM_CRUISE_SM"]["DISTANCE_LINES"]

# some TSS2 cars have low speed lockout permanently set, so ignore on those cars
# these cars are identified by an ACC_TYPE value of 2.
# TODO: it is possible to avoid the lockout and gain stop and go if you
Expand Down Expand Up @@ -202,6 +209,11 @@ def get_can_parser(CP):
]
checks.append(("BSM", 1))

# KRKeegan - Add support for toyota distance button
if CP.carFingerprint in TSS2_CAR:
signals.append(("DISTANCE_LINES", "PCM_CRUISE_SM", 0))
checks.append(("PCM_CRUISE_SM", 1))

return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 0)

@staticmethod
Expand All @@ -221,4 +233,7 @@ def get_cam_can_parser(CP):
signals.append(("ACC_TYPE", "ACC_CONTROL"))
checks.append(("ACC_CONTROL", 33))

# KRKeegan - Add support for toyota distance button
signals.append(("DISTANCE", "ACC_CONTROL", 0))

return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2)
4 changes: 2 additions & 2 deletions selfdrive/car/toyota/toyotacan.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ def create_lta_steer_command(packer, steer, steer_req, raw_cnt):
return packer.make_can_msg("STEERING_LTA", 0, values)


def create_accel_command(packer, accel, pcm_cancel, standstill_req, lead, acc_type):
def create_accel_command(packer, accel, pcm_cancel, standstill_req, lead, acc_type, distance):
# TODO: find the exact canceling bit that does not create a chime
values = {
"ACCEL_CMD": accel,
"ACC_TYPE": acc_type,
"DISTANCE": 0,
"DISTANCE": distance,
"MINI_CAR": lead,
"PERMIT_BRAKING": 1,
"RELEASE_STANDSTILL": not standstill_req,
Expand Down
60 changes: 45 additions & 15 deletions selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

X_DIM = 3
U_DIM = 1
PARAM_DIM= 4
PARAM_DIM= 5
COST_E_DIM = 5
COST_DIM = COST_E_DIM + 1
CONSTR_DIM = 4
Expand Down Expand Up @@ -53,14 +53,14 @@
COMFORT_BRAKE = 2.5
STOP_DISTANCE = 6.0

def get_stopped_equivalence_factor(v_lead):
def get_stopped_equivalence_factor(v_lead, t_follow=T_FOLLOW):
return (v_lead**2) / (2 * COMFORT_BRAKE)

def get_safe_obstacle_distance(v_ego):
return (v_ego**2) / (2 * COMFORT_BRAKE) + T_FOLLOW * v_ego + STOP_DISTANCE
def get_safe_obstacle_distance(v_ego, t_follow=T_FOLLOW):
return (v_ego**2) / (2 * COMFORT_BRAKE) + t_follow * v_ego + STOP_DISTANCE

def desired_follow_distance(v_ego, v_lead):
return get_safe_obstacle_distance(v_ego) - get_stopped_equivalence_factor(v_lead)
def desired_follow_distance(v_ego, v_lead, t_follow=T_FOLLOW):
return get_safe_obstacle_distance(v_ego, t_follow) - get_stopped_equivalence_factor(v_lead, t_follow)


def gen_long_model():
Expand Down Expand Up @@ -88,7 +88,8 @@ def gen_long_model():
a_max = SX.sym('a_max')
x_obstacle = SX.sym('x_obstacle')
prev_a = SX.sym('prev_a')
model.p = vertcat(a_min, a_max, x_obstacle, prev_a)
desired_TF = SX.sym('desired_TF')
model.p = vertcat(a_min, a_max, x_obstacle, prev_a, desired_TF)

# dynamics model
f_expl = vertcat(v_ego, a_ego, j_ego)
Expand Down Expand Up @@ -122,11 +123,12 @@ def gen_long_mpc_solver():
a_min, a_max = ocp.model.p[0], ocp.model.p[1]
x_obstacle = ocp.model.p[2]
prev_a = ocp.model.p[3]
desired_TF = ocp.model.p[4]

ocp.cost.yref = np.zeros((COST_DIM, ))
ocp.cost.yref_e = np.zeros((COST_E_DIM, ))

desired_dist_comfort = get_safe_obstacle_distance(v_ego)
desired_dist_comfort = get_safe_obstacle_distance(v_ego, desired_TF)

# The main cost in normal operation is how close you are to the "desired" distance
# from an obstacle at every timestep. This obstacle can be a lead car
Expand All @@ -153,7 +155,7 @@ def gen_long_mpc_solver():

x0 = np.zeros(X_DIM)
ocp.constraints.x0 = x0
ocp.parameter_values = np.array([-1.2, 1.2, 0.0, 0.0])
ocp.parameter_values = np.array([-1.2, 1.2, 0.0, 0.0, T_FOLLOW])

# We put all constraint cost weights to 0 and only set them at runtime
cost_weights = np.zeros(CONSTR_DIM)
Expand Down Expand Up @@ -193,6 +195,7 @@ def gen_long_mpc_solver():
class LongitudinalMpc:
def __init__(self, e2e=False):
self.e2e = e2e
self.desired_TF = T_FOLLOW
self.reset()
self.source = SOURCES[2]

Expand Down Expand Up @@ -228,18 +231,30 @@ def set_weights(self):
else:
self.set_weights_for_lead_policy()

def get_cost_multipliers(self):
TFs = [1.0, 1.25, T_FOLLOW]
# KRKeegan adjustments to costs for different TFs
# these were calculated using the test_longitudial.py deceleration tests
a_change_tf = interp(self.desired_TF, TFs, [.1, .8, 1.])
j_ego_tf = interp(self.desired_TF, TFs, [.6, .8, 1.])
d_zone_tf = interp(self.desired_TF, TFs, [1.6, 1.3, 1.])
return (a_change_tf, j_ego_tf, d_zone_tf)

def set_weights_for_lead_policy(self):
W = np.asfortranarray(np.diag([X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, A_CHANGE_COST, J_EGO_COST]))
cost_mulitpliers = self.get_cost_multipliers()
W = np.asfortranarray(np.diag([X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST,
A_EGO_COST, A_CHANGE_COST * cost_mulitpliers[0],
J_EGO_COST * cost_mulitpliers[1]]))
for i in range(N):
# KRKeegan, decreased timescale to .5s since Toyota lag is set to .3s
W[4,4] = A_CHANGE_COST * np.interp(T_IDXS[i], [0.0, 0.5, 2.0], [1.0, 1.0, 0.0])
W[4,4] = A_CHANGE_COST * cost_mulitpliers[0] * np.interp(T_IDXS[i], [0.0, 0.5, 2.0], [1.0, 1.0, 0.0])
self.solver.cost_set(i, 'W', W)
# Setting the slice without the copy make the array not contiguous,
# causing issues with the C interface.
self.solver.cost_set(N, 'W', np.copy(W[:COST_E_DIM, :COST_E_DIM]))

# Set L2 slack cost on lower bound constraints
Zl = np.array([LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST])
Zl = np.array([LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST * cost_mulitpliers[2]])
for i in range(N):
self.solver.cost_set(i, 'Zl', Zl)

Expand Down Expand Up @@ -301,7 +316,21 @@ def set_accel_limits(self, min_a, max_a):
self.cruise_min_a = min_a
self.cruise_max_a = max_a

def update_TF(self, carstate):
if carstate.distanceLines == 1: # Traffic
# At slow speeds more time, decrease time up to 60mph
# in mph ~= 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90
x_vel = [0, 2.25, 4.5, 6.75, 9, 11.25, 13.5, 15.75, 18, 20.25, 22.5, 24.75, 27, 29.25, 31.5, 33.75, 36, 38.25, 40.5]
y_dist = [1.25, 1.24, 1.23, 1.22, 1.21, 1.20, 1.18, 1.16, 1.13, 1.11, 1.09, 1.07, 1.05, 1.05, 1.05, 1.05, 1.05, 1.05, 1.05]
self.desired_TF = np.interp(carstate.vEgo, x_vel, y_dist)
elif carstate.distanceLines == 2: # Relaxed
self.desired_TF = 1.25
else:
self.desired_TF = T_FOLLOW

def update(self, carstate, radarstate, v_cruise, prev_accel_constraint=False):
self.update_TF(carstate)
self.set_weights()
v_ego = self.x0[1]
a_ego = self.x0[2]
self.status = radarstate.leadOne.status or radarstate.leadTwo.status
Expand All @@ -316,8 +345,8 @@ def update(self, carstate, radarstate, v_cruise, prev_accel_constraint=False):
# To estimate a safe distance from a moving lead, we calculate how much stopping
# distance that lead needs as a minimum. We can add that to the current distance
# and then treat that as a stopped car/obstacle at this new distance.
lead_0_obstacle = lead_xv_0[:,0] + get_stopped_equivalence_factor(lead_xv_0[:,1])
lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1])
lead_0_obstacle = lead_xv_0[:,0] + get_stopped_equivalence_factor(lead_xv_0[:,1], self.desired_TF)
lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1], self.desired_TF)

# Fake an obstacle for cruise, this ensures smooth acceleration to set speed
# when the leads are no factor.
Expand All @@ -326,7 +355,7 @@ def update(self, carstate, radarstate, v_cruise, prev_accel_constraint=False):
v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),
v_lower,
v_upper)
cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped)
cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, self.desired_TF)

x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle])
self.source = SOURCES[np.argmin(x_obstacles[0])]
Expand All @@ -335,6 +364,7 @@ def update(self, carstate, radarstate, v_cruise, prev_accel_constraint=False):
self.params[:,3] = np.copy(self.prev_a)
else:
self.params[:,3] = a_ego
self.params[:,4] = self.desired_TF

self.run()
if (np.any(lead_xv_0[:,0] - self.x_sol[:,0] < CRASH_DISTANCE) and
Expand Down

0 comments on commit fce1586

Please sign in to comment.