parent
75939d9843
commit
b6a29d0d1c
4 changed files with 145 additions and 97 deletions
@ -0,0 +1,125 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
import numpy as np |
||||||
|
from collections import deque |
||||||
|
|
||||||
|
import cereal.messaging as messaging |
||||||
|
from cereal import car |
||||||
|
from openpilot.common.params import Params |
||||||
|
from openpilot.common.realtime import config_realtime_process, DT_CTRL |
||||||
|
from openpilot.selfdrive.locationd.helpers import ParameterEstimator |
||||||
|
|
||||||
|
MIN_LAG_VEL = 15.0 |
||||||
|
MAX_SANE_LAG = 3.0 |
||||||
|
MIN_HIST_LEN_SEC = 60 |
||||||
|
MAX_LAG_HIST_LEN_SEC = 300 |
||||||
|
MOVING_CORR_WINDOW = 60 |
||||||
|
OVERLAP_FACTOR = 0.25 |
||||||
|
|
||||||
|
|
||||||
|
class LagEstimator(ParameterEstimator): |
||||||
|
def __init__(self, CP, dt, min_hist_len_sec, max_lag_hist_len_sec, moving_corr_window, overlap_factor): |
||||||
|
self.dt = dt |
||||||
|
self.min_hist_len = int(min_hist_len_sec / self.dt) |
||||||
|
self.window_len = int(moving_corr_window / self.dt) |
||||||
|
self.initial_lag = CP.steerActuatorDelay |
||||||
|
self.current_lag = self.initial_lag |
||||||
|
|
||||||
|
self.lat_active = False |
||||||
|
self.steering_pressed = False |
||||||
|
self.v_ego = 0.0 |
||||||
|
self.lags = deque(maxlen= int(max_lag_hist_len_sec / (moving_corr_window * overlap_factor))) |
||||||
|
self.curvature = deque(maxlen=int(moving_corr_window / self.dt)) |
||||||
|
self.desired_curvature = deque(maxlen=int(moving_corr_window / self.dt)) |
||||||
|
self.frame = 0 |
||||||
|
|
||||||
|
def correlation_lags(self, sig_len, dt): |
||||||
|
return np.arange(0, sig_len) * dt |
||||||
|
|
||||||
|
def actuator_delay(self, expected_sig, actual_sig, dt, max_lag=MAX_SANE_LAG): |
||||||
|
assert len(expected_sig) == len(actual_sig) |
||||||
|
correlations = np.correlate(expected_sig, actual_sig, mode='full') |
||||||
|
lags = self.correlation_lags(len(expected_sig), dt) |
||||||
|
|
||||||
|
# only consider negative time shifts within the max_lag |
||||||
|
n_frames_max_delay = int(max_lag / dt) |
||||||
|
correlations = correlations[len(expected_sig) - 1: len(expected_sig) - 1 + n_frames_max_delay] |
||||||
|
lags = lags[:n_frames_max_delay] |
||||||
|
|
||||||
|
max_corr_index = np.argmax(correlations) |
||||||
|
|
||||||
|
lag, corr = lags[max_corr_index], correlations[max_corr_index] |
||||||
|
return lag, corr |
||||||
|
|
||||||
|
def handle_log(self, t, which, msg) -> None: |
||||||
|
if which == "carControl": |
||||||
|
self.lat_active = msg.latActive |
||||||
|
elif which == "carState": |
||||||
|
self.steering_pressed = msg.steeringPressed |
||||||
|
self.v_ego = msg.vEgo |
||||||
|
elif which == "controlsState": |
||||||
|
curvature = msg.curvature |
||||||
|
desired_curvature = msg.desiredCurvature |
||||||
|
if self.lat_active and not self.steering_pressed: |
||||||
|
self.curvature.append((t, curvature)) |
||||||
|
self.desired_curvature.append((t, desired_curvature)) |
||||||
|
self.frame += 1 |
||||||
|
|
||||||
|
def get_msg(self, valid: bool, with_points: bool): |
||||||
|
if len(self.curvature) >= self.min_hist_len: |
||||||
|
if self.frame % int(self.window_len * OVERLAP_FACTOR) == 0: |
||||||
|
_, curvature = zip(*self.curvature) |
||||||
|
_, desired_curvature = zip(*self.desired_curvature) |
||||||
|
delay_curvature, _ = self.actuator_delay(curvature, desired_curvature, self.dt) |
||||||
|
if delay_curvature != 0.0: |
||||||
|
self.lags.append(delay_curvature) |
||||||
|
# FIXME: this is fragile and ugly, refactor this |
||||||
|
if len(self.lags) > 0: |
||||||
|
steer_actuation_delay = float(np.mean(self.lags)) |
||||||
|
else: |
||||||
|
steer_actuation_delay = self.initial_lag |
||||||
|
else: |
||||||
|
steer_actuation_delay = self.initial_lag |
||||||
|
|
||||||
|
msg = messaging.new_message('liveActuatorDelay') |
||||||
|
msg.valid = valid |
||||||
|
|
||||||
|
liveActuatorDelay = msg.liveActuatorDelay |
||||||
|
liveActuatorDelay.steerActuatorDelay = steer_actuation_delay |
||||||
|
liveActuatorDelay.totalPoints = len(self.curvature) |
||||||
|
|
||||||
|
if with_points: |
||||||
|
liveActuatorDelay.points = [[c, dc] for ((_, c), (_, dc)) in zip(self.curvature, self.desired_curvature)] |
||||||
|
|
||||||
|
return msg |
||||||
|
|
||||||
|
|
||||||
|
def main(): |
||||||
|
config_realtime_process([0, 1, 2, 3], 5) |
||||||
|
|
||||||
|
pm = messaging.PubMaster(['liveActuatorDelay', 'alertDebug']) |
||||||
|
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState'], poll='controlsState') |
||||||
|
|
||||||
|
params = Params() |
||||||
|
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) |
||||||
|
estimator = LagEstimator(CP, DT_CTRL, MIN_HIST_LEN_SEC, MAX_LAG_HIST_LEN_SEC, MOVING_CORR_WINDOW, OVERLAP_FACTOR) |
||||||
|
|
||||||
|
while True: |
||||||
|
sm.update() |
||||||
|
if sm.all_checks(): |
||||||
|
for which in sm.updated.keys(): |
||||||
|
if sm.updated[which]: |
||||||
|
t = sm.logMonoTime[which] * 1e-9 |
||||||
|
estimator.handle_log(t, which, sm[which]) |
||||||
|
|
||||||
|
if sm.frame % 25 == 0: |
||||||
|
msg = estimator.get_msg(sm.all_checks(), with_points=True) |
||||||
|
alert_msg = messaging.new_message('alertDebug') |
||||||
|
alert_msg.alertDebug.alertText1 = f"Lag estimateb (fixed: {CP.steerActuatorDelay:.2f} s)" |
||||||
|
alert_msg.alertDebug.alertText2 = f"{msg.liveActuatorDelay.steerActuatorDelay:.2f} s" |
||||||
|
|
||||||
|
pm.send('liveActuatorDelay', msg) |
||||||
|
pm.send('alertDebug', alert_msg) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
main() |
Loading…
Reference in new issue