Example #1
0
 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)
Example #2
0
    def __init__(self):
        self.sm = SubMaster(['laneSpeed'])
        self.pm = PubMaster(['dynamicCameraOffset'])
        self.op_params = opParams()
        self.camera_offset = self.op_params.get('camera_offset')

        self.left_lane_oncoming = False  # these variables change
        self.right_lane_oncoming = False
        self.last_left_lane_oncoming = False
        self.last_right_lane_oncoming = False
        self.last_oncoming_time = 0
        self.i = 0.0

        self._setup_static()
Example #3
0
def comm_issue_alert(CP: car.CarParams, CS: car.CarState,
                     sm: messaging.SubMaster, metric: bool,
                     soft_disable_time: int) -> Alert:
    bs = [s for s in sm.data.keys() if not sm.all_checks([
        s,
    ])]
    msg = ', '.join(bs[:4])  # can't fit too many on one line
    return NoEntryAlert(msg,
                        alert_text_1="Communication Issue Between Processes")
Example #4
0
def camera_malfunction_alert(CP: car.CarParams, CS: car.CarState,
                             sm: messaging.SubMaster, metric: bool,
                             soft_disable_time: int) -> Alert:
    all_cams = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState')
    bad_cams = [
        s for s in all_cams if s in sm.data.keys() and not sm.all_checks([
            s,
        ])
    ]
    return NormalPermanentAlert("Camera Malfunction", ', '.join(bad_cams))
Example #5
0
def main():
  def get_influxdb_line(measurement: str, value: float, timestamp: datetime, tags: dict):
    res = f"{measurement}"
    for k, v in tags.items():
      res += f",{k}={str(v)}"
    res += f" value={value} {int(timestamp.timestamp() * 1e9)}\n"
    return res

  # open statistics socket
  ctx = zmq.Context().instance()
  sock = ctx.socket(zmq.PULL)
  sock.bind(STATS_SOCKET)

  # initialize stats directory
  Path(STATS_DIR).mkdir(parents=True, exist_ok=True)

  # initialize tags
  tags = {
    'dongleId': Params().get("DongleId", encoding='utf-8'),
    'started': False,
    'version': get_short_version(),
    'branch': get_short_branch(),
    'dirty': is_dirty(),
    'origin': get_normalized_origin(),
    'deviceType': HARDWARE.get_device_type(),
  }

  # subscribe to deviceState for started state
  sm = SubMaster(['deviceState'])

  last_flush_time = time.monotonic()
  gauges = {}
  while True:
    started_prev = sm['deviceState'].started
    sm.update()

    # Update metrics
    while True:
      try:
        metric = sock.recv_string(zmq.NOBLOCK)
        try:
          metric_type = metric.split('|')[1]
          metric_name = metric.split(':')[0]
          metric_value = metric.split('|')[0].split(':')[1]

          if metric_type == METRIC_TYPE.GAUGE:
            gauges[metric_name] = metric_value
          else:
            cloudlog.event("unknown metric type", metric_type=metric_type)
        except Exception:
          cloudlog.event("malformed metric", metric=metric)
      except zmq.error.Again:
        break

    # flush when started state changes or after FLUSH_TIME_S
    if (time.monotonic() > last_flush_time + STATS_FLUSH_TIME_S) or (sm['deviceState'].started != started_prev):
      result = ""
      current_time = datetime.utcnow().replace(tzinfo=timezone.utc)
      tags['started'] = sm['deviceState'].started

      for gauge_key in gauges:
        result += get_influxdb_line(f"gauge.{gauge_key}", gauges[gauge_key], current_time, tags)

      # clear intermediate data
      gauges = {}
      last_flush_time = time.monotonic()

      # check that we aren't filling up the drive
      if len(os.listdir(STATS_DIR)) < STATS_DIR_FILE_LIMIT:
        if len(result) > 0:
          stats_path = os.path.join(STATS_DIR, str(int(current_time.timestamp())))
          with atomic_write_in_dir(stats_path) as f:
            f.write(result)
      else:
        cloudlog.error("stats dir full")
Example #6
0

