Files
opendbc-meb/opendbc/car/toyota/carcontroller.py
Shane Smiskol 40951c19ee Toyota: lower accel down rate limit (#1489)
lower rate limit
2024-11-14 18:00:14 -08:00

264 lines
13 KiB
Python

import math
from opendbc.car import carlog, apply_meas_steer_torque_limits, apply_std_steer_angle_limits, common_fault_avoidance, \
make_tester_present_msg, rate_limit, structs, ACCELERATION_DUE_TO_GRAVITY, DT_CTRL
from opendbc.car.can_definitions import CanData
from opendbc.car.common.filter_simple import FirstOrderFilter
from opendbc.car.common.numpy_fast import clip
from opendbc.car.secoc import add_mac, build_sync_mac
from opendbc.car.interfaces import CarControllerBase
from opendbc.car.toyota import toyotacan
from opendbc.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_TIMER_CAR, TSS2_CAR, \
CarControllerParams, ToyotaFlags, \
UNSUPPORTED_DSU_CAR
from opendbc.can.packer import CANPacker
LongCtrlState = structs.CarControl.Actuators.LongControlState
SteerControlType = structs.CarParams.SteerControlType
VisualAlert = structs.CarControl.HUDControl.VisualAlert
# The up limit allows the brakes/gas to unwind quickly leaving a stop,
# the down limit roughly matches the rate of ACCEL_NET, reducing PCM compensation windup
ACCEL_WINDUP_LIMIT = 0.5 # m/s^2 / frame
ACCEL_WINDDOWN_LIMIT = -4.0 * DT_CTRL * 3 # m/s^2 / frame
# LKA limits
# EPS faults if you apply torque while the steering rate is above 100 deg/s for too long
MAX_STEER_RATE = 100 # deg/s
MAX_STEER_RATE_FRAMES = 18 # tx control frames needed before torque can be cut
# EPS allows user torque above threshold for 50 frames before permanently faulting
MAX_USER_TORQUE = 500
# LTA limits
# EPS ignores commands above this angle and causes PCS to fault
MAX_LTA_ANGLE = 94.9461 # deg
MAX_LTA_DRIVER_TORQUE_ALLOWANCE = 150 # slightly above steering pressed allows some resistance when changing lanes
class CarController(CarControllerBase):
def __init__(self, dbc_name, CP):
super().__init__(dbc_name, CP)
self.params = CarControllerParams(self.CP)
self.last_steer = 0
self.last_angle = 0
self.alert_active = False
self.last_standstill = False
self.standstill_req = False
self.steer_rate_counter = 0
self.distance_button = 0
self.pcm_accel_compensation = FirstOrderFilter(0, 0.5, DT_CTRL * 3)
self.permit_braking = True
self.packer = CANPacker(dbc_name)
self.accel = 0
self.prev_accel = 0
self.secoc_lka_message_counter = 0
self.secoc_lta_message_counter = 0
self.secoc_prev_reset_counter = 0
self.secoc_mismatch_counter = 0
def update(self, CC, CS, now_nanos):
actuators = CC.actuators
stopping = actuators.longControlState == LongCtrlState.stopping
hud_control = CC.hudControl
pcm_cancel_cmd = CC.cruiseControl.cancel
lat_active = CC.latActive and abs(CS.out.steeringTorque) < MAX_USER_TORQUE
# *** control msgs ***
can_sends = []
# *** handle secoc reset counter increase ***
if self.CP.flags & ToyotaFlags.SECOC.value:
if CS.secoc_synchronization['RESET_CNT'] != self.secoc_prev_reset_counter:
self.secoc_lka_message_counter = 0
self.secoc_lta_message_counter = 0
self.secoc_prev_reset_counter = CS.secoc_synchronization['RESET_CNT']
expected_mac = build_sync_mac(self.secoc_key, int(CS.secoc_synchronization['TRIP_CNT']), int(CS.secoc_synchronization['RESET_CNT']))
if int(CS.secoc_synchronization['AUTHENTICATOR']) != expected_mac and self.secoc_mismatch_counter < 100:
carlog.error("SecOC synchronization MAC mismatch, wrong key?")
self.secoc_mismatch_counter += 1
# *** steer torque ***
new_steer = int(round(actuators.steer * self.params.STEER_MAX))
apply_steer = apply_meas_steer_torque_limits(new_steer, self.last_steer, CS.out.steeringTorqueEps, self.params)
# >100 degree/sec steering fault prevention
self.steer_rate_counter, apply_steer_req = common_fault_avoidance(abs(CS.out.steeringRateDeg) >= MAX_STEER_RATE, lat_active,
self.steer_rate_counter, MAX_STEER_RATE_FRAMES)
if not lat_active:
apply_steer = 0
# *** steer angle ***
if self.CP.steerControlType == SteerControlType.angle:
# If using LTA control, disable LKA and set steering angle command
apply_steer = 0
apply_steer_req = False
if self.frame % 2 == 0:
# EPS uses the torque sensor angle to control with, offset to compensate
apply_angle = actuators.steeringAngleDeg + CS.out.steeringAngleOffsetDeg
# Angular rate limit based on speed
apply_angle = apply_std_steer_angle_limits(apply_angle, self.last_angle, CS.out.vEgoRaw, self.params)
if not lat_active:
apply_angle = CS.out.steeringAngleDeg + CS.out.steeringAngleOffsetDeg
self.last_angle = clip(apply_angle, -MAX_LTA_ANGLE, MAX_LTA_ANGLE)
self.last_steer = apply_steer
# toyota can trace shows STEERING_LKA at 42Hz, with counter adding alternatively 1 and 2;
# sending it at 100Hz seem to allow a higher rate limit, as the rate limit seems imposed
# on consecutive messages
steer_command = toyotacan.create_steer_command(self.packer, apply_steer, apply_steer_req)
if self.CP.flags & ToyotaFlags.SECOC.value:
# TODO: check if this slow and needs to be done by the CANPacker
steer_command = add_mac(self.secoc_key,
int(CS.secoc_synchronization['TRIP_CNT']),
int(CS.secoc_synchronization['RESET_CNT']),
self.secoc_lka_message_counter,
steer_command)
self.secoc_lka_message_counter += 1
can_sends.append(steer_command)
# STEERING_LTA does not seem to allow more rate by sending faster, and may wind up easier
if self.frame % 2 == 0 and self.CP.carFingerprint in TSS2_CAR:
lta_active = lat_active and self.CP.steerControlType == SteerControlType.angle
# cut steering torque with TORQUE_WIND_DOWN when either EPS torque or driver torque is above
# the threshold, to limit max lateral acceleration and for driver torque blending respectively.
full_torque_condition = (abs(CS.out.steeringTorqueEps) < self.params.STEER_MAX and
abs(CS.out.steeringTorque) < MAX_LTA_DRIVER_TORQUE_ALLOWANCE)
# TORQUE_WIND_DOWN at 0 ramps down torque at roughly the max down rate of 1500 units/sec
torque_wind_down = 100 if lta_active and full_torque_condition else 0
can_sends.append(toyotacan.create_lta_steer_command(self.packer, self.CP.steerControlType, self.last_angle,
lta_active, self.frame // 2, torque_wind_down))
if self.CP.flags & ToyotaFlags.SECOC.value:
lta_steer_2 = toyotacan.create_lta_steer_command_2(self.packer, self.frame // 2)
lta_steer_2 = add_mac(self.secoc_key,
int(CS.secoc_synchronization['TRIP_CNT']),
int(CS.secoc_synchronization['RESET_CNT']),
self.secoc_lta_message_counter,
lta_steer_2)
self.secoc_lta_message_counter += 1
can_sends.append(lta_steer_2)
# *** gas and brake ***
# on entering standstill, send standstill request
if CS.out.standstill and not self.last_standstill and (self.CP.carFingerprint not in NO_STOP_TIMER_CAR):
self.standstill_req = True
if CS.pcm_acc_status != 8:
# pcm entered standstill or it's disabled
self.standstill_req = False
self.last_standstill = CS.out.standstill
# handle UI messages
fcw_alert = hud_control.visualAlert == VisualAlert.fcw
steer_alert = hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw)
lead = hud_control.leadVisible or CS.out.vEgo < 12. # at low speed we always assume the lead is present so ACC can be engaged
if self.CP.openpilotLongitudinalControl:
if self.frame % 3 == 0:
# Press distance button until we are at the correct bar length. Only change while enabled to avoid skipping startup popup
if self.frame % 6 == 0 and self.CP.openpilotLongitudinalControl:
desired_distance = 4 - hud_control.leadDistanceBars
if CS.out.cruiseState.enabled and CS.pcm_follow_distance != desired_distance:
self.distance_button = not self.distance_button
else:
self.distance_button = 0
# internal PCM gas command can get stuck unwinding from negative accel so we apply a generous rate limit
pcm_accel_cmd = actuators.accel
if CC.longActive:
pcm_accel_cmd = rate_limit(pcm_accel_cmd, self.prev_accel, ACCEL_WINDDOWN_LIMIT, ACCEL_WINDUP_LIMIT)
# calculate amount of acceleration PCM should apply to reach target, given pitch
accel_due_to_pitch = math.sin(CC.orientationNED[1]) * ACCELERATION_DUE_TO_GRAVITY if len(CC.orientationNED) == 3 else 0.0
net_acceleration_request = pcm_accel_cmd + accel_due_to_pitch
# For cars where we allow a higher max acceleration of 2.0 m/s^2, compensate for PCM request overshoot and imprecise braking
if self.CP.flags & ToyotaFlags.RAISED_ACCEL_LIMIT and CC.longActive and not CS.out.cruiseState.standstill:
# let PCM handle stopping for now
pcm_accel_compensation = 0.0
if not stopping:
pcm_accel_compensation = 2.0 * (CS.pcm_accel_net - net_acceleration_request)
# prevent compensation windup
pcm_accel_compensation = clip(pcm_accel_compensation, pcm_accel_cmd - self.params.ACCEL_MAX,
pcm_accel_cmd - self.params.ACCEL_MIN)
pcm_accel_cmd = pcm_accel_cmd - self.pcm_accel_compensation.update(pcm_accel_compensation)
else:
self.pcm_accel_compensation.x = 0.0
self.permit_braking = True
# Along with rate limiting positive jerk above, this greatly improves gas response time
# Consider the net acceleration request that the PCM should be applying (pitch included)
if net_acceleration_request < 0.1 or stopping or not CC.longActive:
self.permit_braking = True
elif net_acceleration_request > 0.2:
self.permit_braking = False
pcm_accel_cmd = clip(pcm_accel_cmd, self.params.ACCEL_MIN, self.params.ACCEL_MAX)
can_sends.append(toyotacan.create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.permit_braking, self.standstill_req, lead,
CS.acc_type, fcw_alert, self.distance_button))
self.accel = pcm_accel_cmd
self.prev_accel = actuators.accel
else:
# we can spam can to cancel the system even if we are using lat only control
if pcm_cancel_cmd:
if self.CP.carFingerprint in UNSUPPORTED_DSU_CAR:
can_sends.append(toyotacan.create_acc_cancel_command(self.packer))
else:
can_sends.append(toyotacan.create_accel_command(self.packer, 0, pcm_cancel_cmd, True, False, lead, CS.acc_type, False, self.distance_button))
# *** hud ui ***
if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V:
# ui mesg is at 1Hz but we send asap if:
# - there is something to display
# - there is something to stop displaying
send_ui = False
if ((fcw_alert or steer_alert) and not self.alert_active) or \
(not (fcw_alert or steer_alert) and self.alert_active):
send_ui = True
self.alert_active = not self.alert_active
elif pcm_cancel_cmd:
# forcing the pcm to disengage causes a bad fault sound so play a good sound instead
send_ui = True
if self.frame % 20 == 0 or send_ui:
can_sends.append(toyotacan.create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, hud_control.leftLaneVisible,
hud_control.rightLaneVisible, hud_control.leftLaneDepart,
hud_control.rightLaneDepart, CC.enabled, CS.lkas_hud))
if (self.frame % 100 == 0 or send_ui) and (self.CP.enableDsu or self.CP.flags & ToyotaFlags.DISABLE_RADAR.value):
can_sends.append(toyotacan.create_fcw_command(self.packer, fcw_alert))
# *** static msgs ***
for addr, cars, bus, fr_step, vl in STATIC_DSU_MSGS:
if self.frame % fr_step == 0 and self.CP.enableDsu and self.CP.carFingerprint in cars:
can_sends.append(CanData(addr, vl, bus))
# keep radar disabled
if self.frame % 20 == 0 and self.CP.flags & ToyotaFlags.DISABLE_RADAR.value:
can_sends.append(make_tester_present_msg(0x750, 0, 0xF))
new_actuators = actuators.as_builder()
new_actuators.steer = apply_steer / self.params.STEER_MAX
new_actuators.steerOutputCan = apply_steer
new_actuators.steeringAngleDeg = self.last_angle
new_actuators.accel = self.accel
self.frame += 1
return new_actuators, can_sends