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.
107 lines
4.8 KiB
107 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
|
|
|