def cputime_total(ct):
    return ct.user + ct.nice + ct.system + ct.idle + ct.iowait + ct.irq + ct.softirq


def cputime_busy(ct):
    return ct.user + ct.nice + ct.system + ct.irq + ct.softirq


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--mem', action='store_true')
    args = parser.parse_args()

    sm = SubMaster(['thermal', 'procLog'])

    last_temp = 0.0
    last_mem = 0.0
    total_times = [0., 0., 0., 0.]
    busy_times = [0., 0., 0.0, 0.]

    while True:
        sm.update()

        if sm.updated['thermal']:
            t = sm['thermal']
            last_temp = np.mean([t.cpu0, t.cpu1, t.cpu2, t.cpu3]) / 10.
            last_mem = t.memUsedPercent

        if sm.updated['procLog']:
Example #7
0
def main() -> NoReturn:
  dongle_id = Params().get("DongleId", encoding='utf-8')
  def get_influxdb_line(measurement: str, value: Union[float, Dict[str, float]],  timestamp: datetime, tags: dict) -> str:
    res = f"{measurement}"
    for k, v in tags.items():
      res += f",{k}={str(v)}"
    res += " "

    if isinstance(value, float):
      value = {'value': value}

    for k, v in value.items():
      res += f"{k}={v},"

    res += f"dongle_id=\"{dongle_id}\" {int(timestamp.timestamp() * 1e9)}\n"
    return res

  # open statistics socket
  ctx = zmq.Context().instance()
  sock = ctx.socket(zmq.PULL)
  sock.bind(STATS_SOCKET)

  # initialize stats directory
  Path(STATS_DIR).mkdir(parents=True, exist_ok=True)

  # initialize tags
  tags = {
    'started': False,
    'version': get_short_version(),
    'branch': get_short_branch(),
    'dirty': is_dirty(),
    'origin': get_normalized_origin(),
    'deviceType': HARDWARE.get_device_type(),
  }

  # subscribe to deviceState for started state
  sm = SubMaster(['deviceState'])

  idx = 0
  last_flush_time = time.monotonic()
  gauges = {}
  samples: Dict[str, List[float]] = defaultdict(list)
  while True:
    started_prev = sm['deviceState'].started
    sm.update()

    # Update metrics
    while True:
      try:
        metric = sock.recv_string(zmq.NOBLOCK)
        try:
          metric_type = metric.split('|')[1]
          metric_name = metric.split(':')[0]
          metric_value = float(metric.split('|')[0].split(':')[1])

          if metric_type == METRIC_TYPE.GAUGE:
            gauges[metric_name] = metric_value
          elif metric_type == METRIC_TYPE.SAMPLE:
            samples[metric_name].append(metric_value)
          else:
            cloudlog.event("unknown metric type", metric_type=metric_type)
        except Exception:
          cloudlog.event("malformed metric", metric=metric)
      except zmq.error.Again:
        break

    # flush when started state changes or after FLUSH_TIME_S
    if (time.monotonic() > last_flush_time + STATS_FLUSH_TIME_S) or (sm['deviceState'].started != started_prev):
      result = ""
      current_time = datetime.utcnow().replace(tzinfo=timezone.utc)
      tags['started'] = sm['deviceState'].started

      for key, value in gauges.items():
        result += get_influxdb_line(f"gauge.{key}", value, current_time, tags)

      for key, values in samples.items():
        values.sort()
        sample_count = len(values)
        sample_sum = sum(values)

        stats = {
          'count': sample_count,
          'min': values[0],
          'max': values[-1],
          'mean': sample_sum / sample_count,
        }
        for percentile in [0.05, 0.5, 0.95]:
          value = values[int(round(percentile * (sample_count - 1)))]
          stats[f"p{int(percentile * 100)}"] = value

        result += get_influxdb_line(f"sample.{key}", stats, current_time, tags)

      # clear intermediate data
      gauges.clear()
      samples.clear()
      last_flush_time = time.monotonic()

      # check that we aren't filling up the drive
      if len(os.listdir(STATS_DIR)) < STATS_DIR_FILE_LIMIT:
        if len(result) > 0:
          stats_path = os.path.join(STATS_DIR, f"{current_time.timestamp():.0f}_{idx}")
          with atomic_write_in_dir(stats_path) as f:
            f.write(result)
          idx += 1
      else:
        cloudlog.error("stats dir full")
