class DynamicFollow: def __init__(self, mpc_id): self.mpc_id = mpc_id self.op_params = opParams() self.df_profiles = dfProfiles() self.df_manager = dfManager(self.op_params) self.dmc_v_rel = DistanceModController(k_i=0.042, k_d=0.08, x_clip=[-1, 0, 0.66], mods=[1.15, 1., 0.95]) self.dmc_a_rel = DistanceModController( k_i=0.042 * 1.05, k_d=0.08, x_clip=[-1, 0, 0.33], mods=[1.15, 1., 0.98]) # a_lead loop is 5% faster if not travis and mpc_id == 1: self.pm = messaging.PubMaster(['dynamicFollowData']) else: self.pm = None # Model variables mpc_rate = 1 / 20. self.model_scales = { 'v_ego': [-0.06112159043550491, 37.96522521972656], 'a_lead': [-3.109330892562866, 3.3612186908721924], 'v_lead': [0.0, 35.27671432495117], 'x_lead': [2.4600000381469727, 141.44000244140625] } self.predict_rate = 1 / 4. self.skip_every = round(0.25 / mpc_rate) self.model_input_len = round(45 / mpc_rate) # Dynamic follow variables self.default_TR = 1.8 self.TR = 1.8 self.v_ego_retention = 2.5 self.v_rel_retention = 1.75 self.sng_TR = 1.8 # reacceleration stop and go TR self.sng_speed = 18.0 * CV.MPH_TO_MS self._setup_collector() self._setup_changing_variables() def _setup_collector(self): self.sm_collector = SubMaster(['liveTracks']) self.log_auto_df = self.op_params.get('log_auto_df') if not isinstance(self.log_auto_df, bool): self.log_auto_df = False self.data_collector = DataCollector(file_path='/data/df_data', keys=[ 'v_ego', 'a_ego', 'a_lead', 'v_lead', 'x_lead', 'profile', 'time' ], log_data=self.log_auto_df) def _setup_changing_variables(self): self.TR = self.default_TR self.user_profile = self.df_profiles.relaxed # just a starting point self.model_profile = self.df_profiles.relaxed self.last_effective_profile = self.user_profile self.profile_change_time = 0 self.sng = False self.car_data = CarData() self.lead_data = LeadData() self.df_data = dfData() # dynamic follow data self.last_cost = 0.0 self.last_predict_time = 0.0 self.auto_df_model_data = [] self._get_live_params() # so they're defined just in case def update(self, CS, libmpc): self._get_live_params() self._update_car(CS) self._get_profiles() if self.mpc_id == 1 and self.log_auto_df: self._gather_data() if not self.lead_data.status: self.TR = self.default_TR else: self._store_df_data() self.TR = self._get_TR() if not travis: self._change_cost(libmpc) self._send_cur_state() return self.TR def _get_profiles(self): """This receives profile change updates from dfManager and runs the auto-df prediction if auto mode""" df_out = self.df_manager.update() self.user_profile = df_out.user_profile if df_out.is_auto: # todo: find some way to share prediction between the two mpcs to reduce processing overhead self._get_pred( ) # sets self.model_profile, all other checks are inside function def _gather_data(self): self.sm_collector.update(0) # live_tracks = [[i.dRel, i.vRel, i.aRel, i.yRel] for i in self.sm_collector['liveTracks']] if self.car_data.cruise_enabled: self.data_collector.update([ self.car_data.v_ego, self.car_data.a_ego, self.lead_data.a_lead, self.lead_data.v_lead, self.lead_data.x_lead, self.user_profile, sec_since_boot() ]) def _norm(self, x, name): self.x = x return interp(x, self.model_scales[name], [0, 1]) def _send_cur_state(self): if self.mpc_id == 1 and self.pm is not None: dat = messaging.new_message('dynamicFollowData') dat.dynamicFollowData.mpcTR = self.TR dat.dynamicFollowData.profilePred = self.model_profile self.pm.send('dynamicFollowData', dat) def _change_cost(self, libmpc): TRs = [0.9, 1.8, 2.7] costs = [1., 0.1, 0.01] cost = interp(self.TR, TRs, costs) # change_time = sec_since_boot() - self.profile_change_time # change_time_x = [0, 0.5, 4] # for three seconds after effective profile has changed # change_mod_y = [3, 6, 1] # multiply cost by multiplier to quickly change distance # if change_time < change_time_x[-1]: # if profile changed in last 3 seconds # cost_mod = interp(change_time, change_time_x, change_mod_y) # cost_mod_speeds = [0, 20 * CV.MPH_TO_MS] # don't change cost too much under 20 mph # cost_mods = [cost_mod * 0.01, cost_mod] # cost *= interp(cost_mod, cost_mod_speeds, cost_mods) if self.last_cost != cost: libmpc.change_costs( MPC_COST_LONG.TTC, cost, MPC_COST_LONG.ACCELERATION, MPC_COST_LONG.JERK ) # todo: jerk is the derivative of acceleration, could tune that self.last_cost = cost def _store_df_data(self): cur_time = sec_since_boot() # Store custom relative accel over time if self.lead_data.status: if self.lead_data.new_lead: self.df_data.v_rels = [] # reset when new lead else: self.df_data.v_rels = self._remove_old_entries( self.df_data.v_rels, cur_time, self.v_rel_retention) self.df_data.v_rels.append({ 'v_ego': self.car_data.v_ego, 'v_lead': self.lead_data.v_lead, 'time': cur_time }) # Store our velocity for better sng self.df_data.v_egos = self._remove_old_entries(self.df_data.v_egos, cur_time, self.v_ego_retention) self.df_data.v_egos.append({ 'v_ego': self.car_data.v_ego, 'time': cur_time }) # Store data for auto-df model self.auto_df_model_data.append([ self._norm(self.car_data.v_ego, 'v_ego'), self._norm(self.lead_data.v_lead, 'v_lead'), self._norm(self.lead_data.a_lead, 'a_lead'), self._norm(self.lead_data.x_lead, 'x_lead') ]) while len(self.auto_df_model_data) > self.model_input_len: del self.auto_df_model_data[0] def _get_pred(self): cur_time = sec_since_boot() if self.car_data.cruise_enabled and self.lead_data.status: if cur_time - self.last_predict_time > self.predict_rate: if len(self.auto_df_model_data) == self.model_input_len: pred = predict( np.array(self.auto_df_model_data[::self.skip_every], dtype=np.float32).flatten()) self.last_predict_time = cur_time self.model_profile = int(np.argmax(pred)) @staticmethod def _remove_old_entries(lst, cur_time, retention): return [ sample for sample in lst if cur_time - sample['time'] <= retention ] def _relative_accel_mod(self): """ Returns relative acceleration mod calculated from list of lead and ego velocities over time (longer than 1s) If min_consider_time has not been reached, uses lead accel and ego accel from openpilot (kalman filtered) """ a_ego = self.car_data.a_ego a_lead = self.lead_data.a_lead min_consider_time = 0.75 # minimum amount of time required to consider calculation if len(self.df_data.v_rels) > 0: # if not empty elapsed_time = self.df_data.v_rels[-1][ 'time'] - self.df_data.v_rels[0]['time'] if elapsed_time > min_consider_time: a_ego = (self.df_data.v_rels[-1]['v_ego'] - self.df_data.v_rels[0]['v_ego']) / elapsed_time a_lead = (self.df_data.v_rels[-1]['v_lead'] - self.df_data.v_rels[0]['v_lead']) / elapsed_time mods_x = [-1.5, -.75, 0] mods_y = [1, 1.25, 1.3] if a_lead < 0: # more weight to slight lead decel a_lead *= interp(a_lead, mods_x, mods_y) if a_lead - a_ego > 0: # return only if adding distance return 0 rel_x = [ -2.6822, -1.7882, -0.8941, -0.447, -0.2235, 0.0, 0.2235, 0.447, 0.8941, 1.7882, 2.6822 ] mod_y = [ 0.3245 * 1.1, 0.277 * 1.08, 0.11075 * 1.06, 0.08106 * 1.045, 0.06325 * 1.035, 0.0, -0.09, -0.09375, -0.125, -0.3, -0.35 ] return interp(a_lead - a_ego, rel_x, mod_y) def global_profile_mod(self, x_vel, y_dist): """ This function modifies the y_dist list used by dynamic follow in accordance with global_df_mod """ if self.global_df_mod == 1.: return y_dist global_df_mod = 1 - self.global_df_mod # Calculate new TRs speeds, mods = [0.], [1.] # if increasing don't limit if self.global_df_mod < 1: # if reducing distance speeds = [ 0, self.sng_speed, 18, x_vel[-1] ] # [0, 18 mph, ~40 mph, highest profile mod speed (~78 mph)] mods = [ 0, 0.25, 0.75, 1 ] # how much to limit global_df_mod at each speed, 1 is full effect return [ y - (y * global_df_mod * interp(x, speeds, mods)) for x, y in zip(x_vel, y_dist) ] def _get_TR(self): if self.df_manager.is_auto: # decide which profile to use, model profile will be updated before this df_profile = self.model_profile else: df_profile = self.user_profile if df_profile != self.last_effective_profile: self.profile_change_time = sec_since_boot() self.last_effective_profile = df_profile x_vel = [ 0.0, 1.8627, 3.7253, 5.588, 7.4507, 9.3133, 11.5598, 13.645, 22.352, 31.2928, 33.528, 35.7632, 40.2336 ] # velocities if df_profile == self.df_profiles.roadtrip: y_dist = [ 1.6428, 1.646, 1.6514, 1.6591, 1.6744, 1.6992, 1.7422, 1.7739, 1.8335, 1.8687, 1.8755, 1.8833, 1.8961 ] # TRs elif df_profile == self.df_profiles.traffic: # for in congested traffic x_vel = [ 0.0, 1.892, 3.7432, 5.8632, 8.0727, 10.7301, 14.343, 17.6275, 22.4049, 28.6752, 34.8858, 40.35 ] y_dist = [ 1.3781, 1.3791, 1.3457, 1.3134, 1.3145, 1.318, 1.3485, 1.257, 1.144, 0.979, 0.9461, 0.9156 ] elif df_profile == self.df_profiles.relaxed: # default to relaxed/stock y_dist = [ 1.411, 1.418, 1.428, 1.441, 1.461, 1.49, 1.535, 1.561, 1.589, 1.612, 1.621, 1.632, 1.648 ] else: raise Exception('Unknown profile type: {}'.format(df_profile)) # Global df mod y_dist = self.global_profile_mod(x_vel, y_dist) v_rel_dist_factor = self.dmc_v_rel.update(self.lead_data.v_lead - self.car_data.v_ego) a_lead_dist_factor = self.dmc_a_rel.update(self.lead_data.a_lead - self.car_data.a_ego) TR = interp(self.car_data.v_ego, x_vel, y_dist) TR *= v_rel_dist_factor TR *= a_lead_dist_factor if self.car_data.v_ego > self.sng_speed: # keep sng distance until we're above sng speed again self.sng = False if (self.car_data.v_ego >= self.sng_speed or self.df_data.v_egos[0]['v_ego'] >= self.car_data.v_ego ) and not self.sng: # if above 15 mph OR we're decelerating to a stop, keep shorter TR. when we reaccelerate, use sng_TR and slowly decrease TR = interp(self.car_data.v_ego, x_vel, y_dist) else: # this allows us to get closer to the lead car when stopping, while being able to have smooth stop and go when reaccelerating self.sng = True x = [ self.sng_speed * 0.7, self.sng_speed ] # decrease TR between 12.6 and 18 mph from 1.8s to defined TR above at 18mph while accelerating y = [self.sng_TR, interp(self.sng_speed, x_vel, y_dist)] TR = interp(self.car_data.v_ego, x, y) return float(clip(TR, self.min_TR, 2.7)) TR_mods = [] # Dynamic follow modifications (the secret sauce) x = [ -26, -15.6464, -9.8422, -6.0, -4.0, -2.68, -2.3, -1.8, -1.26, -0.61, 0, 0.61, 1.26, 2.1, 2.68, 4.4704 ] # relative velocity values y = [ 1.76, 1.504, 1.34, 1.29, 1.25, 1.22, 1.19, 1.13, 1.053, 1.017, 1.0, 0.985, 0.958, 0.87, 0.81, 0.685 ] # multiplier values y = np.array(y) - 1 # converts back to original abs mod y *= 1.1 # multiplier for how much to mod y = y / TR + 1 # converts back to multipliers TR_mods.append( interp(self.lead_data.v_lead - self.car_data.v_ego, x, y)) x = [ -4.4795, -2.8122, -1.5727, -1.1129, -0.6611, -0.2692, 0.0, 0.1466, 0.5144, 0.6903, 0.9302 ] # lead acceleration values y = [ 1.16, 1.1067, 1.0613, 1.0343, 1.0203, 1.0147, 1.0, 0.9898, 0.972, 0.9647, 0.9607 ] # multiplier values converted_with_TR = 1.5 # todo: do without numpy and simplify by converting with TR of 1, so only subtract absolute_y_TR_mod = np.array( y ) * converted_with_TR - converted_with_TR # converts back to original abs mod absolute_y_TR_mod *= 1.2 # multiplier for how much to mod y = absolute_y_TR_mod / TR + 1 # converts back to multipliers with accel mod of 1.4 taking current TR into account TR_mods.append(interp(self.get_rel_accel(), x, y)) # todo: make this over more than 1 sec # deadzone = self.car_data.v_ego / 3 # 10 mph at 30 mph # todo: tune pedal to react similarly to without before adding/testing this # if self.lead_data.v_lead - deadzone > self.car_data.v_ego: # TR_mods.append(self._relative_accel_mod()) # x = [self.sng_speed, self.sng_speed / 5.0] # as we approach 0, apply x% more distance # y = [1.0, 1.05] TR *= mean( TR_mods ) # with mods as multipliers, profile mods shouldn't be needed # if (self.car_data.left_blinker or self.car_data.right_blinker) and df_profile != self.df_profiles.traffic: # x = [8.9408, 22.352, 31.2928] # 20, 50, 70 mph # y = [1.0, .75, .65] # TR *= interp(self.car_data.v_ego, x, y) # reduce TR when changing lanes return float(clip(TR, self.min_TR, 2.7)) def update_lead(self, v_lead=None, a_lead=None, x_lead=None, status=False, new_lead=False): self.lead_data.v_lead = v_lead self.lead_data.a_lead = a_lead self.lead_data.x_lead = x_lead self.lead_data.status = status self.lead_data.new_lead = new_lead def _update_car(self, CS): self.car_data.v_ego = CS.vEgo self.car_data.a_ego = CS.aEgo self.car_data.left_blinker = CS.leftBlinker self.car_data.right_blinker = CS.rightBlinker self.car_data.cruise_enabled = CS.cruiseState.enabled def _get_live_params(self): self.global_df_mod = self.op_params.get('global_df_mod') if self.global_df_mod != 1.: self.global_df_mod = clip(self.global_df_mod, 0.85, 2.5) self.min_TR = self.op_params.get('min_TR') if self.min_TR != 1.: self.min_TR = clip(self.min_TR, 0.85, 1.6)
class DynamicFollow: def __init__(self, mpc_id): self.mpc_id = mpc_id self.op_params = opParams() self.df_profiles = dfProfiles() self.df_manager = dfManager(self.op_params) if not travis and mpc_id == 1: self.pm = messaging.PubMaster(['dynamicFollowData']) else: self.pm = None # Model variables mpc_rate = 1 / 20. self.model_scales = {'v_ego': [-0.06112159043550491, 37.96522521972656], 'a_lead': [-3.109330892562866, 3.3612186908721924], 'v_lead': [0.0, 35.27671432495117], 'x_lead': [2.4600000381469727, 141.44000244140625]} self.predict_rate = 1 / 4. self.skip_every = round(0.25 / mpc_rate) self.model_input_len = round(45 / mpc_rate) # Dynamic follow variables self.default_TR = 1.8 self.TR = 1.8 self.v_ego_retention = 2.5 self.v_rel_retention = 1.75 self.sng_TR = 1.8 # reacceleration stop and go TR self.sng_speed = 18.0 * CV.MPH_TO_MS self._setup_collector() self._setup_changing_variables() def _setup_collector(self): self.sm_collector = SubMaster(['liveTracks', 'laneSpeed']) self.log_auto_df = self.op_params.get('log_auto_df') if not isinstance(self.log_auto_df, bool): self.log_auto_df = False self.data_collector = DataCollector(file_path='/data/df_data', keys=['v_ego', 'a_ego', 'a_lead', 'v_lead', 'x_lead', 'left_lane_speeds', 'middle_lane_speeds', 'right_lane_speeds', 'left_lane_distances', 'middle_lane_distances', 'right_lane_distances', 'profile', 'time'], log_data=self.log_auto_df) def _setup_changing_variables(self): self.TR = self.default_TR self.user_profile = self.df_profiles.relaxed # just a starting point self.model_profile = self.df_profiles.relaxed self.last_effective_profile = self.user_profile self.profile_change_time = 0 self.sng = False self.car_data = CarData() self.lead_data = LeadData() self.df_data = dfData() # dynamic follow data self.last_cost = 0.0 self.last_predict_time = 0.0 self.auto_df_model_data = [] self._get_live_params() # so they're defined just in case def update(self, CS, libmpc): self._get_live_params() self._update_car(CS) self._get_profiles() if self.mpc_id == 1 and self.log_auto_df: self._gather_data() if not self.lead_data.status: self.TR = self.default_TR else: self._store_df_data() self.TR = self._get_TR() if not travis: self._change_cost(libmpc) self._send_cur_state() return self.TR def _get_profiles(self): """This receives profile change updates from dfManager and runs the auto-df prediction if auto mode""" df_out = self.df_manager.update() self.user_profile = df_out.user_profile if df_out.is_auto: # todo: find some way to share prediction between the two mpcs to reduce processing overhead self._get_pred() # sets self.model_profile, all other checks are inside function def _gather_data(self): self.sm_collector.update(0) # live_tracks = [[i.dRel, i.vRel, i.aRel, i.yRel] for i in self.sm_collector['liveTracks']] if self.car_data.cruise_enabled: self.data_collector.update([self.car_data.v_ego, self.car_data.a_ego, self.lead_data.a_lead, self.lead_data.v_lead, self.lead_data.x_lead, list(self.sm_collector['laneSpeed'].leftLaneSpeeds), list(self.sm_collector['laneSpeed'].middleLaneSpeeds), list(self.sm_collector['laneSpeed'].rightLaneSpeeds), list(self.sm_collector['laneSpeed'].leftLaneDistances), list(self.sm_collector['laneSpeed'].middleLaneDistances), list(self.sm_collector['laneSpeed'].rightLaneDistances), self.user_profile, sec_since_boot()]) def _norm(self, x, name): self.x = x return interp(x, self.model_scales[name], [0, 1]) def _send_cur_state(self): if self.mpc_id == 1 and self.pm is not None: dat = messaging.new_message() dat.init('dynamicFollowData') dat.dynamicFollowData.mpcTR = self.TR dat.dynamicFollowData.profilePred = self.model_profile self.pm.send('dynamicFollowData', dat) def _change_cost(self, libmpc): TRs = [0.9, 1.8, 2.7] costs = [1.15, 0.15, 0.05] cost = interp(self.TR, TRs, costs) change_time = sec_since_boot() - self.profile_change_time change_time_x = [0, 0.5, 4] # for three seconds after effective profile has changed change_mod_y = [3, 6, 1] # multiply cost by multiplier to quickly change distance if change_time < change_time_x[-1]: # if profile changed in last 3 seconds cost_mod = interp(change_time, change_time_x, change_mod_y) cost_mod_speeds = [0, 20 * CV.MPH_TO_MS] # don't change cost too much under 20 mph cost_mods = [cost_mod * 0.01, cost_mod] cost *= interp(cost_mod, cost_mod_speeds, cost_mods) if self.last_cost != cost: libmpc.change_costs(MPC_COST_LONG.TTC, cost, MPC_COST_LONG.ACCELERATION, MPC_COST_LONG.JERK) self.last_cost = cost def _store_df_data(self): cur_time = sec_since_boot() # Store custom relative accel over time if self.lead_data.status: if self.lead_data.new_lead: self.df_data.v_rels = [] # reset when new lead else: self.df_data.v_rels = self._remove_old_entries(self.df_data.v_rels, cur_time, self.v_rel_retention) self.df_data.v_rels.append({'v_ego': self.car_data.v_ego, 'v_lead': self.lead_data.v_lead, 'time': cur_time}) # Store our velocity for better sng self.df_data.v_egos = self._remove_old_entries(self.df_data.v_egos, cur_time, self.v_ego_retention) self.df_data.v_egos.append({'v_ego': self.car_data.v_ego, 'time': cur_time}) # Store data for auto-df model self.auto_df_model_data.append([self._norm(self.car_data.v_ego, 'v_ego'), self._norm(self.lead_data.v_lead, 'v_lead'), self._norm(self.lead_data.a_lead, 'a_lead'), self._norm(self.lead_data.x_lead, 'x_lead')]) while len(self.auto_df_model_data) > self.model_input_len: del self.auto_df_model_data[0] def _get_pred(self): cur_time = sec_since_boot() if self.car_data.cruise_enabled and self.lead_data.status: if cur_time - self.last_predict_time > self.predict_rate: if len(self.auto_df_model_data) == self.model_input_len: pred = predict(np.array(self.auto_df_model_data[::self.skip_every], dtype=np.float32).flatten()) self.last_predict_time = cur_time self.model_profile = int(np.argmax(pred)) def _remove_old_entries(self, lst, cur_time, retention): return [sample for sample in lst if cur_time - sample['time'] <= retention] def _relative_accel_mod(self): """ Returns relative acceleration mod calculated from list of lead and ego velocities over time (longer than 1s) If min_consider_time has not been reached, uses lead accel and ego accel from openpilot (kalman filtered) """ a_ego = self.car_data.a_ego a_lead = self.lead_data.a_lead min_consider_time = 0.75 # minimum amount of time required to consider calculation if len(self.df_data.v_rels) > 0: # if not empty elapsed_time = self.df_data.v_rels[-1]['time'] - self.df_data.v_rels[0]['time'] if elapsed_time > min_consider_time: a_ego = (self.df_data.v_rels[-1]['v_ego'] - self.df_data.v_rels[0]['v_ego']) / elapsed_time a_lead = (self.df_data.v_rels[-1]['v_lead'] - self.df_data.v_rels[0]['v_lead']) / elapsed_time mods_x = [-1.5, -.75, 0] mods_y = [1, 1.25, 1.3] if a_lead < 0: # more weight to slight lead decel a_lead *= interp(a_lead, mods_x, mods_y) if a_lead - a_ego > 0: # return only if adding distance return 0 rel_x = [-2.6822, -1.7882, -0.8941, -0.447, -0.2235, 0.0, 0.2235, 0.447, 0.8941, 1.7882, 2.6822] mod_y = [0.3245 * 1.1, 0.277 * 1.08, 0.11075 * 1.06, 0.08106 * 1.045, 0.06325 * 1.035, 0.0, -0.09, -0.09375, -0.125, -0.3, -0.35] return interp(a_lead - a_ego, rel_x, mod_y) def global_profile_mod(self, profile_mod_x, profile_mod_pos, profile_mod_neg, x_vel, y_dist): """ This function modifies the y_dist list used by dynamic follow in accordance with global_df_mod It also intelligently adjusts the profile mods at each breakpoint based on the change in TR """ if self.global_df_mod == 1.: return profile_mod_pos, profile_mod_neg, y_dist global_df_mod = 1 - self.global_df_mod # Calculate new TRs speeds = [0, self.sng_speed, 18, x_vel[-1]] # [0, 18 mph, ~40 mph, highest profile mod speed (~78 mph)] mods = [0, 0.1, 0.7, 1] # how much to limit global_df_mod at each speed, 1 is full effect y_dist_new = [y - (y * global_df_mod * interp(x, speeds, mods)) for x, y in zip(x_vel, y_dist)] # Calculate how to change profile mods based on change in TR # eg. if df mod is 0.7, then increase positive mod and decrease negative mod calc_profile_mods = [(interp(mod_x, x_vel, y_dist) - interp(mod_x, x_vel, y_dist_new) + 1) for mod_x in profile_mod_x] profile_mod_pos = [mod_pos * mod for mod_pos, mod in zip(profile_mod_pos, calc_profile_mods)] profile_mod_neg = [mod_neg * ((1 - mod) + 1) for mod_neg, mod in zip(profile_mod_neg, calc_profile_mods)] return profile_mod_pos, profile_mod_neg, y_dist_new def _get_TR(self): x_vel = [0.0, 1.8627, 3.7253, 5.588, 7.4507, 9.3133, 11.5598, 13.645, 22.352, 31.2928, 33.528, 35.7632, 40.2336] # velocities profile_mod_x = [5 * CV.MPH_TO_MS, 30 * CV.MPH_TO_MS, 55 * CV.MPH_TO_MS, 80 * CV.MPH_TO_MS] # profile mod speeds if self.df_manager.is_auto: # decide which profile to use, model profile will be updated before this df_profile = self.model_profile else: df_profile = self.user_profile if df_profile != self.last_effective_profile: self.profile_change_time = sec_since_boot() self.last_effective_profile = df_profile if df_profile == self.df_profiles.roadtrip: y_dist = [1.5486, 1.556, 1.5655, 1.5773, 1.5964, 1.6246, 1.6715, 1.7057, 1.7859, 1.8542, 1.8697, 1.8833, 1.8961] # TRs profile_mod_pos = [0.5, 0.35, 0.1, 0.03] profile_mod_neg = [1.3, 1.4, 1.8, 2.0] elif df_profile == self.df_profiles.traffic: # for in congested traffic x_vel = [0.0, 1.892, 3.7432, 5.8632, 8.0727, 10.7301, 14.343, 17.6275, 22.4049, 28.6752, 34.8858, 40.35] y_dist = [1.3781, 1.3791, 1.3457, 1.3134, 1.3145, 1.318, 1.3485, 1.257, 1.144, 0.979, 0.9461, 0.9156] profile_mod_pos = [1.075, 1.55, 2.6, 3.75] profile_mod_neg = [0.95, .275, 0.1, 0.05] elif df_profile == self.df_profiles.relaxed: # default to relaxed/stock y_dist = [1.385, 1.394, 1.406, 1.421, 1.444, 1.474, 1.521, 1.544, 1.568, 1.588, 1.599, 1.613, 1.634] profile_mod_pos = [1.0, 0.955, 0.898, 0.905] profile_mod_neg = [1.0, 1.0825, 1.1877, 1.174] else: raise Exception('Unknown profile type: {}'.format(df_profile)) # Global df mod profile_mod_pos, profile_mod_neg, y_dist = self.global_profile_mod(profile_mod_x, profile_mod_pos, profile_mod_neg, x_vel, y_dist) # Profile modifications - Designed so that each profile reacts similarly to changing lead dynamics profile_mod_pos = interp(self.car_data.v_ego, profile_mod_x, profile_mod_pos) profile_mod_neg = interp(self.car_data.v_ego, profile_mod_x, profile_mod_neg) if self.car_data.v_ego > self.sng_speed: # keep sng distance until we're above sng speed again self.sng = False if (self.car_data.v_ego >= self.sng_speed or self.df_data.v_egos[0]['v_ego'] >= self.car_data.v_ego) and not self.sng: # if above 15 mph OR we're decelerating to a stop, keep shorter TR. when we reaccelerate, use sng_TR and slowly decrease TR = interp(self.car_data.v_ego, x_vel, y_dist) else: # this allows us to get closer to the lead car when stopping, while being able to have smooth stop and go when reaccelerating self.sng = True x = [self.sng_speed * 0.7, self.sng_speed] # decrease TR between 12.6 and 18 mph from 1.8s to defined TR above at 18mph while accelerating y = [self.sng_TR, interp(self.sng_speed, x_vel, y_dist)] TR = interp(self.car_data.v_ego, x, y) TR_mods = [] # Dynamic follow modifications (the secret sauce) x = [-26.8224, -20.0288, -15.6871, -11.1965, -7.8645, -4.9472, -3.0541, -2.2244, -1.5045, -0.7908, -0.3196, 0.0, 0.5588, 1.3682, 1.898, 2.7316, 4.4704] # relative velocity values y = [.76, 0.62323, 0.49488, 0.40656, 0.32227, 0.23914*1.025, 0.12269*1.05, 0.10483*1.075, 0.08074*1.15, 0.04886*1.25, 0.0072*1.075, 0.0, -0.05648, -0.0792, -0.15675, -0.23289, -0.315] # modification values TR_mods.append(interp(self.lead_data.v_lead - self.car_data.v_ego, x, y)) x = [-4.4795, -2.8122, -1.5727, -1.1129, -0.6611, -0.2692, 0.0, 0.1466, 0.5144, 0.6903, 0.9302] # lead acceleration values y = [0.24, 0.16, 0.092, 0.0515, 0.0305, 0.022, 0.0, -0.0153, -0.042, -0.053, -0.059] # modification values TR_mods.append(interp(self.lead_data.a_lead, x, y)) # deadzone = self.car_data.v_ego / 3 # 10 mph at 30 mph # todo: tune pedal to react similarly to without before adding/testing this # if self.lead_data.v_lead - deadzone > self.car_data.v_ego: # TR_mods.append(self._relative_accel_mod()) x = [self.sng_speed, self.sng_speed / 5.0] # as we approach 0, apply x% more distance y = [1.0, 1.05] profile_mod_pos *= interp(self.car_data.v_ego, x, y) # but only for currently positive mods TR_mod = sum([mod * profile_mod_neg if mod < 0 else mod * profile_mod_pos for mod in TR_mods]) # alter TR modification according to profile TR += TR_mod if (self.car_data.left_blinker or self.car_data.right_blinker) and df_profile != self.df_profiles.traffic: x = [8.9408, 22.352, 31.2928] # 20, 50, 70 mph y = [1.0, .75, .65] TR *= interp(self.car_data.v_ego, x, y) # reduce TR when changing lanes return float(clip(TR, self.min_TR, 2.7)) def update_lead(self, v_lead=None, a_lead=None, x_lead=None, status=False, new_lead=False): self.lead_data.v_lead = v_lead self.lead_data.a_lead = a_lead self.lead_data.x_lead = x_lead self.lead_data.status = status self.lead_data.new_lead = new_lead def _update_car(self, CS): self.car_data.v_ego = CS.vEgo self.car_data.a_ego = CS.aEgo self.car_data.left_blinker = CS.leftBlinker self.car_data.right_blinker = CS.rightBlinker self.car_data.cruise_enabled = CS.cruiseState.enabled def _get_live_params(self): self.global_df_mod = self.op_params.get('global_df_mod') if self.global_df_mod != 1.: self.global_df_mod = clip(self.global_df_mod, 0.85, 1.2) self.min_TR = self.op_params.get('min_TR') if self.min_TR != 1.: self.min_TR = clip(self.min_TR, 0.85, 1.6)