import math import numpy as np from cereal import log from common.realtime import DT_CTRL from common.numpy_fast import clip from selfdrive.car.toyota.values import SteerLimitParams from selfdrive.car import apply_toyota_steer_torque_limits from selfdrive.controls.lib.drive_helpers import get_steer_max class LatControlINDI(): def __init__(self, CP): self.angle_steers_des = 0. A = np.matrix([[1.0, DT_CTRL, 0.0], [0.0, 1.0, DT_CTRL], [0.0, 0.0, 1.0]]) C = np.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) # Q = np.matrix([[1e-2, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 10.0]]) # R = np.matrix([[1e-2, 0.0], [0.0, 1e3]]) # (x, l, K) = control.dare(np.transpose(A), np.transpose(C), Q, R) # K = np.transpose(K) K = np.matrix([[7.30262179e-01, 2.07003658e-04], [7.29394177e+00, 1.39159419e-02], [1.71022442e+01, 3.38495381e-02]]) self.K = K self.A_K = A - np.dot(K, C) self.x = np.matrix([[0.], [0.], [0.]]) self.enforce_rate_limit = CP.carName == "toyota" self.RC = CP.lateralTuning.indi.timeConstant self.G = CP.lateralTuning.indi.actuatorEffectiveness self.outer_loop_gain = CP.lateralTuning.indi.outerLoopGain self.inner_loop_gain = CP.lateralTuning.indi.innerLoopGain self.alpha = 1. - DT_CTRL / (self.RC + DT_CTRL) self.sat_count_rate = 1.0 * DT_CTRL self.sat_limit = CP.steerLimitTimer self.reset() def reset(self): self.delayed_output = 0. self.output_steer = 0. self.sat_count = 0.0 def _check_saturation(self, control, check_saturation, limit): saturated = abs(control) == limit if saturated and check_saturation: self.sat_count += self.sat_count_rate else: self.sat_count -= self.sat_count_rate self.sat_count = clip(self.sat_count, 0.0, 1.0) return self.sat_count > self.sat_limit def update(self, active, v_ego, angle_steers, angle_steers_rate, eps_torque, steer_override, rate_limited, CP, path_plan): # Update Kalman filter y = np.matrix([[math.radians(angle_steers)], [math.radians(angle_steers_rate)]]) self.x = np.dot(self.A_K, self.x) + np.dot(self.K, y) indi_log = log.ControlsState.LateralINDIState.new_message() indi_log.steerAngle = math.degrees(self.x[0]) indi_log.steerRate = math.degrees(self.x[1]) indi_log.steerAccel = math.degrees(self.x[2]) if v_ego < 0.3 or not active: indi_log.active = False self.output_steer = 0.0 self.delayed_output = 0.0 else: self.angle_steers_des = path_plan.angleSteers self.rate_steers_des = path_plan.rateSteers steers_des = math.radians(self.angle_steers_des) rate_des = math.radians(self.rate_steers_des) # Expected actuator value self.delayed_output = self.delayed_output * self.alpha + self.output_steer * (1. - self.alpha) # Compute acceleration error rate_sp = self.outer_loop_gain * (steers_des - self.x[0]) + rate_des accel_sp = self.inner_loop_gain * (rate_sp - self.x[1]) accel_error = accel_sp - self.x[2] # Compute change in actuator g_inv = 1. / self.G delta_u = g_inv * accel_error # Enforce rate limit if self.enforce_rate_limit: steer_max = float(SteerLimitParams.STEER_MAX) new_output_steer_cmd = steer_max * (self.delayed_output + delta_u) prev_output_steer_cmd = steer_max * self.output_steer new_output_steer_cmd = apply_toyota_steer_torque_limits(new_output_steer_cmd, prev_output_steer_cmd, prev_output_steer_cmd, SteerLimitParams) self.output_steer = new_output_steer_cmd / steer_max else: self.output_steer = self.delayed_output + delta_u steers_max = get_steer_max(CP, v_ego) self.output_steer = clip(self.output_steer, -steers_max, steers_max) indi_log.active = True indi_log.rateSetPoint = float(rate_sp) indi_log.accelSetPoint = float(accel_sp) indi_log.accelError = float(accel_error) indi_log.delayedOutput = float(self.delayed_output) indi_log.delta = float(delta_u) indi_log.output = float(self.output_steer) check_saturation = (v_ego > 10.) and not rate_limited and not steer_override indi_log.saturated = self._check_saturation(self.output_steer, check_saturation, steers_max) return float(self.output_steer), float(self.angle_steers_des), indi_log