Example #8
0
class DynamicCameraOffset:
    def __init__(self):
        self.sm = SubMaster(['laneSpeed'])
        self.pm = PubMaster(['dynamicCameraOffset'])
        self.op_params = opParams()
        self.camera_offset = self.op_params.get('camera_offset')

        self.left_lane_oncoming = False  # these variables change
        self.right_lane_oncoming = False
        self.last_left_lane_oncoming = False
        self.last_right_lane_oncoming = False
        self.last_oncoming_time = 0
        self.i = 0.0

        self._setup_static()

    def _setup_static(self):  # these variables are static
        self._enabled = self.op_params.get('dynamic_camera_offset')
        self._min_enable_speed = 35 * CV.MPH_TO_MS
        self._min_lane_width_certainty = 0.4
        hug = 0.1  # how much to hug
        self._center_ratio = 0.5
        self._hug_left_ratio = self._center_ratio - hug
        self._hug_right_ratio = self._center_ratio + hug

        self._keep_offset_for = self.op_params.get(
            'dynamic_camera_offset_time')  # seconds after losing oncoming lane
        self._ramp_angles = [0, 12.5, 25]
        self._ramp_angle_mods = [1, 0.85,
                                 0.1]  # multiply offset by this based on angle

        self._ramp_down_times = [
            self._keep_offset_for, self._keep_offset_for + 2.
        ]
        self._ramp_down_multipliers = [
            1, 0
        ]  # ramp down 1.5s after time has passed

        self._poly_prob_speeds = [
            0, 25 * CV.MPH_TO_MS, 35 * CV.MPH_TO_MS, 60 * CV.MPH_TO_MS
        ]
        self._poly_probs = [0.2, 0.25, 0.45,
                            0.55]  # we're good if only one line is above this

        self._k_p = 1.5
        _i_rate = 1 / 20
        self._k_i = 1.2 * _i_rate

    def update(self, v_ego, active, angle_steers, lane_width_estimate,
               lane_width_certainty, polys, probs):
        if self._enabled:
            self.sm.update(0)
            self.camera_offset = self.op_params.get(
                'camera_offset')  # update base offset from user
            self.left_lane_oncoming = self.sm['laneSpeed'].leftLaneOncoming
            self.right_lane_oncoming = self.sm['laneSpeed'].rightLaneOncoming
            self.lane_width_estimate, self.lane_width_certainty = lane_width_estimate, lane_width_certainty
            self.l_poly, self.r_poly = polys
            self.l_prob, self.r_prob = probs

            dynamic_offset = self._get_camera_offset(v_ego, active,
                                                     angle_steers)
            self._send_state(
            )  # for alerts, before speed check so alerts don't get stuck on
            if dynamic_offset is not None:
                return self.camera_offset + dynamic_offset

            self.i = 0  # reset when not active
        return self.camera_offset  # don't offset if no lane line in direction we're going to hug

    def _get_camera_offset(self, v_ego, active, angle_steers):
        self.keeping_left, self.keeping_right = False, False  # reset keeping
        time_since_oncoming = sec_since_boot() - self.last_oncoming_time
        if not active:  # no alert when not engaged
            return
        if np.isnan(self.l_poly[3]) or np.isnan(self.r_poly[3]):
            return
        if v_ego < self._min_enable_speed:
            return
        _min_poly_prob = interp(v_ego, self._poly_prob_speeds,
                                self._poly_probs)
        if self.l_prob < _min_poly_prob and self.r_prob < _min_poly_prob:  # we only need one line and an accurate current lane width
            return

        left_lane_oncoming = self.left_lane_oncoming
        right_lane_oncoming = self.right_lane_oncoming

        if self.have_oncoming:
            if self.lane_width_certainty < self._min_lane_width_certainty:
                return
            self.last_oncoming_time = sec_since_boot()
            self.last_left_lane_oncoming = self.left_lane_oncoming  # only update last oncoming vars when currently have oncoming. one should always be True for the 2 second ramp down
            self.last_right_lane_oncoming = self.right_lane_oncoming
        elif time_since_oncoming > self._keep_offset_for:  # return if it's 2+ seconds after last oncoming, no need to offset
            return
        else:  # no oncoming and not yet 2 seconds after we lost an oncoming lane. use last oncoming lane for 2 seconds to ramp down offset
            left_lane_oncoming = self.last_left_lane_oncoming
            right_lane_oncoming = self.last_right_lane_oncoming

        estimated_lane_position = self._get_camera_position()

        hug_modifier = interp(
            abs(angle_steers), self._ramp_angles,
            self._ramp_angle_mods)  # don't offset as much when angle is high
        if left_lane_oncoming:
            self.keeping_right = True
            hug_ratio = (self._hug_right_ratio * hug_modifier) + (
                self._center_ratio * (1 - hug_modifier))  # weighted average
        elif right_lane_oncoming:
            self.keeping_left = True
            hug_ratio = (self._hug_left_ratio *
                         hug_modifier) + (self._center_ratio *
                                          (1 - hug_modifier))
        else:
            raise Exception('Error, no lane is oncoming but we\'re here!')

        error = estimated_lane_position - hug_ratio
        self.i += error * self._k_i  # PI controller
        offset = self.i + error * self._k_p

        if time_since_oncoming <= self._keep_offset_for and not self.have_oncoming:  # not yet 3 seconds after last oncoming, ramp down from 1.5 second
            offset *= interp(time_since_oncoming, self._ramp_down_times,
                             self._ramp_down_multipliers)  # ramp down offset

        return offset

    def _send_state(self):
        dco_send = messaging.new_message('dynamicCameraOffset')
        dco_send.dynamicCameraOffset.keepingLeft = self.keeping_left
        dco_send.dynamicCameraOffset.keepingRight = self.keeping_right
        self.pm.send('dynamicCameraOffset', dco_send)

    @property
    def have_oncoming(self):
        return self.left_lane_oncoming != self.right_lane_oncoming  # only one lane oncoming

    def _get_camera_position(self):
        """
    Returns the position of the camera in the lane as a percentage. left to right: [0, 1]; 0.5 is centered
    You MUST verify that either left or right polys and lane width are accurate before calling this function.
    """
        left_line_pos = self.l_poly[
            3] + self.camera_offset  # polys have not been offset yet
        right_line_pos = self.r_poly[3] + self.camera_offset
        cam_pos_left = left_line_pos / self.lane_width_estimate  # estimated position of car in lane based on left line
        cam_pos_right = 1 - abs(
            right_line_pos
        ) / self.lane_width_estimate  # estimated position of car in lane based on right line

        # find car's camera position using weighted average of lane poly certainty
        # if certainty of both lines are high, then just average ~equally
        l_prob = self.l_prob / (self.l_prob + self.r_prob
                                )  # this and next line sums to 1
        r_prob = self.r_prob / (self.l_prob + self.r_prob)
        # be biased towards position found from most probable lane line
        return cam_pos_left * l_prob + cam_pos_right * r_prob
