openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

108 lines
4.8 KiB

import numpy as np
import math
from opendbc.can.packer import CANPacker
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, AngleSteeringLimits, DT_CTRL, rate_limit
from opendbc.car.interfaces import CarControllerBase, ISO_LATERAL_ACCEL
from opendbc.car.tesla.teslacan import TeslaCAN
from opendbc.car.tesla.values import CarControllerParams
from opendbc.car.vehicle_model import VehicleModel
# limit angle rate to both prevent a fault and for low speed comfort (~12 mph rate down to 0 mph)
MAX_ANGLE_RATE = 5 # deg/20ms frame, EPS faults at 12 at a standstill
# Add extra tolerance for average banked road since safety doesn't have the roll
AVERAGE_ROAD_ROLL = 0.06 # ~3.4 degrees, 6% superelevation. higher actual roll lowers lateral acceleration
MAX_LATERAL_ACCEL = ISO_LATERAL_ACCEL + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL) # ~3.6 m/s^2
MAX_LATERAL_JERK = 3.0 + (ACCELERATION_DUE_TO_GRAVITY * AVERAGE_ROAD_ROLL) # ~3.6 m/s^3
def get_max_angle_delta(v_ego_raw: float, VM: VehicleModel):
max_curvature_rate_sec = MAX_LATERAL_JERK / (max(v_ego_raw, 1) ** 2) # (1/m)/s
max_angle_rate_sec = math.degrees(VM.get_steer_from_curvature(max_curvature_rate_sec, v_ego_raw, 0)) # deg/s
return max_angle_rate_sec * (DT_CTRL * CarControllerParams.STEER_STEP)
def get_max_angle(v_ego_raw: float, VM: VehicleModel):
max_curvature = MAX_LATERAL_ACCEL / (max(v_ego_raw, 1) ** 2) # 1/m
return math.degrees(VM.get_steer_from_curvature(max_curvature, v_ego_raw, 0)) # deg
def apply_tesla_steer_angle_limits(apply_angle: float, apply_angle_last: float, v_ego_raw: float, steering_angle: float,
lat_active: bool, limits: AngleSteeringLimits, VM: VehicleModel) -> float:
# *** max lateral jerk limit ***
max_angle_delta = get_max_angle_delta(v_ego_raw, VM)
# prevent fault
max_angle_delta = min(max_angle_delta, MAX_ANGLE_RATE)
new_apply_angle = rate_limit(apply_angle, apply_angle_last, -max_angle_delta, max_angle_delta)
# *** max lateral accel limit ***
max_angle = get_max_angle(v_ego_raw, VM)
new_apply_angle = np.clip(new_apply_angle, -max_angle, max_angle)
# angle is current angle when inactive
if not lat_active:
new_apply_angle = steering_angle
# prevent fault
return float(np.clip(new_apply_angle, -limits.STEER_ANGLE_MAX, limits.STEER_ANGLE_MAX))
def get_safety_CP():
# We use the TESLA_MODEL_Y platform for lateral limiting to match safety
# A Model 3 at 40 m/s using the Model Y limits sees a <0.3% difference in max angle (from curvature factor)
from opendbc.car.tesla.interface import CarInterface
return CarInterface.get_non_essential_params("TESLA_MODEL_Y")
class CarController(CarControllerBase):
def __init__(self, dbc_names, CP):
super().__init__(dbc_names, CP)
self.apply_angle_last = 0
self.packer = CANPacker(dbc_names[Bus.party])
self.tesla_can = TeslaCAN(self.packer)
# Vehicle model used for lateral limiting
self.VM = VehicleModel(get_safety_CP())
def update(self, CC, CS, now_nanos):
actuators = CC.actuators
can_sends = []
# Tesla EPS enforces disabling steering on heavy lateral override force.
# When enabling in a tight curve, we wait until user reduces steering force to start steering.
# Canceling is done on rising edge and is handled generically with CC.cruiseControl.cancel
lat_active = CC.latActive and CS.hands_on_level < 3
if self.frame % 2 == 0:
# Angular rate limit based on speed
self.apply_angle_last = apply_tesla_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgoRaw,
CS.out.steeringAngleDeg, lat_active,
CarControllerParams.ANGLE_LIMITS, self.VM)
can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_last, lat_active))
if self.frame % 10 == 0:
can_sends.append(self.tesla_can.create_steering_allowed())
# Longitudinal control
if self.CP.openpilotLongitudinalControl:
if self.frame % 4 == 0:
state = 13 if CC.cruiseControl.cancel else 4 # 4=ACC_ON, 13=ACC_CANCEL_GENERIC_SILENT
accel = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX))
cntr = (self.frame // 4) % 8
can_sends.append(self.tesla_can.create_longitudinal_command(state, accel, cntr, CS.out.vEgo, CC.longActive))
else:
# Increment counter so cancel is prioritized even without openpilot longitudinal
if CC.cruiseControl.cancel:
cntr = (CS.das_control["DAS_controlCounter"] + 1) % 8
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, CS.out.vEgo, False))
# TODO: HUD control
new_actuators = actuators.as_builder()
new_actuators.steeringAngleDeg = self.apply_angle_last
self.frame += 1
return new_actuators, can_sends