import math import numpy as np from common.numpy_fast import clip, interp from selfdrive.config import Conversions as CV _K_CURV_V = [1., 0.6] _K_CURV_BP = [0., 0.002] def calc_d_lookahead(v_ego, d_poly): #*** this function computes how far too look for lateral control # howfar we look ahead is function of speed and how much curvy is the path offset_lookahead = 1. k_lookahead = 7. # integrate abs value of second derivative of poly to get a measure of path curvature pts_len = 50. # m if len(d_poly)>0: pts = np.polyval([6*d_poly[0], 2*d_poly[1]], np.arange(0, pts_len)) else: pts = 0. curv = np.sum(np.abs(pts))/pts_len k_curv = interp(curv, _K_CURV_BP, _K_CURV_V) # sqrt on speed is needed to keep, for a given curvature, the y_des # proportional to speed. Indeed, y_des is prop to d_lookahead^2 # 36m at 25m/s d_lookahead = offset_lookahead + math.sqrt(max(v_ego, 0)) * k_lookahead * k_curv return d_lookahead def calc_lookahead_offset(v_ego, angle_steers, d_lookahead, VM, angle_offset): #*** this function returns the lateral offset given the steering angle, speed and the lookahead distance sa = (angle_steers - angle_offset) * CV.DEG_TO_RAD curvature = VM.calc_curvature(sa, v_ego) # clip is to avoid arcsin NaNs due to too sharp turns y_actual = d_lookahead * np.tan(np.arcsin(np.clip(d_lookahead * curvature, -0.999, 0.999))/2.) return y_actual, curvature def calc_desired_steer_angle(v_ego, y_des, d_lookahead, VM, angle_offset): # inverse of the above function curvature = np.sin(np.arctan(y_des / d_lookahead) * 2.) / d_lookahead steer_des = VM.get_steer_from_curvature(curvature, v_ego) * CV.RAD_TO_DEG + angle_offset return steer_des, curvature def pid_lateral_control(v_ego, sa_actual, sa_des, Ui_steer, steer_max, steer_override, sat_count, enabled, Kp, Ki, rate): sat_count_rate = 1./rate sat_count_limit = 0.8 # after 0.8s of continuous saturation, an alert will be sent error_steer = sa_des - sa_actual Ui_unwind_speed = 0.3/rate #.3 per second Up_steer = error_steer*Kp Ui_steer_new = Ui_steer + error_steer*Ki * 1./rate output_steer_new = Ui_steer_new + Up_steer # Anti-wind up for integrator: do not integrate if we are against the steer limits if ( (error_steer >= 0. and (output_steer_new < steer_max or Ui_steer < 0)) or (error_steer <= 0. and (output_steer_new > -steer_max or Ui_steer > 0))) and not steer_override: #update integrator Ui_steer = Ui_steer_new # unwind integrator if driver is maneuvering the steering wheel elif steer_override: Ui_steer -= Ui_unwind_speed * np.sign(Ui_steer) # still, intergral term should not be bigger then limits Ui_steer = clip(Ui_steer, -steer_max, steer_max) output_steer = Up_steer + Ui_steer # don't run steer control if at very low speed if v_ego < 0.3 or not enabled: output_steer = 0. Ui_steer = 0. # useful to know if control is against the limit lateral_control_sat = False if abs(output_steer) > steer_max: lateral_control_sat = True output_steer = clip(output_steer, -steer_max, steer_max) # if lateral control is saturated for a certain period of time, send an alert for taking control of the car # wind if lateral_control_sat and not steer_override and v_ego > 10 and abs(error_steer) > 0.1: sat_count += sat_count_rate # unwind else: sat_count -= sat_count_rate sat_flag = False if sat_count >= sat_count_limit: sat_flag = True sat_count = clip(sat_count, 0, 1) return output_steer, Up_steer, Ui_steer, lateral_control_sat, sat_count, sat_flag class LatControl(object): def __init__(self): self.Up_steer = 0. self.sat_count = 0 self.y_des = 0.0 self.lateral_control_sat = False self.Ui_steer = 0. self.reset() def reset(self): self.Ui_steer = 0. def update(self, enabled, v_ego, angle_steers, steer_override, d_poly, angle_offset, VM): rate = 100 steer_max = 1.0 # how far we look ahead is function of speed and desired path d_lookahead = calc_d_lookahead(v_ego, d_poly) # desired lookahead offset self.y_des = np.polyval(d_poly, d_lookahead) # calculate actual offset at the lookahead point self.angle_steers_des, _ = calc_desired_steer_angle(v_ego, self.y_des, d_lookahead, VM, angle_offset) output_steer, self.Up_steer, self.Ui_steer, self.lateral_control_sat, self.sat_count, sat_flag = pid_lateral_control( v_ego, angle_steers, self.angle_steers_des, self.Ui_steer, steer_max, steer_override, self.sat_count, enabled, VM.CP.steerKp, VM.CP.steerKi, rate) final_steer = clip(output_steer, -steer_max, steer_max) return final_steer, sat_flag