Example #9
0
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
            )  # 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))

    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)
Example #10
0
    name = proc.name
    if len(proc.cmdline):
        name = proc.cmdline[0]
    if len(proc.exe):
        name = proc.exe + " - " + name

    return name


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--mem', action='store_true')
    parser.add_argument('--cpu', action='store_true')
    args = parser.parse_args()

    sm = SubMaster(['deviceState', 'procLog'])

    last_temp = 0.0
    last_mem = 0.0
    total_times = [0.] * 8
    busy_times = [0.] * 8

    prev_proclog: Optional[capnp._DynamicStructReader] = None
    prev_proclog_t: Optional[int] = None

    while True:
        sm.update()

        if sm.updated['deviceState']:
            t = sm['deviceState']
            last_temp = mean(t.cpuTempC)
Example #11
0
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.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   #Orig
        #self.sng_TR = 2.3  # Last value
        #self.sng_TR = 1.5   # Try to decrease stop and start TR so would start move faster from stop
        self.sng_TR = 1.  # Try to decrease stop and start TR so would start move faster from stop
        self.sng_TR = 2.5  # Maybe I thought this wrong
        self.sng_speed = 18.0 * CV.MPH_TO_MS  #Orig
        #self.sng_speed = 12.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('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]     #Original values
        #costs = [1., 0.5, 0.1]      # Last values
        costs = [1., 0.75, 0.5]
        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.stock:
            return 1.8
        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]       # Original values
            #y_dist = [1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.612, 1.621, 1.632, 1.648]
            #y_dist = [1.411, 1.418, 1.8, 1.9, 2.1, 2.0, 1.8, 1.6, 1.6, 1.612, 1.621, 1.632, 1.648]
            #y_dist = [1.8, 1.8, 1.8, 1.9, 2.1, 2.0, 1.8, 1.6, 1.6, 1.55, 1.5, 1.45, 1.4]      # Last used
            #y_dist = [3., 2.6, 2.2, 2.1, 2.1, 2.0, 1.8, 1.6, 1.6, 1.55, 1.5, 1.45, 1.4]
            y_dist = [
                3., 2.6, 2.2, 2.1, 2.1, 2.0, 1.8, 1.7, 1.7, 1.65, 1.55, 1.45,
                1.4
            ]
        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)
            #TR = TR
        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
            #x = [self.sng_speed * 0.55, self.sng_speed]
            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, 2.7)
