|
| 1 | +import math |
| 2 | +import numpy as np |
| 3 | + |
| 4 | +from cereal import log |
| 5 | +from common.filter_simple import FirstOrderFilter |
| 6 | +from common.numpy_fast import clip, interp |
| 7 | +from common.realtime import DT_CTRL |
| 8 | +from selfdrive.controls.lib.latcontrol import LatControl |
| 9 | + |
| 10 | + |
| 11 | +class LatControlINDI(LatControl): |
| 12 | + def __init__(self, CP, CI): |
| 13 | + super().__init__(CP, CI) |
| 14 | + self.angle_steers_des = 0. |
| 15 | + |
| 16 | + A = np.array([[1.0, DT_CTRL, 0.0], |
| 17 | + [0.0, 1.0, DT_CTRL], |
| 18 | + [0.0, 0.0, 1.0]]) |
| 19 | + C = np.array([[1.0, 0.0, 0.0], |
| 20 | + [0.0, 1.0, 0.0]]) |
| 21 | + |
| 22 | + # Q = np.matrix([[1e-2, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 10.0]]) |
| 23 | + # R = np.matrix([[1e-2, 0.0], [0.0, 1e3]]) |
| 24 | + |
| 25 | + # (x, l, K) = control.dare(np.transpose(A), np.transpose(C), Q, R) |
| 26 | + # K = np.transpose(K) |
| 27 | + K = np.array([[7.30262179e-01, 2.07003658e-04], |
| 28 | + [7.29394177e+00, 1.39159419e-02], |
| 29 | + [1.71022442e+01, 3.38495381e-02]]) |
| 30 | + |
| 31 | + self.speed = 0. |
| 32 | + |
| 33 | + self.K = K |
| 34 | + self.A_K = A - np.dot(K, C) |
| 35 | + self.x = np.array([[0.], [0.], [0.]]) |
| 36 | + |
| 37 | + self._RC = (CP.lateralTuning.indi.timeConstantBP, CP.lateralTuning.indi.timeConstantV) |
| 38 | + self._G = (CP.lateralTuning.indi.actuatorEffectivenessBP, CP.lateralTuning.indi.actuatorEffectivenessV) |
| 39 | + self._outer_loop_gain = (CP.lateralTuning.indi.outerLoopGainBP, CP.lateralTuning.indi.outerLoopGainV) |
| 40 | + self._inner_loop_gain = (CP.lateralTuning.indi.innerLoopGainBP, CP.lateralTuning.indi.innerLoopGainV) |
| 41 | + |
| 42 | + self.steer_filter = FirstOrderFilter(0., self.RC, DT_CTRL) |
| 43 | + self.reset() |
| 44 | + |
| 45 | + @property |
| 46 | + def RC(self): |
| 47 | + return interp(self.speed, self._RC[0], self._RC[1]) |
| 48 | + |
| 49 | + @property |
| 50 | + def G(self): |
| 51 | + return interp(self.speed, self._G[0], self._G[1]) |
| 52 | + |
| 53 | + @property |
| 54 | + def outer_loop_gain(self): |
| 55 | + return interp(self.speed, self._outer_loop_gain[0], self._outer_loop_gain[1]) |
| 56 | + |
| 57 | + @property |
| 58 | + def inner_loop_gain(self): |
| 59 | + return interp(self.speed, self._inner_loop_gain[0], self._inner_loop_gain[1]) |
| 60 | + |
| 61 | + def reset(self): |
| 62 | + super().reset() |
| 63 | + self.steer_filter.x = 0. |
| 64 | + self.speed = 0. |
| 65 | + |
| 66 | + def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): |
| 67 | + self.speed = CS.vEgo |
| 68 | + # Update Kalman filter |
| 69 | + y = np.array([[math.radians(CS.steeringAngleDeg)], [math.radians(CS.steeringRateDeg)]]) |
| 70 | + self.x = np.dot(self.A_K, self.x) + np.dot(self.K, y) |
| 71 | + |
| 72 | + indi_log = log.ControlsState.LateralINDIState.new_message() |
| 73 | + indi_log.steeringAngleDeg = math.degrees(self.x[0]) |
| 74 | + indi_log.steeringRateDeg = math.degrees(self.x[1]) |
| 75 | + indi_log.steeringAccelDeg = math.degrees(self.x[2]) |
| 76 | + |
| 77 | + steers_des = VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll) |
| 78 | + steers_des += math.radians(params.angleOffsetDeg) |
| 79 | + indi_log.steeringAngleDesiredDeg = math.degrees(steers_des) |
| 80 | + |
| 81 | + # desired rate is the desired rate of change in the setpoint, not the absolute desired curvature |
| 82 | + rate_des = VM.get_steer_from_curvature(-desired_curvature_rate, CS.vEgo, 0) |
| 83 | + indi_log.steeringRateDesiredDeg = math.degrees(rate_des) |
| 84 | + |
| 85 | + if not active: |
| 86 | + indi_log.active = False |
| 87 | + self.steer_filter.x = 0.0 |
| 88 | + output_steer = 0 |
| 89 | + else: |
| 90 | + # Expected actuator value |
| 91 | + self.steer_filter.update_alpha(self.RC) |
| 92 | + self.steer_filter.update(last_actuators.steer) |
| 93 | + |
| 94 | + # Compute acceleration error |
| 95 | + rate_sp = self.outer_loop_gain * (steers_des - self.x[0]) + rate_des |
| 96 | + accel_sp = self.inner_loop_gain * (rate_sp - self.x[1]) |
| 97 | + accel_error = accel_sp - self.x[2] |
| 98 | + |
| 99 | + # Compute change in actuator |
| 100 | + g_inv = 1. / self.G |
| 101 | + delta_u = g_inv * accel_error |
| 102 | + |
| 103 | + # If steering pressed, only allow wind down |
| 104 | + if CS.steeringPressed and (delta_u * last_actuators.steer > 0): |
| 105 | + delta_u = 0 |
| 106 | + |
| 107 | + output_steer = self.steer_filter.x + delta_u |
| 108 | + |
| 109 | + output_steer = clip(output_steer, -self.steer_max, self.steer_max) |
| 110 | + |
| 111 | + indi_log.active = True |
| 112 | + indi_log.rateSetPoint = float(rate_sp) |
| 113 | + indi_log.accelSetPoint = float(accel_sp) |
| 114 | + indi_log.accelError = float(accel_error) |
| 115 | + indi_log.delayedOutput = float(self.steer_filter.x) |
| 116 | + indi_log.delta = float(delta_u) |
| 117 | + indi_log.output = float(output_steer) |
| 118 | + indi_log.saturated = self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS, steer_limited) |
| 119 | + |
| 120 | + return float(output_steer), float(steers_des), indi_log |
0 commit comments