Example #12
0
 def _setup_collector(self):
     self.sm = SubMaster(['liveTracks'])
Example #13
0
class DynamicFollow:
    def __init__(self, mpc_id):
        self.mpc_id = mpc_id
        self.df_profiles = dfProfiles()

        # 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_lead_retention = 2.0  # keep only last x seconds
        self.v_ego_retention = 2.5
        self.v_rel_retention = 1.5

        self.sng_TR = 1.8  # reacceleration stop and go TR
        self.sng_speed = 18.0 * CV.MPH_TO_MS

        # dp params
        self.last_ts = 0.
        self.dp_df_profile = PROFILE_OFF
        self.dp_last_modified = None
        self.params = Params()

        self._setup_collector()
        self._setup_changing_variables()

    def _setup_collector(self):
        self.sm = SubMaster(['liveTracks'])

    def _setup_changing_variables(self):
        self.TR = self.default_TR
        self.model_profile = None

        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:
            self._gather_data()

        if not self.lead_data.status or self.dp_df_profile == PROFILE_OFF:
            self.TR = self.default_TR
        else:
            self._store_df_data()
            self.TR = self._get_TR()

        if not travis:
            self._change_cost(libmpc)

        return self.TR

    def _get_profiles(self):
        # dp
        # update() gets call every time so we can read profile from param here
        # as usual, we update every 5 secs
        ts = sec_since_boot()
        if self.last_ts is None or ts - self.last_ts >= 5.:
            modified = get_last_modified()
            if self.dp_last_modified != modified:
                try:
                    self.dp_df_profile = int(
                        self.params.get("DragonDynamicFollow",
                                        encoding='utf8'))
                    if self.dp_df_profile > 4 or self.dp_df_profile < 0:
                        self.dp_df_profile = 0
                except (TypeError, ValueError):
                    self.dp_df_profile = PROFILE_OFF
                self.dp_last_modified = modified
            self.last_ts = ts
        if self.dp_df_profile == PROFILE_AUTO:
            self._get_pred(
            )  # sets self.model_profile, all other checks are inside function

    def _gather_data(self):
        self.sm.update(0)

    def _norm(self, x, name):
        self.x = x
        return np.interp(x, self.model_scales[name], [0, 1])

    def _change_cost(self, libmpc):
        TRs = [0.9, 1.8, 2.7]
        costs = [1.0, 0.115, 0.05]
        cost = interp(self.TR, TRs, costs)
        if self.last_cost != cost:
            libmpc.change_tr(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 _calculate_relative_accel_new(self):
        #   """
        #   Moving window returning the following: (final relative velocity - initial relative velocity) / dT with a few extra mods
        #   Output properties:
        #     When the lead is starting to decelerate, and our car remains the same speed, the output decreases (and vice versa)
        #     However when our car finally starts to decelerate at the same rate as the lead car, the output will move to near 0
        #       >>> a = [(15 - 18), (14 - 17)]
        #       >>> (a[-1] - a[0]) / 1
        #       > 0.0
        #   """
        min_consider_time = 0.5  # 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:
                x = [
                    -2.6822, -1.7882, -0.8941, -0.447, -0.2235, 0.0, 0.2235,
                    0.447, 0.8941, 1.7882, 2.6822
                ]
                y = [
                    0.3245, 0.277, 0.11075, 0.08106, 0.06325, 0.0, -0.09,
                    -0.09375, -0.125, -0.3, -0.35
                ]

                v_lead_start = self.df_data.v_rels[0][
                    'v_lead']  # setup common variables
                v_ego_start = self.df_data.v_rels[0]['v_ego']
                v_lead_end = self.df_data.v_rels[-1]['v_lead']
                v_ego_end = self.df_data.v_rels[-1]['v_ego']

                v_ego_change = v_ego_end - v_ego_start
                v_lead_change = v_lead_end - v_lead_start

                if v_lead_change - v_ego_change == 0 or v_lead_change + v_ego_change == 0:
                    return None

                initial_v_rel = v_lead_start - v_ego_start
                cur_v_rel = v_lead_end - v_ego_end
                delta_v_rel = (cur_v_rel - initial_v_rel) / elapsed_time

                neg_pos = False
                if v_ego_change == 0 or v_lead_change == 0:  # FIXME: this all is a mess, but works. need to simplify
                    lead_factor = v_lead_change / (v_lead_change -
                                                   v_ego_change)

                elif (v_ego_change < 0) != (
                        v_lead_change <
                        0):  # one is negative and one is positive, or ^ = XOR
                    lead_factor = v_lead_change / (v_lead_change -
                                                   v_ego_change)
                    if v_ego_change > 0 > v_lead_change:
                        delta_v_rel = -delta_v_rel  # switch when appropriate
                    neg_pos = True

                elif v_ego_change * v_lead_change > 0:  # both are negative or both are positive
                    lead_factor = v_lead_change / (v_lead_change +
                                                   v_ego_change)
                    if v_ego_change > 0 and v_lead_change > 0:  # both are positive
                        if v_ego_change < v_lead_change:
                            delta_v_rel = -delta_v_rel  # switch when appropriate
                    elif v_ego_change > v_lead_change:  # both are negative and v_ego_change > v_lead_change
                        delta_v_rel = -delta_v_rel

                else:
                    raise Exception(
                        'Uncovered case! Should be impossible to be be here')

                if not neg_pos:  # negative and positive require different mod code to be correct
                    rel_vel_mod = (-delta_v_rel *
                                   abs(lead_factor)) + (delta_v_rel *
                                                        (1 - abs(lead_factor)))
                else:
                    rel_vel_mod = math.copysign(delta_v_rel, v_lead_change -
                                                v_ego_change) * lead_factor

                calc_mod = np.interp(rel_vel_mod, x, y)
                if v_lead_end > v_ego_end and calc_mod >= 0:
                    # if we're accelerating quicker than lead but lead is still faster, reduce mod
                    # todo: could remove this since we restrict this mod where called
                    x = np.array([0, 2, 4, 8]) * CV.MPH_TO_MS
                    y = [1.0, -0.25, -0.65, -0.95]
                    v_rel_mod = np.interp(v_lead_end - v_ego_end, x, y)
                    calc_mod *= v_rel_mod
                return calc_mod
        return None

    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 is None:
            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 * np.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 = [(np.interp(mod_x, x_vel, y_dist) -
                              np.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 = [2.2352, 13.4112, 24.5872, 35.7632
                         ]  # profile mod speeds, mph: [5., 30., 55., 80.]

        if self.dp_df_profile == PROFILE_AUTO:  # decide which profile to use, model profile will be updated before this
            # df is 0 = traffic, 1 = relaxed, 2 = roadtrip, 3 = auto
            # dp is 0 = off, 1 = short, 2 = normal, 3 = long, 4 = auto
            # if it's model profile, we need to convert it
            if self.model_profile is None:
                # when its none, we use normal instead
                df_profile = PROFILE_NORMAL
            else:
                df_profile = self.model_profile + 1
        else:
            df_profile = self.dp_df_profile

        if df_profile == PROFILE_LONG:
            y_dist = [
                1.3978, 1.4132, 1.4318, 1.4536, 1.485, 1.5229, 1.5819, 1.6203,
                1.7238, 1.8231, 1.8379, 1.8495, 1.8535
            ]  # TRs
            profile_mod_pos = [0.92, 0.7, 0.25, 0.15]
            profile_mod_neg = [1.1, 1.3, 2.0, 2.3]
        elif df_profile == PROFILE_SHORT:  # 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.3802, 1.3825, 1.3984, 1.4249, 1.4194, 1.3162, 1.1916, 1.0145, 0.9855, 0.9562]  # original
            # y_dist = [1.3781, 1.3791, 1.3112, 1.2442, 1.2306, 1.2112, 1.2775, 1.1977, 1.0963, 0.9435, 0.9067, 0.8749]  # avg. 7.3 ft closer from 18 to 90 mph
            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.05, 1.55, 2.6, 3.75]
            profile_mod_neg = [0.84, .275, 0.1, 0.05]
        elif df_profile == PROFILE_NORMAL:  # default to relaxed/stock
            y_dist = [
                1.385, 1.394, 1.406, 1.421, 1.444, 1.474, 1.516, 1.534, 1.546,
                1.568, 1.579, 1.593, 1.614
            ]
            profile_mod_pos = [1.0] * 4
            profile_mod_neg = [1.0] * 4
        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, 0.12269, 0.10483,
            0.08074, 0.04886, 0.0072, 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))

        rel_accel_mod = self._calculate_relative_accel_new()
        if rel_accel_mod is not None:  # if available
            deadzone = 2 * CV.MPH_TO_MS
            if self.lead_data.v_lead - deadzone > self.car_data.v_ego:
                TR_mods.append(rel_accel_mod)

        x = [self.sng_speed / 5.0,
             self.sng_speed]  # as we approach 0, apply x% more distance
        y = [1.05, 1.0]
        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 = None  #self.op_params.get('global_df_mod', None)
        if self.global_df_mod is not None:
            self.global_df_mod = clip(self.global_df_mod, 0.85, 1.2)
        self.min_TR = 0.9  # default
Example #14
0
from cereal.messaging import SubMaster
import time

sm = SubMaster(['dynamicFollowData'])

while True:
    sm.update(0)
    print('mpc_TR: {}'.format(sm['dynamicFollowData'].mpcTR))
    time.sleep(1 / 20)
Example #15
0
from cereal.messaging import SubMaster

sm = SubMaster(['modelV2'], poll=['modelV2'])
model_t = [
    0, 0.009765625, 0.0390625, 0.087890625, 0.15625, 0.24414062, 0.3515625,
    0.47851562, 0.625, 0.79101562, 0.9765625, 1.1816406, 1.40625, 1.6503906,
    1.9140625, 2.1972656, 2.5, 2.8222656, 3.1640625, 3.5253906, 3.90625,
    4.3066406, 4.7265625, 5.1660156, 5.625, 6.1035156, 6.6015625, 7.1191406,
    7.65625, 8.2128906, 8.7890625, 9.3847656, 10
]
mpc_idxs = list(range(10))

model_t_idx = [
    sorted(range(len(model_t)),
           key=[abs(idx - t) for t in model_t].__getitem__)[0]
    for idx in mpc_idxs
]  # matches 0 to 9 interval to idx from t
# speed_curr_idx = sorted(range(len(model_t)), key=[abs(t - .1) for t in model_t].__getitem__)[0]  # idx used for current speed, position still uses model_t_idx

while 1:
    sm.update()

    modelV2 = sm['modelV2']
    if not sm.updated['modelV2'] or len(modelV2.position.x) == 0:
        continue

    distances, speeds, accelerations = [], [], [
    ]  # everything is derived from x position since velocity is outputting weird values
    for t in model_t_idx:
        speeds.append(modelV2.velocity.x[t])
        distances.append(modelV2.position.x[t])