Esempio n. 1
0
class LateralPlanner():
    def __init__(self, CP):
        self.LP = LanePlanner()

        self.last_cloudlog_t = 0
        self.steer_rate_cost = CP.steerRateCost

        self.setup_mpc()
        self.solution_invalid_cnt = 0
        self.use_lanelines = Params().get('EndToEndToggle') != b'1'
        self.lane_change_state = LaneChangeState.off
        self.lane_change_direction = LaneChangeDirection.none
        self.lane_change_timer = 0.0
        self.lane_change_ll_prob = 1.0
        self.prev_one_blinker = False
        self.desire = log.LateralPlan.Desire.none

        self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3))
        self.plan_yaw = np.zeros((TRAJECTORY_SIZE, ))
        self.t_idxs = np.arange(TRAJECTORY_SIZE)
        self.y_pts = np.zeros(TRAJECTORY_SIZE)

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                         self.steer_rate_cost)

        self.mpc_solution = libmpc_py.ffi.new("log_t *")
        self.cur_state = libmpc_py.ffi.new("state_t *")
        self.cur_state[0].x = 0.0
        self.cur_state[0].y = 0.0
        self.cur_state[0].psi = 0.0
        self.cur_state[0].curvature = 0.0

        self.desired_curvature = 0.0
        self.safe_desired_curvature = 0.0
        self.desired_curvature_rate = 0.0
        self.safe_desired_curvature_rate = 0.0

    def update(self, sm, CP):
        v_ego = sm['carState'].vEgo
        active = sm['controlsState'].active
        measured_curvature = sm['controlsState'].curvature

        md = sm['modelV2']
        self.LP.parse_model(sm['modelV2'])
        if len(md.position.x) == TRAJECTORY_SIZE and len(
                md.orientation.x) == TRAJECTORY_SIZE:
            self.path_xyz = np.column_stack(
                [md.position.x, md.position.y, md.position.z])
            self.t_idxs = np.array(md.position.t)
            self.plan_yaw = list(md.orientation.z)

        # Lane change logic
        one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
        below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN

        if sm['carState'].leftBlinker:
            self.lane_change_direction = LaneChangeDirection.left
        elif sm['carState'].rightBlinker:
            self.lane_change_direction = LaneChangeDirection.right

        if (not active) or (self.lane_change_timer > LANE_CHANGE_TIME_MAX):
            self.lane_change_state = LaneChangeState.off
            self.lane_change_direction = LaneChangeDirection.none
        else:
            torque_applied = sm['carState'].steeringPressed and \
                             ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                              (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))

            blindspot_detected = (
                (sm['carState'].leftBlindspot
                 and self.lane_change_direction == LaneChangeDirection.left) or
                (sm['carState'].rightBlindspot
                 and self.lane_change_direction == LaneChangeDirection.right))

            lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob

            # State transitions
            # off
            if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
                self.lane_change_state = LaneChangeState.preLaneChange
                self.lane_change_ll_prob = 1.0

            # pre
            elif self.lane_change_state == LaneChangeState.preLaneChange:
                if not one_blinker or below_lane_change_speed:
                    self.lane_change_state = LaneChangeState.off
                elif torque_applied and not blindspot_detected:
                    self.lane_change_state = LaneChangeState.laneChangeStarting

            # starting
            elif self.lane_change_state == LaneChangeState.laneChangeStarting:
                # fade out over .5s
                self.lane_change_ll_prob = max(
                    self.lane_change_ll_prob - 2 * DT_MDL, 0.0)
                # 98% certainty
                if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
                    self.lane_change_state = LaneChangeState.laneChangeFinishing

            # finishing
            elif self.lane_change_state == LaneChangeState.laneChangeFinishing:
                # fade in laneline over 1s
                self.lane_change_ll_prob = min(
                    self.lane_change_ll_prob + DT_MDL, 1.0)
                if one_blinker and self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.preLaneChange
                elif self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.off

        if self.lane_change_state in [
                LaneChangeState.off, LaneChangeState.preLaneChange
        ]:
            self.lane_change_timer = 0.0
        else:
            self.lane_change_timer += DT_MDL

        self.prev_one_blinker = one_blinker

        self.desire = DESIRES[self.lane_change_direction][
            self.lane_change_state]

        # Turn off lanes during lane change
        if self.desire == log.LateralPlan.Desire.laneChangeRight or self.desire == log.LateralPlan.Desire.laneChangeLeft:
            self.LP.lll_prob *= self.lane_change_ll_prob
            self.LP.rll_prob *= self.lane_change_ll_prob
        if self.use_lanelines:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
        else:
            d_path_xyz = self.path_xyz
        y_pts = np.interp(v_ego * self.t_idxs[:MPC_N + 1],
                          np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1])
        heading_pts = np.interp(v_ego * self.t_idxs[:MPC_N + 1],
                                np.linalg.norm(self.path_xyz, axis=1),
                                self.plan_yaw)
        self.y_pts = y_pts

        assert len(y_pts) == MPC_N + 1
        assert len(heading_pts) == MPC_N + 1
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            float(v_ego), CAR_ROTATION_RADIUS, list(y_pts),
                            list(heading_pts))
        # init state for next
        self.cur_state.x = 0.0
        self.cur_state.y = 0.0
        self.cur_state.psi = 0.0
        self.cur_state.curvature = interp(DT_MDL, self.t_idxs[:MPC_N + 1],
                                          self.mpc_solution.curvature)

        # TODO this needs more thought, use .2s extra for now to estimate other delays
        delay = CP.steerActuatorDelay + .2
        current_curvature = self.mpc_solution.curvature[0]
        psi = interp(delay, self.t_idxs[:MPC_N + 1], self.mpc_solution.psi)
        next_curvature_rate = self.mpc_solution.curvature_rate[0]

        # MPC can plan to turn the wheel and turn back before t_delay. This means
        # in high delay cases some corrections never even get commanded. So just use
        # psi to calculate a simple linearization of desired curvature
        curvature_diff_from_psi = psi / (max(v_ego, 1e-1) *
                                         delay) - current_curvature
        next_curvature = current_curvature + 2 * curvature_diff_from_psi

        self.desired_curvature = next_curvature
        self.desired_curvature_rate = next_curvature_rate
        max_curvature_rate = interp(v_ego, MAX_CURVATURE_RATE_SPEEDS,
                                    MAX_CURVATURE_RATES)
        self.safe_desired_curvature_rate = clip(self.desired_curvature_rate,
                                                -max_curvature_rate,
                                                max_curvature_rate)
        self.safe_desired_curvature = clip(
            self.desired_curvature,
            self.safe_desired_curvature - max_curvature_rate / DT_MDL,
            self.safe_desired_curvature + max_curvature_rate / DT_MDL)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution.curvature)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                             CP.steerRateCost)
            self.cur_state.curvature = measured_curvature

            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning("Lateral mpc - nan: True")

        if self.mpc_solution[
                0].cost > 20000. or mpc_nans:  # TODO: find a better way to detect when MPC did not converge
            self.solution_invalid_cnt += 1
        else:
            self.solution_invalid_cnt = 0

    def publish(self, sm, pm):
        plan_solution_valid = self.solution_invalid_cnt < 2
        plan_send = messaging.new_message('lateralPlan')
        plan_send.valid = sm.all_alive_and_valid(
            service_list=['carState', 'controlsState', 'modelV2'])
        plan_send.lateralPlan.laneWidth = float(self.LP.lane_width)
        plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts]
        plan_send.lateralPlan.lProb = float(self.LP.lll_prob)
        plan_send.lateralPlan.rProb = float(self.LP.rll_prob)
        plan_send.lateralPlan.dProb = float(self.LP.d_prob)

        plan_send.lateralPlan.rawCurvature = float(self.desired_curvature)
        plan_send.lateralPlan.rawCurvatureRate = float(
            self.desired_curvature_rate)
        plan_send.lateralPlan.curvature = float(self.safe_desired_curvature)
        plan_send.lateralPlan.curvatureRate = float(
            self.safe_desired_curvature_rate)

        plan_send.lateralPlan.mpcSolutionValid = bool(plan_solution_valid)

        plan_send.lateralPlan.desire = self.desire
        plan_send.lateralPlan.laneChangeState = self.lane_change_state
        plan_send.lateralPlan.laneChangeDirection = self.lane_change_direction

        pm.send('lateralPlan', plan_send)

        if LOG_MPC:
            dat = messaging.new_message('liveMpc')
            dat.liveMpc.x = list(self.mpc_solution.x)
            dat.liveMpc.y = list(self.mpc_solution.y)
            dat.liveMpc.psi = list(self.mpc_solution.psi)
            dat.liveMpc.curvature = list(self.mpc_solution.curvature)
            dat.liveMpc.cost = self.mpc_solution.cost
            pm.send('liveMpc', dat)
Esempio n. 2
0
class LateralPlanner():
  def __init__(self, CP):
    self.LP = LanePlanner()

    self.last_cloudlog_t = 0
    self.steer_rate_cost = CP.steerRateCost

    self.setup_mpc()
    self.solution_invalid_cnt = 0
    self.lane_change_enabled = Params().get('LaneChangeEnabled') == b'1'
    self.lane_change_state = LaneChangeState.off
    self.lane_change_direction = LaneChangeDirection.none
    self.lane_change_timer = 0.0
    self.lane_change_ll_prob = 1.0
    self.prev_one_blinker = False
    self.desire = log.LateralPlan.Desire.none

    self.path_xyz = np.zeros((TRAJECTORY_SIZE,3))
    self.path_xyz_stds = np.ones((TRAJECTORY_SIZE,3))
    self.plan_yaw = np.zeros((TRAJECTORY_SIZE,))
    self.t_idxs = np.arange(TRAJECTORY_SIZE)
    self.y_pts = np.zeros(TRAJECTORY_SIZE)

    self.kegman = kegman_conf(CP)
    self.mpc_frame = 0
    self.model_laneless = "0"


  def setup_mpc(self):
    self.libmpc = libmpc_py.libmpc
    self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, self.steer_rate_cost)

    self.mpc_solution = libmpc_py.ffi.new("log_t *")
    self.cur_state = libmpc_py.ffi.new("state_t *")
    self.cur_state[0].x = 0.0
    self.cur_state[0].y = 0.0
    self.cur_state[0].psi = 0.0
    self.cur_state[0].curvature = 0.0

    self.angle_steers_des = 0.0
    self.angle_steers_des_mpc = 0.0
    self.angle_steers_des_time = 0.0

  def update(self, sm, CP, VM):
    v_ego = sm['carState'].vEgo
    active = sm['controlsState'].active
    steering_wheel_angle_offset_deg = sm['liveParameters'].angleOffsetDeg
    steering_wheel_angle_deg = sm['carState'].steeringAngleDeg

    # Update vehicle model
    x = max(sm['liveParameters'].stiffnessFactor, 0.1)
    sr = max(sm['liveParameters'].steerRatio, 0.1)
    VM.update_params(x, sr)
    curvature_factor = VM.curvature_factor(v_ego)
    measured_curvature = -curvature_factor * math.radians(steering_wheel_angle_deg - steering_wheel_angle_offset_deg) / VM.sR


    md = sm['modelV2']
    self.LP.parse_model(sm['modelV2'])
    if len(md.position.x) == TRAJECTORY_SIZE and len(md.orientation.x) == TRAJECTORY_SIZE:
      self.path_xyz = np.column_stack([md.position.x, md.position.y, md.position.z])
      self.t_idxs = np.array(md.position.t)
      self.plan_yaw = list(md.orientation.z)
    if len(md.orientation.xStd) == TRAJECTORY_SIZE:
      self.path_xyz_stds = np.column_stack([md.position.xStd, md.position.yStd, md.position.zStd])
    # Lane change logic
    one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
    below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN

    if sm['carState'].leftBlinker:
      self.lane_change_direction = LaneChangeDirection.left
    elif sm['carState'].rightBlinker:
      self.lane_change_direction = LaneChangeDirection.right

    if (not active) or (self.lane_change_timer > LANE_CHANGE_TIME_MAX) or (not self.lane_change_enabled):
      self.lane_change_state = LaneChangeState.off
      self.lane_change_direction = LaneChangeDirection.none
    else:
      torque_applied = sm['carState'].steeringPressed and \
                       ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                        (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))

      blindspot_detected = ((sm['carState'].leftBlindspot and self.lane_change_direction == LaneChangeDirection.left) or
                            (sm['carState'].rightBlindspot and self.lane_change_direction == LaneChangeDirection.right))

      lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob

      # State transitions
      # off
      if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
        self.lane_change_state = LaneChangeState.preLaneChange
        self.lane_change_ll_prob = 1.0

      # pre
      elif self.lane_change_state == LaneChangeState.preLaneChange:
        if not one_blinker or below_lane_change_speed:
          self.lane_change_state = LaneChangeState.off
        elif torque_applied and not blindspot_detected:
          self.lane_change_state = LaneChangeState.laneChangeStarting

      # starting
      elif self.lane_change_state == LaneChangeState.laneChangeStarting:
        # fade out over .5s
        self.lane_change_ll_prob = max(self.lane_change_ll_prob - 2*DT_MDL, 0.0)
        # 98% certainty
        if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
          self.lane_change_state = LaneChangeState.laneChangeFinishing

      # finishing
      elif self.lane_change_state == LaneChangeState.laneChangeFinishing:
        # fade in laneline over 1s
        self.lane_change_ll_prob = min(self.lane_change_ll_prob + DT_MDL, 1.0)
        if one_blinker and self.lane_change_ll_prob > 0.99:
          self.lane_change_state = LaneChangeState.preLaneChange
        elif self.lane_change_ll_prob > 0.99:
          self.lane_change_state = LaneChangeState.off

    if self.lane_change_state in [LaneChangeState.off, LaneChangeState.preLaneChange]:
      self.lane_change_timer = 0.0
    else:
      self.lane_change_timer += DT_MDL

    self.prev_one_blinker = one_blinker

    self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]

    # Turn off lanes during lane change
    if self.desire == log.LateralPlan.Desire.laneChangeRight or self.desire == log.LateralPlan.Desire.laneChangeLeft:
      self.LP.lll_prob *= self.lane_change_ll_prob
      self.LP.rll_prob *= self.lane_change_ll_prob
   
    
    self.mpc_frame += 1
    if self.mpc_frame % 1000 == 0:
      # live tuning through /data/openpilot/tune.py for laneless toggle
      self.kegman = kegman_conf()
      self.model_laneless = float(self.kegman.conf['laneLess'])
      self.mpc_frame = 0

    if self.model_laneless == 0:
      d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
      self.libmpc.init_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost)
    else:
      d_path_xyz = self.path_xyz
      path_cost = np.clip(abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5, 5.0) * MPC_COST_LAT.PATH
      # Heading cost is useful at low speed, otherwise end of plan can be off-heading
      heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.0])
      self.libmpc.init_weights(path_cost, heading_cost, CP.steerRateCost)
    y_pts = np.interp(v_ego * self.t_idxs[:MPC_N+1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:,1])
    heading_pts = np.interp(v_ego * self.t_idxs[:MPC_N+1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw)
   
    self.y_pts = y_pts

    assert len(y_pts) == MPC_N + 1
    assert len(heading_pts) == MPC_N + 1
    self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                        float(v_ego),
                        CAR_ROTATION_RADIUS,
                        list(y_pts),
                        list(heading_pts))
    # init state for next
    self.cur_state.x = 0.0
    self.cur_state.y = 0.0
    self.cur_state.psi = 0.0
    self.cur_state.curvature = interp(DT_MDL, self.t_idxs[:MPC_N+1], self.mpc_solution.curvature)

    # TODO this needs more thought, use .2s extra for now to estimate other delays
    delay = CP.steerActuatorDelay + .2
    current_curvature = self.mpc_solution.curvature[0]
    psi = interp(delay, self.t_idxs[:MPC_N+1], self.mpc_solution.psi)
    next_curvature_rate = self.mpc_solution.curvature_rate[0]

    # MPC can plan to turn the wheel and turn back before t_delay. This means
    # in high delay cases some corrections never even get commanded. So just use
    # psi to calculate a simple linearization of desired curvature
    curvature_diff_from_psi = psi/(max(v_ego, 1e-1) * delay) - current_curvature
    next_curvature = current_curvature + 2*curvature_diff_from_psi

    # reset to current steer angle if not active or overriding
    if active:
      curvature_desired = next_curvature
      desired_curvature_rate = next_curvature_rate
    else:
      curvature_desired = measured_curvature
      desired_curvature_rate = 0.0

    # negative sign, controls uses different convention
    self.desired_steering_wheel_angle_deg = -float(math.degrees(curvature_desired * VM.sR)/curvature_factor) + steering_wheel_angle_offset_deg
    self.desired_steering_wheel_angle_rate_deg = -float(math.degrees(desired_curvature_rate * VM.sR)/curvature_factor)

    #  Check for infeasable MPC solution
    mpc_nans = any(math.isnan(x) for x in self.mpc_solution.curvature)
    t = sec_since_boot()
    if mpc_nans:
      self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost)
      self.cur_state.curvature = measured_curvature

      if t > self.last_cloudlog_t + 5.0:
        self.last_cloudlog_t = t
        cloudlog.warning("Lateral mpc - nan: True")

    if self.mpc_solution[0].cost > 20000. or mpc_nans:   # TODO: find a better way to detect when MPC did not converge
      self.solution_invalid_cnt += 1
    else:
      self.solution_invalid_cnt = 0

  def publish(self, sm, pm):
    plan_solution_valid = self.solution_invalid_cnt < 2
    plan_send = messaging.new_message('lateralPlan')
    plan_send.valid = sm.all_alive_and_valid(service_list=['carState', 'controlsState', 'liveParameters', 'modelV2'])
    plan_send.lateralPlan.laneWidth = float(self.LP.lane_width)
    plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts]
    plan_send.lateralPlan.lProb = float(self.LP.lll_prob)
    plan_send.lateralPlan.rProb = float(self.LP.rll_prob)
    plan_send.lateralPlan.dProb = float(self.LP.d_prob)

    plan_send.lateralPlan.steeringAngleDeg = float(self.desired_steering_wheel_angle_deg)
    plan_send.lateralPlan.steeringRateDeg = float(self.desired_steering_wheel_angle_rate_deg)
    plan_send.lateralPlan.angleOffsetDeg = float(sm['liveParameters'].angleOffsetAverageDeg)
    plan_send.lateralPlan.mpcSolutionValid = bool(plan_solution_valid)

    plan_send.lateralPlan.desire = self.desire
    plan_send.lateralPlan.laneChangeState = self.lane_change_state
    plan_send.lateralPlan.laneChangeDirection = self.lane_change_direction

    pm.send('lateralPlan', plan_send)

    if LOG_MPC:
      dat = messaging.new_message('liveMpc')
      dat.liveMpc.x = list(self.mpc_solution[0].x)
      dat.liveMpc.y = list(self.mpc_solution[0].y)
      dat.liveMpc.psi = list(self.mpc_solution[0].psi)
      dat.liveMpc.tire_angle = list(self.mpc_solution[0].tire_angle)
      dat.liveMpc.cost = self.mpc_solution[0].cost
      pm.send('liveMpc', dat)
Esempio n. 3
0
class PathPlanner():
    def __init__(self, CP):
        self.LP = LanePlanner()

        self.last_cloudlog_t = 0
        self.steer_rate_cost = CP.steerRateCost

        self.setup_mpc()
        self.solution_invalid_cnt = 0
        self.lane_change_enabled = Params().get('LaneChangeEnabled') == b'1'
        self.lane_change_state = LaneChangeState.off
        self.lane_change_direction = LaneChangeDirection.none
        self.lane_change_timer = 0.0
        self.lane_change_ll_prob = 1.0
        self.prev_one_blinker = False
        self.desire = log.PathPlan.Desire.none

        self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3))
        self.plan_yaw = np.zeros((TRAJECTORY_SIZE, ))
        self.t_idxs = np.arange(TRAJECTORY_SIZE)
        self.y_pts = np.zeros(TRAJECTORY_SIZE)

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                         self.steer_rate_cost)

        self.mpc_solution = libmpc_py.ffi.new("log_t *")
        self.cur_state = libmpc_py.ffi.new("state_t *")
        self.cur_state[0].x = 0.0
        self.cur_state[0].y = 0.0
        self.cur_state[0].psi = 0.0
        self.cur_state[0].tire_angle = 0.0

        self.angle_steers_des = 0.0
        self.angle_steers_des_mpc = 0.0
        self.angle_steers_des_time = 0.0

    def update(self, sm, CP, VM):
        v_ego = sm['carState'].vEgo
        active = sm['controlsState'].active
        steering_wheel_angle_offset_deg = sm['liveParameters'].angleOffset
        steering_wheel_angle_deg = sm['carState'].steeringAngle
        measured_tire_angle = -math.radians(
            steering_wheel_angle_deg - steering_wheel_angle_offset_deg) / VM.sR

        # Update vehicle model
        x = max(sm['liveParameters'].stiffnessFactor, 0.1)
        sr = max(sm['liveParameters'].steerRatio, 0.1)
        VM.update_params(x, sr)
        curvature_factor = VM.curvature_factor(v_ego)

        md = sm['modelV2']
        self.LP.parse_model(sm['modelV2'])
        if len(md.position.x) == TRAJECTORY_SIZE and len(
                md.orientation.x) == TRAJECTORY_SIZE:
            self.path_xyz = np.column_stack(
                [md.position.x, md.position.y, md.position.z])
            self.t_idxs = np.array(md.position.t)
            self.plan_yaw = list(md.orientation.z)

        # Lane change logic
        one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
        below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN

        if sm['carState'].leftBlinker:
            self.lane_change_direction = LaneChangeDirection.left
        elif sm['carState'].rightBlinker:
            self.lane_change_direction = LaneChangeDirection.right

        if (not active) or (self.lane_change_timer > LANE_CHANGE_TIME_MAX) or (
                not self.lane_change_enabled):
            self.lane_change_state = LaneChangeState.off
            self.lane_change_direction = LaneChangeDirection.none
        else:
            torque_applied = sm['carState'].steeringPressed and \
                             ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                              (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))

            blindspot_detected = (
                (sm['carState'].leftBlindspot
                 and self.lane_change_direction == LaneChangeDirection.left) or
                (sm['carState'].rightBlindspot
                 and self.lane_change_direction == LaneChangeDirection.right))

            lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob

            # State transitions
            # off
            if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
                self.lane_change_state = LaneChangeState.preLaneChange
                self.lane_change_ll_prob = 1.0

            # pre
            elif self.lane_change_state == LaneChangeState.preLaneChange:
                if not one_blinker or below_lane_change_speed:
                    self.lane_change_state = LaneChangeState.off
                elif torque_applied and not blindspot_detected:
                    self.lane_change_state = LaneChangeState.laneChangeStarting

            # starting
            elif self.lane_change_state == LaneChangeState.laneChangeStarting:
                # fade out over .5s
                self.lane_change_ll_prob = max(
                    self.lane_change_ll_prob - 2 * DT_MDL, 0.0)
                # 98% certainty
                if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
                    self.lane_change_state = LaneChangeState.laneChangeFinishing

            # finishing
            elif self.lane_change_state == LaneChangeState.laneChangeFinishing:
                # fade in laneline over 1s
                self.lane_change_ll_prob = min(
                    self.lane_change_ll_prob + DT_MDL, 1.0)
                if one_blinker and self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.preLaneChange
                elif self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.off

        if self.lane_change_state in [
                LaneChangeState.off, LaneChangeState.preLaneChange
        ]:
            self.lane_change_timer = 0.0
        else:
            self.lane_change_timer += DT_MDL

        self.prev_one_blinker = one_blinker

        self.desire = DESIRES[self.lane_change_direction][
            self.lane_change_state]

        # Turn off lanes during lane change
        if self.desire == log.PathPlan.Desire.laneChangeRight or self.desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.lll_prob *= self.lane_change_ll_prob
            self.LP.rll_prob *= self.lane_change_ll_prob
        d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
        y_pts = np.interp(v_ego * self.t_idxs[:MPC_N + 1],
                          np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1])
        heading_pts = np.interp(v_ego * self.t_idxs[:MPC_N + 1],
                                np.linalg.norm(self.path_xyz, axis=1),
                                self.plan_yaw)
        self.y_pts = y_pts

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        assert len(y_pts) == MPC_N + 1
        assert len(heading_pts) == MPC_N + 1
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            float(v_ego_mpc),
                            curvature_factor, CAR_ROTATION_RADIUS, list(y_pts),
                            list(heading_pts))
        # init state for next
        self.cur_state.x = 0.0
        self.cur_state.y = 0.0
        self.cur_state.psi = 0.0
        self.cur_state.tire_angle = interp(DT_MDL, self.t_idxs[:MPC_N + 1],
                                           self.mpc_solution.tire_angle)

        # TODO this needs more thought, use .2s extra for now to estimate other delays
        delay = CP.steerActuatorDelay + .2
        next_tire_angle = interp(DT_MDL + delay, self.t_idxs[:MPC_N + 1],
                                 self.mpc_solution.tire_angle)
        next_tire_angle_rate = self.mpc_solution.tire_angle_rate[0]

        # reset to current steer angle if not active or overriding
        if active:
            tire_angle_desired = next_tire_angle
            desired_tire_angle_rate = next_tire_angle_rate
        else:
            tire_angle_desired = measured_tire_angle
            desired_tire_angle_rate = 0.0

        # negative sign, controls uses different convention
        self.desired_steering_wheel_angle_deg = -float(
            math.degrees(
                tire_angle_desired * VM.sR)) + steering_wheel_angle_offset_deg
        self.desired_steering_wheel_angle_rate_deg = -float(
            math.degrees(desired_tire_angle_rate * VM.sR))

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution.tire_angle)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                             CP.steerRateCost)
            self.cur_state.tire_angle = measured_tire_angle

            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning("Lateral mpc - nan: True")

        if self.mpc_solution[
                0].cost > 20000. or mpc_nans:  # TODO: find a better way to detect when MPC did not converge
            self.solution_invalid_cnt += 1
        else:
            self.solution_invalid_cnt = 0

    def publish(self, sm, pm):
        plan_solution_valid = self.solution_invalid_cnt < 2
        plan_send = messaging.new_message('pathPlan')
        plan_send.valid = sm.all_alive_and_valid(service_list=[
            'carState', 'controlsState', 'liveParameters', 'modelV2'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPathPoints = [float(x) for x in self.y_pts]
        plan_send.pathPlan.lProb = float(self.LP.lll_prob)
        plan_send.pathPlan.rProb = float(self.LP.rll_prob)
        plan_send.pathPlan.dProb = float(self.LP.d_prob)

        plan_send.pathPlan.angleSteers = float(
            self.desired_steering_wheel_angle_deg)
        plan_send.pathPlan.rateSteers = float(
            self.desired_steering_wheel_angle_rate_deg)
        plan_send.pathPlan.angleOffset = float(
            sm['liveParameters'].angleOffsetAverage)
        plan_send.pathPlan.mpcSolutionValid = bool(plan_solution_valid)
        plan_send.pathPlan.paramsValid = bool(sm['liveParameters'].valid)

        plan_send.pathPlan.desire = self.desire
        plan_send.pathPlan.laneChangeState = self.lane_change_state
        plan_send.pathPlan.laneChangeDirection = self.lane_change_direction

        pm.send('pathPlan', plan_send)

        if LOG_MPC:
            dat = messaging.new_message('liveMpc')
            dat.liveMpc.x = list(self.mpc_solution[0].x)
            dat.liveMpc.y = list(self.mpc_solution[0].y)
            dat.liveMpc.psi = list(self.mpc_solution[0].psi)
            dat.liveMpc.tire_angle = list(self.mpc_solution[0].tire_angle)
            dat.liveMpc.cost = self.mpc_solution[0].cost
            pm.send('liveMpc', dat)
Esempio n. 4
0
class LateralPlanner():
    def __init__(self, CP, use_lanelines=True, wide_camera=False):
        self.use_lanelines = use_lanelines
        self.LP = LanePlanner(wide_camera)

        self.last_cloudlog_t = 0
        self.steer_rate_cost = CP.steerRateCost

        self.solution_invalid_cnt = 0
        self.lane_change_state = LaneChangeState.off
        self.lane_change_direction = LaneChangeDirection.none
        self.lane_change_timer = 0.0
        self.lane_change_ll_prob = 1.0
        self.keep_pulse_timer = 0.0
        self.prev_one_blinker = False
        self.desire = log.LateralPlan.Desire.none

        self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3))
        self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3))
        self.plan_yaw = np.zeros((TRAJECTORY_SIZE, ))
        self.t_idxs = np.arange(TRAJECTORY_SIZE)
        self.y_pts = np.zeros(TRAJECTORY_SIZE)

        self.lat_mpc = LateralMpc()
        self.reset_mpc(np.zeros(6))

    def reset_mpc(self, x0=np.zeros(6)):
        self.x0 = x0
        self.lat_mpc.reset(x0=self.x0)
        self.desired_curvature = 0.0
        self.safe_desired_curvature = 0.0
        self.desired_curvature_rate = 0.0
        self.safe_desired_curvature_rate = 0.0

    def update(self, sm, CP):
        v_ego = sm['carState'].vEgo
        active = sm['controlsState'].active
        measured_curvature = sm['controlsState'].curvature

        md = sm['modelV2']
        self.LP.parse_model(sm['modelV2'])
        if len(md.position.x) == TRAJECTORY_SIZE and len(
                md.orientation.x) == TRAJECTORY_SIZE:
            self.path_xyz = np.column_stack(
                [md.position.x, md.position.y, md.position.z])
            self.t_idxs = np.array(md.position.t)
            self.plan_yaw = list(md.orientation.z)
        if len(md.position.xStd) == TRAJECTORY_SIZE:
            self.path_xyz_stds = np.column_stack(
                [md.position.xStd, md.position.yStd, md.position.zStd])

        # Lane change logic
        one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
        below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN

        if (not active) or (self.lane_change_timer > LANE_CHANGE_TIME_MAX):
            self.lane_change_state = LaneChangeState.off
            self.lane_change_direction = LaneChangeDirection.none
        else:
            # LaneChangeState.off
            if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
                self.lane_change_state = LaneChangeState.preLaneChange
                self.lane_change_ll_prob = 1.0

            # LaneChangeState.preLaneChange
            elif self.lane_change_state == LaneChangeState.preLaneChange:
                # Set lane change direction
                if sm['carState'].leftBlinker:
                    self.lane_change_direction = LaneChangeDirection.left
                elif sm['carState'].rightBlinker:
                    self.lane_change_direction = LaneChangeDirection.right
                else:  # If there are no blinkers we will go back to LaneChangeState.off
                    self.lane_change_direction = LaneChangeDirection.none

                torque_applied = sm['carState'].steeringPressed and \
                                ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                                  (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))

                blindspot_detected = (
                    (sm['carState'].leftBlindspot and
                     self.lane_change_direction == LaneChangeDirection.left) or
                    (sm['carState'].rightBlindspot and
                     self.lane_change_direction == LaneChangeDirection.right))

                if not one_blinker or below_lane_change_speed:
                    self.lane_change_state = LaneChangeState.off
                elif torque_applied and not blindspot_detected:
                    self.lane_change_state = LaneChangeState.laneChangeStarting

            # LaneChangeState.laneChangeStarting
            elif self.lane_change_state == LaneChangeState.laneChangeStarting:
                # fade out over .5s
                self.lane_change_ll_prob = max(
                    self.lane_change_ll_prob - 2 * DT_MDL, 0.0)

                # 98% certainty
                lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob
                if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
                    self.lane_change_state = LaneChangeState.laneChangeFinishing

            # LaneChangeState.laneChangeFinishing
            elif self.lane_change_state == LaneChangeState.laneChangeFinishing:
                # fade in laneline over 1s
                self.lane_change_ll_prob = min(
                    self.lane_change_ll_prob + DT_MDL, 1.0)
                if one_blinker and self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.preLaneChange
                elif self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.off

        if self.lane_change_state in [
                LaneChangeState.off, LaneChangeState.preLaneChange
        ]:
            self.lane_change_timer = 0.0
        else:
            self.lane_change_timer += DT_MDL

        self.prev_one_blinker = one_blinker

        self.desire = DESIRES[self.lane_change_direction][
            self.lane_change_state]

        # Send keep pulse once per second during LaneChangeStart.preLaneChange
        if self.lane_change_state in [
                LaneChangeState.off, LaneChangeState.laneChangeStarting
        ]:
            self.keep_pulse_timer = 0.0
        elif self.lane_change_state == LaneChangeState.preLaneChange:
            self.keep_pulse_timer += DT_MDL
            if self.keep_pulse_timer > 1.0:
                self.keep_pulse_timer = 0.0
            elif self.desire in [
                    log.LateralPlan.Desire.keepLeft,
                    log.LateralPlan.Desire.keepRight
            ]:
                self.desire = log.LateralPlan.Desire.none

        # Turn off lanes during lane change
        if self.desire == log.LateralPlan.Desire.laneChangeRight or self.desire == log.LateralPlan.Desire.laneChangeLeft:
            self.LP.lll_prob *= self.lane_change_ll_prob
            self.LP.rll_prob *= self.lane_change_ll_prob
        if self.use_lanelines:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                     CP.steerRateCost)
        else:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                5.0) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.lat_mpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
        y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1],
                          np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1])
        heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1],
                                np.linalg.norm(self.path_xyz, axis=1),
                                self.plan_yaw)
        self.y_pts = y_pts

        assert len(y_pts) == LAT_MPC_N + 1
        assert len(heading_pts) == LAT_MPC_N + 1
        self.x0[4] = v_ego
        self.lat_mpc.run(self.x0, v_ego, CAR_ROTATION_RADIUS, y_pts,
                         heading_pts)
        # init state for next
        self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1],
                            self.lat_mpc.x_sol[:, 3])

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.lat_mpc.x_sol[:, 3])
        t = sec_since_boot()
        if mpc_nans or self.lat_mpc.solution_status != 0:
            self.reset_mpc()
            self.x0[3] = measured_curvature
            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning("Lateral mpc - nan: True")

        if self.lat_mpc.cost > 20000. or mpc_nans:
            self.solution_invalid_cnt += 1
        else:
            self.solution_invalid_cnt = 0

    def publish(self, sm, pm):
        plan_solution_valid = self.solution_invalid_cnt < 2
        plan_send = messaging.new_message('lateralPlan')
        plan_send.valid = sm.all_alive_and_valid(
            service_list=['carState', 'controlsState', 'modelV2'])
        plan_send.lateralPlan.laneWidth = float(self.LP.lane_width)
        plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts]
        plan_send.lateralPlan.psis = [
            float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 2]
        ]
        plan_send.lateralPlan.curvatures = [
            float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 3]
        ]
        plan_send.lateralPlan.curvatureRates = [
            float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]
        ] + [0.0]
        plan_send.lateralPlan.lProb = float(self.LP.lll_prob)
        plan_send.lateralPlan.rProb = float(self.LP.rll_prob)
        plan_send.lateralPlan.dProb = float(self.LP.d_prob)

        plan_send.lateralPlan.mpcSolutionValid = bool(plan_solution_valid)

        plan_send.lateralPlan.desire = self.desire
        plan_send.lateralPlan.useLaneLines = self.use_lanelines
        plan_send.lateralPlan.laneChangeState = self.lane_change_state
        plan_send.lateralPlan.laneChangeDirection = self.lane_change_direction

        pm.send('lateralPlan', plan_send)
Esempio n. 5
0
class LateralPlanner():
    def __init__(self, CP, use_lanelines=True, wide_camera=False):
        self.use_lanelines = use_lanelines
        self.LP = LanePlanner(wide_camera)

        self.last_cloudlog_t = 0
        self.steer_rate_cost = CP.steerRateCost

        self.setup_mpc()
        self.solution_invalid_cnt = 0
        self.lane_change_state = LaneChangeState.off
        self.lane_change_direction = LaneChangeDirection.none
        self.lane_change_timer = 0.0
        self.lane_change_ll_prob = 1.0
        self.prev_one_blinker = False
        self.desire = log.LateralPlan.Desire.none

        self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3))
        self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3))
        self.plan_yaw = np.zeros((TRAJECTORY_SIZE, ))
        self.t_idxs = np.arange(TRAJECTORY_SIZE)
        self.y_pts = np.zeros(TRAJECTORY_SIZE)

        # dp
        self.dp_lc_auto_allowed = False
        self.dp_lc_auto_timer = None
        self.dp_lc_auto_delay = 2.
        self.dp_lc_auto_cont = False
        self.dp_lc_auto_completed = False

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init()

        self.mpc_solution = libmpc_py.ffi.new("log_t *")
        self.cur_state = libmpc_py.ffi.new("state_t *")
        self.cur_state[0].x = 0.0
        self.cur_state[0].y = 0.0
        self.cur_state[0].psi = 0.0
        self.cur_state[0].curvature = 0.0

        self.desired_curvature = 0.0
        self.safe_desired_curvature = 0.0
        self.desired_curvature_rate = 0.0
        self.safe_desired_curvature_rate = 0.0

    def update(self, sm, CP):
        v_ego = sm['carState'].vEgo
        active = sm['controlsState'].active
        measured_curvature = sm['controlsState'].curvature
        self.LP.update_dp_set_offsets(sm['dragonConf'].dpCameraOffset,
                                      sm['dragonConf'].dpPathOffset)

        md = sm['modelV2']
        self.LP.parse_model(sm['modelV2'])
        if len(md.position.x) == TRAJECTORY_SIZE and len(
                md.orientation.x) == TRAJECTORY_SIZE:
            self.path_xyz = np.column_stack(
                [md.position.x, md.position.y, md.position.z])
            self.t_idxs = np.array(md.position.t)
            self.plan_yaw = list(md.orientation.z)
        if len(md.orientation.xStd) == TRAJECTORY_SIZE:
            self.path_xyz_stds = np.column_stack(
                [md.position.xStd, md.position.yStd, md.position.zStd])

        # Lane change logic
        one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
        below_lane_change_speed = v_ego < (sm['dragonConf'].dpLcMinMph *
                                           CV.MPH_TO_MS)

        if (not active) or (self.lane_change_timer > LANE_CHANGE_TIME_MAX):
            self.lane_change_state = LaneChangeState.off
            self.lane_change_direction = LaneChangeDirection.none
        else:
            # LaneChangeState.off
            if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
                self.lane_change_state = LaneChangeState.preLaneChange
                self.lane_change_ll_prob = 1.0

            # dp alc
            cur_time = sec_since_boot()
            if not below_lane_change_speed and sm[
                    'dragonConf'].dpLateralMode == 2 and v_ego >= (
                        sm['dragonConf'].dpLcAutoMinMph * CV.MPH_TO_MS):
                # we allow auto lc when speed reached dragon_auto_lc_min_mph
                self.dp_lc_auto_allowed = True
            else:
                # if too slow, we reset all the variables
                self.dp_lc_auto_allowed = False
                self.dp_lc_auto_timer = None

            # disable auto lc when continuous is off and already did auto lc once
            if self.dp_lc_auto_allowed and not sm[
                    'dragonConf'].dpLcAutoCont and self.dp_lc_auto_completed:
                self.dp_lc_auto_allowed = False

            # LaneChangeState.preLaneChange
            elif self.lane_change_state == LaneChangeState.preLaneChange:
                # Set lane change direction
                if sm['carState'].leftBlinker:
                    self.lane_change_direction = LaneChangeDirection.left
                elif sm['carState'].rightBlinker:
                    self.lane_change_direction = LaneChangeDirection.right
                else:  # If there are no blinkers we will go back to LaneChangeState.off
                    self.lane_change_direction = LaneChangeDirection.none

                torque_applied = sm['carState'].steeringPressed and \
                                ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                                  (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))

                blindspot_detected = (
                    (sm['carState'].leftBlindspot and
                     self.lane_change_direction == LaneChangeDirection.left) or
                    (sm['carState'].rightBlindspot and
                     self.lane_change_direction == LaneChangeDirection.right))

                # dp alc
                if self.dp_lc_auto_allowed:
                    if self.dp_lc_auto_timer is None:
                        self.dp_lc_auto_timer = cur_time + sm[
                            'dragonConf'].dpLcAutoDelay
                    elif cur_time >= self.dp_lc_auto_timer:
                        # if timer is up, we set torque_applied to True to fake user input
                        torque_applied = True
                        self.dp_lc_auto_completed = True

                # we reset the timers when torque is applied regardless
                if torque_applied and not blindspot_detected:
                    self.dp_lc_auto_timer = None

                if not one_blinker or below_lane_change_speed:
                    self.lane_change_state = LaneChangeState.off
                elif torque_applied and not blindspot_detected:
                    self.lane_change_state = LaneChangeState.laneChangeStarting

            # LaneChangeState.laneChangeStarting
            elif self.lane_change_state == LaneChangeState.laneChangeStarting:
                # fade out over .5s
                self.lane_change_ll_prob = max(
                    self.lane_change_ll_prob - 2 * DT_MDL, 0.0)

                # 98% certainty
                lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob
                if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
                    self.lane_change_state = LaneChangeState.laneChangeFinishing

            # LaneChangeState.laneChangeFinishing
            elif self.lane_change_state == LaneChangeState.laneChangeFinishing:
                # fade in laneline over 1s
                self.lane_change_ll_prob = min(
                    self.lane_change_ll_prob + DT_MDL, 1.0)
                if one_blinker and self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.preLaneChange
                elif self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.off

                # dp when finishing, we reset timer to none.
                self.dp_lc_auto_timer = None

        if self.lane_change_state in [
                LaneChangeState.off, LaneChangeState.preLaneChange
        ]:
            self.lane_change_timer = 0.0
        else:
            self.lane_change_timer += DT_MDL

        if self.prev_one_blinker and not one_blinker:
            self.dp_lc_auto_completed = False

        self.prev_one_blinker = one_blinker

        self.desire = DESIRES[self.lane_change_direction][
            self.lane_change_state]

        # Turn off lanes during lane change
        if self.desire == log.LateralPlan.Desire.laneChangeRight or self.desire == log.LateralPlan.Desire.laneChangeLeft:
            self.LP.lll_prob *= self.lane_change_ll_prob
            self.LP.rll_prob *= self.lane_change_ll_prob
        if self.use_lanelines:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                    CP.steerRateCost)
        else:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                5.0) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.libmpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
        y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1],
                          np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1])
        heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1],
                                np.linalg.norm(self.path_xyz, axis=1),
                                self.plan_yaw)
        self.y_pts = y_pts

        assert len(y_pts) == LAT_MPC_N + 1
        assert len(heading_pts) == LAT_MPC_N + 1
        # for now CAR_ROTATION_RADIUS is disabled
        # to use it, enable it in the MPC
        assert abs(CAR_ROTATION_RADIUS) < 1e-3
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            float(v_ego), CAR_ROTATION_RADIUS, list(y_pts),
                            list(heading_pts))
        # init state for next
        self.cur_state.x = 0.0
        self.cur_state.y = 0.0
        self.cur_state.psi = 0.0
        self.cur_state.curvature = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1],
                                          self.mpc_solution.curvature)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution.curvature)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init()
            self.cur_state.curvature = measured_curvature

            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning("Lateral mpc - nan: True")

        if self.mpc_solution[
                0].cost > 20000. or mpc_nans:  # TODO: find a better way to detect when MPC did not converge
            self.solution_invalid_cnt += 1
        else:
            self.solution_invalid_cnt = 0

    def publish(self, sm, pm):
        plan_solution_valid = self.solution_invalid_cnt < 2
        plan_send = messaging.new_message('lateralPlan')
        plan_send.valid = sm.all_alive_and_valid(service_list=[
            'carState', 'controlsState', 'modelV2', 'dragonConf'
        ])
        plan_send.lateralPlan.laneWidth = float(self.LP.lane_width)
        plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts]
        plan_send.lateralPlan.psis = [
            float(x) for x in self.mpc_solution.psi[0:CONTROL_N]
        ]
        plan_send.lateralPlan.curvatures = [
            float(x) for x in self.mpc_solution.curvature[0:CONTROL_N]
        ]
        plan_send.lateralPlan.curvatureRates = [
            float(x) for x in self.mpc_solution.curvature_rate[0:CONTROL_N - 1]
        ] + [0.0]
        plan_send.lateralPlan.lProb = float(self.LP.lll_prob)
        plan_send.lateralPlan.rProb = float(self.LP.rll_prob)
        plan_send.lateralPlan.dProb = float(self.LP.d_prob)

        plan_send.lateralPlan.mpcSolutionValid = bool(plan_solution_valid)

        plan_send.lateralPlan.desire = self.desire
        plan_send.lateralPlan.laneChangeState = self.lane_change_state
        plan_send.lateralPlan.laneChangeDirection = self.lane_change_direction
        plan_send.lateralPlan.dpALCAllowed = self.dp_lc_auto_allowed

        pm.send('lateralPlan', plan_send)

        if LOG_MPC:
            dat = messaging.new_message('liveMpc')
            dat.liveMpc.x = list(self.mpc_solution.x)
            dat.liveMpc.y = list(self.mpc_solution.y)
            dat.liveMpc.psi = list(self.mpc_solution.psi)
            dat.liveMpc.curvature = list(self.mpc_solution.curvature)
            dat.liveMpc.cost = self.mpc_solution.cost
            pm.send('liveMpc', dat)
Esempio n. 6
0
class LateralPlanner():
  def __init__(self, CP, use_lanelines=True, wide_camera=False):
    self.use_lanelines = use_lanelines
    self.LP = LanePlanner(wide_camera)

    self.last_cloudlog_t = 0

    self.setup_mpc()
    self.solution_invalid_cnt = 0

    self.laneless_mode = int(Params().get("LanelessMode", encoding="utf8"))
    self.laneless_mode_status = False
    self.laneless_mode_status_buffer = False

    self.nudgeless_enabled = Params().get_bool("NudgelessLaneChange")
    self.nudgeless_delay = 3.0 # [s] amount of time blinker has to be on before nudgless lane change

    self.lane_change_state = LaneChangeState.off
    self.prev_lane_change_state = self.lane_change_state
    self.preLaneChange_start_t = 0.
    self.lane_change_direction = LaneChangeDirection.none
    self.lane_change_timer = 0.0
    self.lane_change_ll_prob = 1.0
    self.keep_pulse_timer = 0.0
    self.prev_one_blinker = False
    self.desire = log.LateralPlan.Desire.none

    self.path_xyz = np.zeros((TRAJECTORY_SIZE,3))
    self.path_xyz_stds = np.ones((TRAJECTORY_SIZE,3))
    self.plan_yaw = np.zeros((TRAJECTORY_SIZE,))
    self.t_idxs = np.arange(TRAJECTORY_SIZE)
    self.y_pts = np.zeros(TRAJECTORY_SIZE)
    self.d_path_w_lines_xyz = np.zeros((TRAJECTORY_SIZE, 3))
    self.second = 0.0

  def setup_mpc(self):
    self.libmpc = libmpc_py.libmpc
    self.libmpc.init()

    self.mpc_solution = libmpc_py.ffi.new("log_t *")
    self.cur_state = libmpc_py.ffi.new("state_t *")
    self.cur_state[0].x = 0.0
    self.cur_state[0].y = 0.0
    self.cur_state[0].psi = 0.0
    self.cur_state[0].curvature = 0.0

    self.desired_curvature = 0.0
    self.safe_desired_curvature = 0.0
    self.desired_curvature_rate = 0.0
    self.safe_desired_curvature_rate = 0.0

  def update(self, sm, CP):
    self.second += DT_MDL
    if self.second > 1.0:
      self.use_lanelines = not Params().get_bool("EndToEndToggle")
      self.laneless_mode = int(Params().get("LanelessMode", encoding="utf8"))
      self.second = 0.0
    v_ego = sm['carState'].vEgo
    active = sm['controlsState'].active
    measured_curvature = sm['controlsState'].curvature

    md = sm['modelV2']
    self.LP.parse_model(sm['modelV2'])
    if len(md.position.x) == TRAJECTORY_SIZE and len(md.orientation.x) == TRAJECTORY_SIZE:
      self.path_xyz = np.column_stack([md.position.x, md.position.y, md.position.z])
      self.t_idxs = np.array(md.position.t)
      self.plan_yaw = list(md.orientation.z)
    if len(md.orientation.xStd) == TRAJECTORY_SIZE:
      self.path_xyz_stds = np.column_stack([md.position.xStd, md.position.yStd, md.position.zStd])

    # Lane change logic
    one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
    below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN

    if (not active) or (self.lane_change_timer > LANE_CHANGE_TIME_MAX):
      self.lane_change_state = LaneChangeState.off
      self.lane_change_direction = LaneChangeDirection.none
    else:
      # LaneChangeState.off
      if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
        self.lane_change_state = LaneChangeState.preLaneChange
        self.lane_change_ll_prob = 1.0

      # LaneChangeState.preLaneChange
      elif self.lane_change_state == LaneChangeState.preLaneChange:
        t = sec_since_boot()
        if self.prev_lane_change_state in [LaneChangeState.off, LaneChangeState.laneChangeFinishing] and t - self.preLaneChange_start_t > 3.:
          self.preLaneChange_start_t = t
        # Set lane change direction
        if sm['carState'].leftBlinker:
          self.lane_change_direction = LaneChangeDirection.left
        elif sm['carState'].rightBlinker:
          self.lane_change_direction = LaneChangeDirection.right
        else:  # If there are no blinkers we will go back to LaneChangeState.off
          self.lane_change_direction = LaneChangeDirection.none

        torque_applied = sm['carState'].steeringPressed and \
                        ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                          (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))
        
        torque_applied = torque_applied or (self.nudgeless_enabled and t - self.preLaneChange_start_t > self.nudgeless_delay)

        blindspot_detected = ((sm['carState'].leftBlindspot and self.lane_change_direction == LaneChangeDirection.left) or
                              (sm['carState'].rightBlindspot and self.lane_change_direction == LaneChangeDirection.right))

        if not one_blinker or below_lane_change_speed:
          self.lane_change_state = LaneChangeState.off
        elif torque_applied and not blindspot_detected:
          self.lane_change_state = LaneChangeState.laneChangeStarting

      # LaneChangeState.laneChangeStarting
      elif self.lane_change_state == LaneChangeState.laneChangeStarting:
        # fade out over .5s
        self.lane_change_ll_prob = max(self.lane_change_ll_prob - 2*DT_MDL, 0.0)

        # 98% certainty
        lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob
        if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
          self.lane_change_state = LaneChangeState.laneChangeFinishing

      # LaneChangeState.laneChangeFinishing
      elif self.lane_change_state == LaneChangeState.laneChangeFinishing:
        # fade in laneline over 1s
        self.lane_change_ll_prob = min(self.lane_change_ll_prob + DT_MDL, 1.0)
        if one_blinker and self.lane_change_ll_prob > 0.99:
          self.lane_change_state = LaneChangeState.preLaneChange
        elif self.lane_change_ll_prob > 0.99:
          self.lane_change_state = LaneChangeState.off

      self.prev_lane_change_state = self.lane_change_state

    if self.lane_change_state in [LaneChangeState.off, LaneChangeState.preLaneChange]:
      self.lane_change_timer = 0.0
    else:
      self.lane_change_timer += DT_MDL

    self.prev_one_blinker = one_blinker

    self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]

    # Send keep pulse once per second during LaneChangeStart.preLaneChange
    if self.lane_change_state in [LaneChangeState.off, LaneChangeState.laneChangeStarting]:
      self.keep_pulse_timer = 0.0
    elif self.lane_change_state == LaneChangeState.preLaneChange:
      self.keep_pulse_timer += DT_MDL
      if self.keep_pulse_timer > 1.0:
        self.keep_pulse_timer = 0.0
      elif self.desire in [log.LateralPlan.Desire.keepLeft, log.LateralPlan.Desire.keepRight]:
        self.desire = log.LateralPlan.Desire.none

    # Turn off lanes during lane change
    if self.desire == log.LateralPlan.Desire.laneChangeRight or self.desire == log.LateralPlan.Desire.laneChangeLeft:
      self.LP.lll_prob *= self.lane_change_ll_prob
      self.LP.rll_prob *= self.lane_change_ll_prob
    self.d_path_w_lines_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
    if self.use_lanelines:
      d_path_xyz = self.d_path_w_lines_xyz
      self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost)
      self.laneless_mode_status = False
    elif self.laneless_mode == 0:
      d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
      self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost)
      self.laneless_mode_status = False
    elif self.laneless_mode == 1:
      d_path_xyz = self.path_xyz
      path_cost = np.clip(abs(self.path_xyz[0,1]/self.path_xyz_stds[0,1]), 0.5, 5.0) * MPC_COST_LAT.PATH
      # Heading cost is useful at low speed, otherwise end of plan can be off-heading
      heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.0])
      self.libmpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
      self.laneless_mode_status = True
    elif self.laneless_mode == 2 and ((self.LP.lll_prob + self.LP.rll_prob)/2 < 0.3) and self.lane_change_state == LaneChangeState.off:
      d_path_xyz = self.path_xyz
      path_cost = np.clip(abs(self.path_xyz[0,1]/self.path_xyz_stds[0,1]), 0.5, 5.0) * MPC_COST_LAT.PATH
      # Heading cost is useful at low speed, otherwise end of plan can be off-heading
      heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.0])
      self.libmpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
      self.laneless_mode_status = True
      self.laneless_mode_status_buffer = True
    elif self.laneless_mode == 2 and ((self.LP.lll_prob + self.LP.rll_prob)/2 > 0.5) and \
     self.laneless_mode_status_buffer and self.lane_change_state == LaneChangeState.off:
      d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
      self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost)
      self.laneless_mode_status = False
      self.laneless_mode_status_buffer = False
    elif self.laneless_mode == 2 and self.laneless_mode_status_buffer == True and self.lane_change_state == LaneChangeState.off:
      d_path_xyz = self.path_xyz
      path_cost = np.clip(abs(self.path_xyz[0,1]/self.path_xyz_stds[0,1]), 0.5, 5.0) * MPC_COST_LAT.PATH
      # Heading cost is useful at low speed, otherwise end of plan can be off-heading
      heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.0])
      self.libmpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
      self.laneless_mode_status = True
    else:
      d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
      self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost)
      self.laneless_mode_status = False
      self.laneless_mode_status_buffer = False

    y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:,1])
    heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw)
    self.y_pts = y_pts

    assert len(y_pts) == LAT_MPC_N + 1
    assert len(heading_pts) == LAT_MPC_N + 1
    # for now CAR_ROTATION_RADIUS is disabled
    # to use it, enable it in the MPC
    assert abs(CAR_ROTATION_RADIUS) < 1e-3
    self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                        float(v_ego),
                        CAR_ROTATION_RADIUS,
                        list(y_pts),
                        list(heading_pts))
    # init state for next
    self.cur_state.x = 0.0
    self.cur_state.y = 0.0
    self.cur_state.psi = 0.0
    self.cur_state.curvature = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1], self.mpc_solution.curvature)

    #  Check for infeasable MPC solution
    mpc_nans = any(math.isnan(x) for x in self.mpc_solution.curvature)
    t = sec_since_boot()
    if mpc_nans:
      self.libmpc.init()
      self.cur_state.curvature = measured_curvature

      if t > self.last_cloudlog_t + 5.0:
        self.last_cloudlog_t = t
        cloudlog.warning("Lateral mpc - nan: True")

    if self.mpc_solution[0].cost > 20000. or mpc_nans:   # TODO: find a better way to detect when MPC did not converge
      self.solution_invalid_cnt += 1
    else:
      self.solution_invalid_cnt = 0

  def publish(self, sm, pm):
    plan_solution_valid = self.solution_invalid_cnt < 2
    plan_send = messaging.new_message('lateralPlan')
    plan_send.valid = sm.all_alive_and_valid(service_list=['carState', 'controlsState', 'modelV2'])
    plan_send.lateralPlan.laneWidth = float(self.LP.lane_width)
    plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts]
    plan_send.lateralPlan.psis = [float(x) for x in self.mpc_solution.psi[0:CONTROL_N]]
    plan_send.lateralPlan.curvatures = [float(x) for x in self.mpc_solution.curvature[0:CONTROL_N]]
    plan_send.lateralPlan.curvatureRates = [float(x) for x in self.mpc_solution.curvature_rate[0:CONTROL_N-1]] +[0.0]
    plan_send.lateralPlan.lProb = float(self.LP.lll_prob)
    plan_send.lateralPlan.rProb = float(self.LP.rll_prob)
    plan_send.lateralPlan.dProb = float(self.LP.d_prob)

    plan_send.lateralPlan.mpcSolutionValid = bool(plan_solution_valid)

    plan_send.lateralPlan.desire = self.desire
    plan_send.lateralPlan.laneChangeState = self.lane_change_state
    plan_send.lateralPlan.laneChangeDirection = self.lane_change_direction

    plan_send.lateralPlan.dPathWLinesX = [float(x) for x in self.d_path_w_lines_xyz[:, 0]]
    plan_send.lateralPlan.dPathWLinesY = [float(y) for y in self.d_path_w_lines_xyz[:, 1]]
    
    plan_send.lateralPlan.lanelessMode = bool(self.laneless_mode_status)

    pm.send('lateralPlan', plan_send)
Esempio n. 7
0
class LateralPlanner():
    def __init__(self, CP, use_lanelines=True, wide_camera=False):
        self.use_lanelines = use_lanelines
        self.LP = LanePlanner(wide_camera)

        self.last_cloudlog_t = 0
        self.steer_rate_cost = CP.steerRateCost

        self.setup_mpc()
        self.solution_invalid_cnt = 0

        self.use_lanelines = False
        self.laneless_mode = 0
        self.laneless_mode_status = False
        self.laneless_mode_status_buffer = False
        self.laneless_mode_at_stopping = False
        self.laneless_mode_at_stopping_timer = 0

        if int(Params().get("OpkrAutoLaneChangeDelay", encoding="utf8")) == 0:
            self.lane_change_auto_delay = 0.0
        elif int(Params().get("OpkrAutoLaneChangeDelay",
                              encoding="utf8")) == 1:
            self.lane_change_auto_delay = 0.2
        elif int(Params().get("OpkrAutoLaneChangeDelay",
                              encoding="utf8")) == 2:
            self.lane_change_auto_delay = 0.5
        elif int(Params().get("OpkrAutoLaneChangeDelay",
                              encoding="utf8")) == 3:
            self.lane_change_auto_delay = 1.0
        elif int(Params().get("OpkrAutoLaneChangeDelay",
                              encoding="utf8")) == 4:
            self.lane_change_auto_delay = 1.5
        elif int(Params().get("OpkrAutoLaneChangeDelay",
                              encoding="utf8")) == 5:
            self.lane_change_auto_delay = 2.0

        self.lane_change_wait_timer = 0.0
        self.lane_change_state = LaneChangeState.off
        self.lane_change_direction = LaneChangeDirection.none
        self.lane_change_timer = 0.0
        self.lane_change_ll_prob = 1.0
        self.prev_one_blinker = False
        self.desire = log.LateralPlan.Desire.none

        self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3))
        self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3))
        self.plan_yaw = np.zeros((TRAJECTORY_SIZE, ))
        self.t_idxs = np.arange(TRAJECTORY_SIZE)
        self.y_pts = np.zeros(TRAJECTORY_SIZE)

        self.lane_change_adjust = [0.11, 0.18, 0.75, 1.25]
        self.lane_change_adjust_vel = [8.3, 16, 22, 30]
        self.lane_change_adjust_new = 0.0

        self.standstill_elapsed_time = 0.0
        self.v_cruise_kph = 0
        self.stand_still = False
        self.steer_actuator_delay_to_send = 0.0

        self.output_scale = 0.0

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init()

        self.mpc_solution = libmpc_py.ffi.new("log_t *")
        self.cur_state = libmpc_py.ffi.new("state_t *")
        self.cur_state[0].x = 0.0
        self.cur_state[0].y = 0.0
        self.cur_state[0].psi = 0.0
        self.cur_state[0].curvature = 0.0

        self.desired_curvature = 0.0
        self.safe_desired_curvature = 0.0
        self.desired_curvature_rate = 0.0
        self.safe_desired_curvature_rate = 0.0

    def update(self, sm, CP):
        self.use_lanelines = not Params().get_bool("EndToEndToggle")
        self.laneless_mode = int(Params().get(
            "LanelessMode", encoding="utf8")) if Params().get(
                "LanelessMode", encoding="utf8") is not None else 0
        self.v_cruise_kph = sm['controlsState'].vCruise
        self.stand_still = sm['carState'].standStill
        try:
            lateral_control_method = 0
            lateral_control_method = int(
                sm['controlsState'].lateralControlMethod)
            if lateral_control_method == 0:
                self.output_scale = sm[
                    'controlsState'].lateralControlState.pidState.output
            elif lateral_control_method == 1:
                self.output_scale = sm[
                    'controlsState'].lateralControlState.indiState.output
            elif lateral_control_method == 2:
                self.output_scale = sm[
                    'controlsState'].lateralControlState.lqrState.output
        except:
            pass

        v_ego = sm['carState'].vEgo
        active = sm['controlsState'].active
        measured_curvature = sm['controlsState'].curvature

        md = sm['modelV2']
        self.LP.parse_model(sm['modelV2'], sm, v_ego)
        if len(md.position.x) == TRAJECTORY_SIZE and len(
                md.orientation.x) == TRAJECTORY_SIZE:
            self.path_xyz = np.column_stack(
                [md.position.x, md.position.y, md.position.z])
            self.t_idxs = np.array(md.position.t)
            self.plan_yaw = list(md.orientation.z)
        if len(md.orientation.xStd) == TRAJECTORY_SIZE:
            self.path_xyz_stds = np.column_stack(
                [md.position.xStd, md.position.yStd, md.position.zStd])

        # Lane change logic
        one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
        below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN

        if sm['carState'].leftBlinker:
            self.lane_change_direction = LaneChangeDirection.left
        elif sm['carState'].rightBlinker:
            self.lane_change_direction = LaneChangeDirection.right

        if (not active) or (self.lane_change_timer > LANE_CHANGE_TIME_MAX) or (
                abs(self.output_scale) >=
            (CP.steerMaxV[0] - 0.15) and self.lane_change_timer > 1):
            self.lane_change_state = LaneChangeState.off
            self.lane_change_direction = LaneChangeDirection.none
        else:
            torque_applied = sm['carState'].steeringPressed and \
                             ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                              (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))

            blindspot_detected = (
                (sm['carState'].leftBlindspot
                 and self.lane_change_direction == LaneChangeDirection.left) or
                (sm['carState'].rightBlindspot
                 and self.lane_change_direction == LaneChangeDirection.right))

            lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob

            # State transitions
            # off
            if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
                self.lane_change_state = LaneChangeState.preLaneChange
                self.lane_change_ll_prob = 1.0
                self.lane_change_wait_timer = 0

            # pre
            elif self.lane_change_state == LaneChangeState.preLaneChange:
                self.lane_change_wait_timer += DT_MDL
                if not one_blinker or below_lane_change_speed:
                    self.lane_change_state = LaneChangeState.off
                elif not blindspot_detected and (
                        torque_applied or (self.lane_change_auto_delay
                                           and self.lane_change_wait_timer >
                                           self.lane_change_auto_delay)):
                    self.lane_change_state = LaneChangeState.laneChangeStarting

            # starting
            elif self.lane_change_state == LaneChangeState.laneChangeStarting:
                # fade out over .5s
                self.lane_change_adjust_new = interp(
                    v_ego, self.lane_change_adjust_vel,
                    self.lane_change_adjust)
                self.lane_change_ll_prob = max(
                    self.lane_change_ll_prob -
                    self.lane_change_adjust_new * DT_MDL, 0.0)
                # 98% certainty
                if lane_change_prob < 0.03 and self.lane_change_ll_prob < 0.02:
                    self.lane_change_state = LaneChangeState.laneChangeFinishing

            # finishing
            elif self.lane_change_state == LaneChangeState.laneChangeFinishing:
                # fade in laneline over 1s
                self.lane_change_ll_prob = min(
                    self.lane_change_ll_prob + DT_MDL, 1.0)
                if one_blinker and self.lane_change_ll_prob > 0.98:
                    self.lane_change_state = LaneChangeState.preLaneChange
                elif self.lane_change_ll_prob > 0.98:
                    self.lane_change_state = LaneChangeState.off

        if self.lane_change_state in [
                LaneChangeState.off, LaneChangeState.preLaneChange
        ]:
            self.lane_change_timer = 0.0
        else:
            self.lane_change_timer += DT_MDL

        self.prev_one_blinker = one_blinker

        self.desire = DESIRES[self.lane_change_direction][
            self.lane_change_state]

        # Turn off lanes during lane change
        if self.desire == log.LateralPlan.Desire.laneChangeRight or self.desire == log.LateralPlan.Desire.laneChangeLeft:
            self.LP.lll_prob *= self.lane_change_ll_prob
            self.LP.rll_prob *= self.lane_change_ll_prob
        if self.use_lanelines:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                    CP.steerRateCost)
            self.laneless_mode_status = False
        elif self.laneless_mode == 0:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                    CP.steerRateCost)
            self.laneless_mode_status = False
        # use laneless, it might mitigate abrubt steering at stopping?
        elif sm['radarState'].leadOne.dRel < 25 and (
                sm['radarState'].leadOne.vRel < 0 or
            (sm['radarState'].leadOne.vRel >= 0 and v_ego < 5)
        ) and (abs(sm['controlsState'].steeringAngleDesiredDeg) -
               abs(sm['carState'].steeringAngleDeg)
               ) > 2 and self.lane_change_state == LaneChangeState.off:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                5.0) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.libmpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
            self.laneless_mode_status = True
            self.laneless_mode_at_stopping = True
            self.laneless_mode_at_stopping_timer = 80
        elif self.laneless_mode_at_stopping and (
                v_ego < 0.5 or self.laneless_mode_at_stopping_timer <= 0):
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                    CP.steerRateCost)
            self.laneless_mode_status = False
            self.laneless_mode_at_stopping = False
        elif self.laneless_mode == 1:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                5.0) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.libmpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
            self.laneless_mode_status = True
        elif self.laneless_mode == 2 and (
            (self.LP.lll_prob + self.LP.rll_prob) / 2 <
                0.3) and self.lane_change_state == LaneChangeState.off:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                5.0) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.libmpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
            self.laneless_mode_status = True
            self.laneless_mode_status_buffer = True
        elif self.laneless_mode == 2 and (
            (self.LP.lll_prob + self.LP.rll_prob) / 2 > 0.5
        ) and self.laneless_mode_status_buffer and not self.laneless_mode_at_stopping and self.lane_change_state == LaneChangeState.off:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                    CP.steerRateCost)
            self.laneless_mode_status = False
            self.laneless_mode_status_buffer = False
        elif self.laneless_mode == 2 and self.laneless_mode_status_buffer == True and self.lane_change_state == LaneChangeState.off:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                5.0) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.libmpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
            self.laneless_mode_status = True
        else:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                    CP.steerRateCost)
            self.laneless_mode_status = False
            self.laneless_mode_status_buffer = False

        if self.laneless_mode_at_stopping_timer > 0:
            self.laneless_mode_at_stopping_timer -= 1

        y_pts = np.interp(v_ego * self.t_idxs[:MPC_N + 1],
                          np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1])
        heading_pts = np.interp(v_ego * self.t_idxs[:MPC_N + 1],
                                np.linalg.norm(self.path_xyz, axis=1),
                                self.plan_yaw)
        self.y_pts = y_pts

        assert len(y_pts) == MPC_N + 1
        assert len(heading_pts) == MPC_N + 1
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            float(v_ego), CAR_ROTATION_RADIUS, list(y_pts),
                            list(heading_pts))
        # init state for next
        self.cur_state.x = 0.0
        self.cur_state.y = 0.0
        self.cur_state.psi = 0.0
        self.cur_state.curvature = interp(DT_MDL, self.t_idxs[:MPC_N + 1],
                                          self.mpc_solution.curvature)

        # TODO this needs more thought, use .2s extra for now to estimate other delays
        delay = CP.steerActuatorDelay
        #delay = CP.steerActuatorDelay + .2
        self.steer_actuator_delay_to_send = delay
        current_curvature = self.mpc_solution.curvature[0]
        psi = interp(delay, self.t_idxs[:MPC_N + 1], self.mpc_solution.psi)
        next_curvature_rate = self.mpc_solution.curvature_rate[0]

        # MPC can plan to turn the wheel and turn back before t_delay. This means
        # in high delay cases some corrections never even get commanded. So just use
        # psi to calculate a simple linearization of desired curvature
        curvature_diff_from_psi = psi / (max(v_ego, 1e-1) *
                                         delay) - current_curvature
        next_curvature = current_curvature + 2 * curvature_diff_from_psi

        self.desired_curvature = next_curvature
        self.desired_curvature_rate = next_curvature_rate
        max_curvature_rate = interp(v_ego, MAX_CURVATURE_RATE_SPEEDS,
                                    MAX_CURVATURE_RATES)
        self.safe_desired_curvature_rate = clip(self.desired_curvature_rate,
                                                -max_curvature_rate,
                                                max_curvature_rate)
        self.safe_desired_curvature = clip(
            self.desired_curvature,
            self.safe_desired_curvature - max_curvature_rate / DT_MDL,
            self.safe_desired_curvature + max_curvature_rate / DT_MDL)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution.curvature)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init()
            self.cur_state.curvature = measured_curvature

            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning("Lateral mpc - nan: True")

        if self.mpc_solution[
                0].cost > 20000. or mpc_nans:  # TODO: find a better way to detect when MPC did not converge
            self.solution_invalid_cnt += 1
        else:
            self.solution_invalid_cnt = 0

    def publish(self, sm, pm):
        plan_solution_valid = self.solution_invalid_cnt < 3
        plan_send = messaging.new_message('lateralPlan')
        plan_send.valid = sm.all_alive_and_valid(
            service_list=['carState', 'controlsState', 'modelV2'])
        plan_send.lateralPlan.laneWidth = float(self.LP.lane_width)
        plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts]
        plan_send.lateralPlan.lProb = float(self.LP.lll_prob)
        plan_send.lateralPlan.rProb = float(self.LP.rll_prob)
        plan_send.lateralPlan.dProb = float(self.LP.d_prob)

        plan_send.lateralPlan.rawCurvature = float(self.desired_curvature)
        plan_send.lateralPlan.rawCurvatureRate = float(
            self.desired_curvature_rate)
        plan_send.lateralPlan.curvature = float(self.safe_desired_curvature)
        plan_send.lateralPlan.curvatureRate = float(
            self.safe_desired_curvature_rate)

        plan_send.lateralPlan.mpcSolutionValid = bool(plan_solution_valid)

        plan_send.lateralPlan.desire = self.desire
        plan_send.lateralPlan.laneChangeState = self.lane_change_state
        plan_send.lateralPlan.laneChangeDirection = self.lane_change_direction

        plan_send.lateralPlan.steerRateCost = float(self.steer_rate_cost)
        plan_send.lateralPlan.outputScale = float(self.output_scale)
        plan_send.lateralPlan.vCruiseSet = float(self.v_cruise_kph)
        plan_send.lateralPlan.vCurvature = float(sm['controlsState'].curvature)
        plan_send.lateralPlan.steerAngleDesireDeg = float(
            sm['controlsState'].steeringAngleDesiredDeg)
        plan_send.lateralPlan.lanelessMode = bool(self.laneless_mode_status)
        plan_send.lateralPlan.steerActuatorDelay = float(
            self.steer_actuator_delay_to_send)

        if self.stand_still:
            self.standstill_elapsed_time += DT_MDL
        else:
            self.standstill_elapsed_time = 0.0
        plan_send.lateralPlan.standstillElapsedTime = int(
            self.standstill_elapsed_time)

        pm.send('lateralPlan', plan_send)

        if LOG_MPC:
            dat = messaging.new_message('liveMpc')
            dat.liveMpc.x = list(self.mpc_solution.x)
            dat.liveMpc.y = list(self.mpc_solution.y)
            dat.liveMpc.psi = list(self.mpc_solution.psi)
            dat.liveMpc.curvature = list(self.mpc_solution.curvature)
            dat.liveMpc.cost = self.mpc_solution.cost
            pm.send('liveMpc', dat)
Esempio n. 8
0
class LateralPlanner:
    def __init__(self, use_lanelines=True, wide_camera=False):
        self.use_lanelines = use_lanelines
        self.LP = LanePlanner(wide_camera)
        self.DH = DesireHelper()

        self.last_cloudlog_t = 0
        self.solution_invalid_cnt = 0

        self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3))
        self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3))
        self.plan_yaw = np.zeros((TRAJECTORY_SIZE, ))
        self.t_idxs = np.arange(TRAJECTORY_SIZE)
        self.y_pts = np.zeros(TRAJECTORY_SIZE)

        self.lat_mpc = LateralMpc()
        self.reset_mpc(np.zeros(4))

    def reset_mpc(self, x0=np.zeros(4)):
        self.x0 = x0
        self.lat_mpc.reset(x0=self.x0)

    def update(self, sm):
        v_ego = sm['carState'].vEgo
        measured_curvature = sm['controlsState'].curvature

        # Parse model predictions
        md = sm['modelV2']
        self.LP.parse_model(md)
        if len(md.position.x) == TRAJECTORY_SIZE and len(
                md.orientation.x) == TRAJECTORY_SIZE:
            self.path_xyz = np.column_stack(
                [md.position.x, md.position.y, md.position.z])
            self.t_idxs = np.array(md.position.t)
            self.plan_yaw = list(md.orientation.z)
        if len(md.position.xStd) == TRAJECTORY_SIZE:
            self.path_xyz_stds = np.column_stack(
                [md.position.xStd, md.position.yStd, md.position.zStd])

        # Lane change logic
        lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob
        self.DH.update(sm['carState'], sm['controlsState'].active,
                       lane_change_prob)

        # Turn off lanes during lane change
        if self.DH.desire == log.LateralPlan.Desire.laneChangeRight or self.DH.desire == log.LateralPlan.Desire.laneChangeLeft:
            self.LP.lll_prob *= self.DH.lane_change_ll_prob
            self.LP.rll_prob *= self.DH.lane_change_ll_prob

        # Calculate final driving path and set MPC costs
        if self.use_lanelines:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                     MPC_COST_LAT.STEER_RATE)
        else:
            d_path_xyz = self.path_xyz
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.15])
            self.lat_mpc.set_weights(MPC_COST_LAT.PATH, heading_cost,
                                     MPC_COST_LAT.STEER_RATE)

        y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1],
                          np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1])
        heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1],
                                np.linalg.norm(self.path_xyz, axis=1),
                                self.plan_yaw)
        self.y_pts = y_pts

        assert len(y_pts) == LAT_MPC_N + 1
        assert len(heading_pts) == LAT_MPC_N + 1
        # self.x0[4] = v_ego
        p = np.array([v_ego, CAR_ROTATION_RADIUS])
        self.lat_mpc.run(self.x0, p, y_pts, heading_pts)
        # init state for next
        # mpc.u_sol is the desired curvature rate given x0 curv state.
        # with x0[3] = measured_curvature, this would be the actual desired rate.
        # instead, interpolate x_sol so that x0[3] is the desired curvature for lat_control.
        self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1],
                            self.lat_mpc.x_sol[:, 3])

        #  Check for infeasible MPC solution
        mpc_nans = np.isnan(self.lat_mpc.x_sol[:, 3]).any()
        t = sec_since_boot()
        if mpc_nans or self.lat_mpc.solution_status != 0:
            self.reset_mpc()
            self.x0[3] = measured_curvature
            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning("Lateral mpc - nan: True")

        if self.lat_mpc.cost > 20000. or mpc_nans:
            self.solution_invalid_cnt += 1
        else:
            self.solution_invalid_cnt = 0

    def publish(self, sm, pm):
        plan_solution_valid = self.solution_invalid_cnt < 2
        plan_send = messaging.new_message('lateralPlan')
        plan_send.valid = sm.all_checks(
            service_list=['carState', 'controlsState', 'modelV2'])

        lateralPlan = plan_send.lateralPlan
        lateralPlan.modelMonoTime = sm.logMonoTime['modelV2']
        lateralPlan.laneWidth = float(self.LP.lane_width)
        lateralPlan.dPathPoints = self.y_pts.tolist()
        lateralPlan.psis = self.lat_mpc.x_sol[0:CONTROL_N, 2].tolist()
        lateralPlan.curvatures = self.lat_mpc.x_sol[0:CONTROL_N, 3].tolist()
        lateralPlan.curvatureRates = [
            float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]
        ] + [0.0]
        lateralPlan.lProb = float(self.LP.lll_prob)
        lateralPlan.rProb = float(self.LP.rll_prob)
        lateralPlan.dProb = float(self.LP.d_prob)

        lateralPlan.mpcSolutionValid = bool(plan_solution_valid)
        lateralPlan.solverExecutionTime = self.lat_mpc.solve_time

        lateralPlan.desire = self.DH.desire
        lateralPlan.useLaneLines = self.use_lanelines
        lateralPlan.laneChangeState = self.DH.lane_change_state
        lateralPlan.laneChangeDirection = self.DH.lane_change_direction

        pm.send('lateralPlan', plan_send)
class LateralPlanner():
    def __init__(self, CP, use_lanelines=True, wide_camera=False):
        self.use_lanelines = use_lanelines
        self.LP = LanePlanner(wide_camera)

        self.last_cloudlog_t = 0
        self.steer_rate_cost = CP.steerRateCost

        self.solution_invalid_cnt = 0
        self.lane_change_state = LaneChangeState.off
        self.lane_change_direction = LaneChangeDirection.none
        self.lane_change_timer = 0.0
        self.lane_change_ll_prob = 1.0
        self.keep_pulse_timer = 0.0
        self.prev_one_blinker = False
        self.desire = log.LateralPlan.Desire.none

        self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3))
        self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3))
        self.plan_yaw = np.zeros((TRAJECTORY_SIZE, ))
        self.t_idxs = np.arange(TRAJECTORY_SIZE)
        self.y_pts = np.zeros(TRAJECTORY_SIZE)
        self.d_path_w_lines_xyz = np.zeros((TRAJECTORY_SIZE, 3))

        self.lat_mpc = LateralMpc()
        self.reset_mpc(np.zeros(6))

        self.d_path_w_lines_xyz = np.zeros((TRAJECTORY_SIZE, 3))
        # dp
        self.dp_torque_apply_length = 1.5  # secs of torque we apply for
        self.dp_lc_auto_start = 0.  # time to start alc
        self.dp_lc_auto_start_in = 0.  # remaining time to start alc
        self.dp_lc_auto_torque_end = 0.  # time to end applying torque
        self.dp_torque_apply = False  # should we apply torque?

        self.laneless_mode = 2  # AUTO
        self.laneless_mode_status = False
        self.laneless_mode_status_buffer = False

    def reset_mpc(self, x0=np.zeros(6)):
        self.x0 = x0
        self.lat_mpc.reset(x0=self.x0)
        self.desired_curvature = 0.0
        self.safe_desired_curvature = 0.0
        self.desired_curvature_rate = 0.0
        self.safe_desired_curvature_rate = 0.0

    def update(self, sm, CP):
        self.use_lanelines = not sm['dragonConf'].dpLaneLessModeCtrl
        self.laneless_mode = sm['dragonConf'].dpLaneLessMode
        v_ego = sm['carState'].vEgo
        active = sm['controlsState'].active
        measured_curvature = sm['controlsState'].curvature
        self.LP.update_dp_set_offsets(sm['dragonConf'].dpCameraOffset,
                                      sm['dragonConf'].dpPathOffset)

        md = sm['modelV2']
        self.LP.parse_model(sm['modelV2'])
        if len(md.position.x) == TRAJECTORY_SIZE and len(
                md.orientation.x) == TRAJECTORY_SIZE:
            self.path_xyz = np.column_stack(
                [md.position.x, md.position.y, md.position.z])
            self.t_idxs = np.array(md.position.t)
            self.plan_yaw = list(md.orientation.z)
        if len(md.position.xStd) == TRAJECTORY_SIZE:
            self.path_xyz_stds = np.column_stack(
                [md.position.xStd, md.position.yStd, md.position.zStd])

        # Lane change logic
        one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
        below_lane_change_speed = v_ego < (sm['dragonConf'].dpLcMinMph *
                                           CV.MPH_TO_MS)

        if (not active) or (self.lane_change_timer > LANE_CHANGE_TIME_MAX):
            self.lane_change_state = LaneChangeState.off
            self.lane_change_direction = LaneChangeDirection.none
        else:
            reset = False
            if one_blinker:
                cur_time = sec_since_boot()
                # reach auto lc condition
                if not below_lane_change_speed and sm[
                        'dragonConf'].dpLateralMode == 2 and v_ego >= (
                            sm['dragonConf'].dpLcAutoMinMph * CV.MPH_TO_MS):
                    # work out alc start time and torque apply end time
                    if self.dp_lc_auto_start == 0.:
                        self.dp_lc_auto_start = cur_time + sm[
                            'dragonConf'].dpLcAutoDelay
                        self.dp_lc_auto_torque_end = self.dp_lc_auto_start + self.dp_torque_apply_length
                    else:
                        # work out how long til alc start
                        # for display only
                        self.dp_lc_auto_start_in = self.dp_lc_auto_start - cur_time
                        self.dp_torque_apply = True if self.dp_lc_auto_start < cur_time <= self.dp_lc_auto_torque_end else False
            else:
                reset = True

            # reset all vals
            if not active or reset:
                self.dp_lc_auto_start = 0.
                self.dp_lc_auto_start_in = 0.
                self.dp_lc_auto_torque_end = 0.
                self.dp_torque_apply = False

            # LaneChangeState.off
            if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
                self.lane_change_state = LaneChangeState.preLaneChange
                self.lane_change_ll_prob = 1.0

            # LaneChangeState.preLaneChange
            elif self.lane_change_state == LaneChangeState.preLaneChange:
                # Set lane change direction
                if sm['carState'].leftBlinker:
                    self.lane_change_direction = LaneChangeDirection.left
                elif sm['carState'].rightBlinker:
                    self.lane_change_direction = LaneChangeDirection.right
                else:  # If there are no blinkers we will go back to LaneChangeState.off
                    self.lane_change_direction = LaneChangeDirection.none

                torque_applied = sm['carState'].steeringPressed and \
                                ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                                  (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))

                blindspot_detected = (
                    (sm['carState'].leftBlindspot and
                     self.lane_change_direction == LaneChangeDirection.left) or
                    (sm['carState'].rightBlindspot and
                     self.lane_change_direction == LaneChangeDirection.right))

                # if human made lane change prior alca, we should stop alca until new blinker (off -> on)
                self.dp_lc_auto_start = self.dp_lc_auto_torque_end if torque_applied else self.dp_lc_auto_start
                torque_applied = self.dp_torque_apply if self.dp_torque_apply else torque_applied
                if not one_blinker or below_lane_change_speed:
                    self.lane_change_state = LaneChangeState.off
                elif torque_applied and not blindspot_detected:
                    self.lane_change_state = LaneChangeState.laneChangeStarting

            # LaneChangeState.laneChangeStarting
            elif self.lane_change_state == LaneChangeState.laneChangeStarting:
                # fade out over .5s
                self.lane_change_ll_prob = max(
                    self.lane_change_ll_prob - 2 * DT_MDL, 0.0)

                # 98% certainty
                lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob
                if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
                    self.lane_change_state = LaneChangeState.laneChangeFinishing

            # LaneChangeState.laneChangeFinishing
            elif self.lane_change_state == LaneChangeState.laneChangeFinishing:
                # fade in laneline over 1s
                self.lane_change_ll_prob = min(
                    self.lane_change_ll_prob + DT_MDL, 1.0)
                if one_blinker and self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.preLaneChange
                elif self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.off

        if self.lane_change_state in [
                LaneChangeState.off, LaneChangeState.preLaneChange
        ]:
            self.lane_change_timer = 0.0
        else:
            self.lane_change_timer += DT_MDL

        self.prev_one_blinker = one_blinker

        self.desire = DESIRES[self.lane_change_direction][
            self.lane_change_state]

        # Send keep pulse once per second during LaneChangeStart.preLaneChange
        if self.lane_change_state in [
                LaneChangeState.off, LaneChangeState.laneChangeStarting
        ]:
            self.keep_pulse_timer = 0.0
        elif self.lane_change_state == LaneChangeState.preLaneChange:
            self.keep_pulse_timer += DT_MDL
            if self.keep_pulse_timer > 1.0:
                self.keep_pulse_timer = 0.0
            elif self.desire in [
                    log.LateralPlan.Desire.keepLeft,
                    log.LateralPlan.Desire.keepRight
            ]:
                self.desire = log.LateralPlan.Desire.none

        # Turn off lanes during lane change
        if self.desire == log.LateralPlan.Desire.laneChangeRight or self.desire == log.LateralPlan.Desire.laneChangeLeft:
            self.LP.lll_prob *= self.lane_change_ll_prob
            self.LP.rll_prob *= self.lane_change_ll_prob
        self.d_path_w_lines_xyz = self.LP.get_d_path(v_ego, self.t_idxs,
                                                     self.path_xyz)
        if self.use_lanelines:
            d_path_xyz = self.d_path_w_lines_xyz
            self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                     CP.steerRateCost)
            self.laneless_mode_status = False
        elif self.laneless_mode == 0:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                     CP.steerRateCost)
            self.laneless_mode_status = False
        elif self.laneless_mode == 1:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                5.0) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.lat_mpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
            self.laneless_mode_status = True
        elif self.laneless_mode == 2 and (
            (self.LP.lll_prob + self.LP.rll_prob) / 2 <
                0.3) and self.lane_change_state == LaneChangeState.off:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                5.0) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.lat_mpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
            self.laneless_mode_status = True
            self.laneless_mode_status_buffer = True
        elif self.laneless_mode == 2 and ((self.LP.lll_prob + self.LP.rll_prob)/2 > 0.5) and \
         self.laneless_mode_status_buffer and self.lane_change_state == LaneChangeState.off:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                     CP.steerRateCost)
            self.laneless_mode_status = False
            self.laneless_mode_status_buffer = False
        elif self.laneless_mode == 2 and self.laneless_mode_status_buffer == True and self.lane_change_state == LaneChangeState.off:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                5.0) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.lat_mpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
            self.laneless_mode_status = True
        else:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                     CP.steerRateCost)
            self.laneless_mode_status = False
            self.laneless_mode_status_buffer = False

        y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1],
                          np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1])
        heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1],
                                np.linalg.norm(self.path_xyz, axis=1),
                                self.plan_yaw)
        self.y_pts = y_pts

        assert len(y_pts) == LAT_MPC_N + 1
        assert len(heading_pts) == LAT_MPC_N + 1
        self.x0[4] = v_ego
        self.lat_mpc.run(self.x0, v_ego, CAR_ROTATION_RADIUS, y_pts,
                         heading_pts)
        # init state for next
        self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1],
                            self.lat_mpc.x_sol[:, 3])

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.lat_mpc.x_sol[:, 3])
        t = sec_since_boot()
        if mpc_nans or self.lat_mpc.solution_status != 0:
            self.reset_mpc()
            self.x0[3] = measured_curvature
            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning("Lateral mpc - nan: True")

        if self.lat_mpc.cost > 20000. or mpc_nans:
            self.solution_invalid_cnt += 1
        else:
            self.solution_invalid_cnt = 0

    def publish(self, sm, pm):
        plan_solution_valid = self.solution_invalid_cnt < 2
        plan_send = messaging.new_message('lateralPlan')
        plan_send.valid = sm.all_alive_and_valid(service_list=[
            'carState', 'controlsState', 'modelV2', 'dragonConf'
        ])
        plan_send.lateralPlan.laneWidth = float(self.LP.lane_width)
        plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts]
        plan_send.lateralPlan.psis = [
            float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 2]
        ]
        plan_send.lateralPlan.curvatures = [
            float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 3]
        ]
        plan_send.lateralPlan.curvatureRates = [
            float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]
        ] + [0.0]
        plan_send.lateralPlan.lProb = float(self.LP.lll_prob)
        plan_send.lateralPlan.rProb = float(self.LP.rll_prob)
        plan_send.lateralPlan.dProb = float(self.LP.d_prob)

        plan_send.lateralPlan.mpcSolutionValid = bool(plan_solution_valid)

        plan_send.lateralPlan.desire = self.desire
        plan_send.lateralPlan.useLaneLines = self.use_lanelines
        plan_send.lateralPlan.laneChangeState = self.lane_change_state
        plan_send.lateralPlan.laneChangeDirection = self.lane_change_direction
        # dp
        plan_send.lateralPlan.dpLaneLessModeStatus = bool(
            self.laneless_mode_status)
        plan_send.lateralPlan.dpALCAStartIn = self.dp_lc_auto_start_in

        plan_send.lateralPlan.dPathWLinesX = [
            float(x) for x in self.d_path_w_lines_xyz[:, 0]
        ]
        plan_send.lateralPlan.dPathWLinesY = [
            float(y) for y in self.d_path_w_lines_xyz[:, 1]
        ]

        pm.send('lateralPlan', plan_send)
Esempio n. 10
0
class LateralPlanner:
  def __init__(self, CP, use_lanelines=True, wide_camera=False):
    self.use_lanelines = use_lanelines
    self.wide_camera = wide_camera
    self.LP = LanePlanner(wide_camera)

    self.last_cloudlog_t = 0

    self.solution_invalid_cnt = 0

    self.lane_change_enabled = Params().get_bool('LaneChangeEnabled')
    self.auto_lane_change_enabled = Params().get_bool('AutoLaneChangeEnabled')
    self.auto_lane_change_timer = 0.0
    self.prev_torque_applied = False

    self.lane_change_state = LaneChangeState.off
    self.lane_change_direction = LaneChangeDirection.none
    self.lane_change_timer = 0.0
    self.lane_change_ll_prob = 1.0
    self.keep_pulse_timer = 0.0
    self.prev_one_blinker = False
    self.desire = log.LateralPlan.Desire.none

    self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3))
    self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3))
    self.plan_yaw = np.zeros((TRAJECTORY_SIZE,))
    self.t_idxs = np.arange(TRAJECTORY_SIZE)
    self.y_pts = np.zeros(TRAJECTORY_SIZE)

    self.lat_mpc = LateralMpc()
    self.reset_mpc(np.zeros(4))

  def reset_mpc(self, x0=np.zeros(4)):
    self.x0 = x0
    self.lat_mpc.reset(x0=self.x0)

  def update(self, sm):
    v_ego = sm['carState'].vEgo
    active = sm['controlsState'].active
    measured_curvature = sm['controlsState'].curvature

    md = sm['modelV2']
    self.LP.parse_model(sm['modelV2'])
    if len(md.position.x) == TRAJECTORY_SIZE and len(md.orientation.x) == TRAJECTORY_SIZE:
      self.path_xyz = np.column_stack([md.position.x, md.position.y, md.position.z])
      self.t_idxs = np.array(md.position.t)
      self.plan_yaw = list(md.orientation.z)
    if len(md.position.xStd) == TRAJECTORY_SIZE:
      self.path_xyz_stds = np.column_stack([md.position.xStd, md.position.yStd, md.position.zStd])

    # Lane change logic
    one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
    below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN

    if (not active) or (self.lane_change_timer > LANE_CHANGE_TIME_MAX) or (not one_blinker) or (not self.lane_change_enabled):
      self.lane_change_state = LaneChangeState.off
      self.lane_change_direction = LaneChangeDirection.none
    else:
      torque_applied = sm['carState'].steeringPressed and \
                       ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                        (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right)) or \
                        self.auto_lane_change_enabled and \
                       (AUTO_LCA_START_TIME+0.25) > self.auto_lane_change_timer > AUTO_LCA_START_TIME

      blindspot_detected = ((sm['carState'].leftBlindspot and self.lane_change_direction == LaneChangeDirection.left) or
                            (sm['carState'].rightBlindspot and self.lane_change_direction == LaneChangeDirection.right))

      lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob

      # State transitions
      # off
      if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
        if sm['carState'].leftBlinker:
          self.lane_change_direction = LaneChangeDirection.left
        elif sm['carState'].rightBlinker:
          self.lane_change_direction = LaneChangeDirection.right

        self.lane_change_state = LaneChangeState.preLaneChange
        self.lane_change_ll_prob = 1.0

      # pre
      elif self.lane_change_state == LaneChangeState.preLaneChange:
        if not one_blinker or below_lane_change_speed:
          self.lane_change_state = LaneChangeState.off
        elif torque_applied and (not blindspot_detected or self.prev_torque_applied):
          self.lane_change_state = LaneChangeState.laneChangeStarting
        elif torque_applied and blindspot_detected and self.auto_lane_change_timer != 10.0:
          self.auto_lane_change_timer = 10.0
        elif not torque_applied and self.auto_lane_change_timer == 10.0 and not self.prev_torque_applied:
          self.prev_torque_applied = True

      # LaneChangeState.laneChangeStarting
      elif self.lane_change_state == LaneChangeState.laneChangeStarting:
        # fade out over .5s
        self.lane_change_ll_prob = max(self.lane_change_ll_prob - 2*DT_MDL, 0.0)
        # 98% certainty
        if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
          self.lane_change_state = LaneChangeState.laneChangeFinishing

      # LaneChangeState.laneChangeFinishing
      elif self.lane_change_state == LaneChangeState.laneChangeFinishing:
        # fade in laneline over 1s
        self.lane_change_ll_prob = min(self.lane_change_ll_prob + DT_MDL, 1.0)
        if self.lane_change_ll_prob > 0.99:
          self.lane_change_direction = LaneChangeDirection.none
          if one_blinker:
            self.lane_change_state = LaneChangeState.preLaneChange
          else:
            self.lane_change_state = LaneChangeState.off

    if self.lane_change_state in (LaneChangeState.off, LaneChangeState.preLaneChange):
      self.lane_change_timer = 0.0
    else:
      self.lane_change_timer += DT_MDL

    if self.lane_change_state == LaneChangeState.off:
      self.auto_lane_change_timer = 0.0
      self.prev_torque_applied = False
    elif self.auto_lane_change_timer < (AUTO_LCA_START_TIME+0.25): # stop afer 3 sec resume from 10 when torque applied
      self.auto_lane_change_timer += DT_MDL

    self.prev_one_blinker = one_blinker

    self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]

    # Send keep pulse once per second during LaneChangeStart.preLaneChange
    if self.lane_change_state in (LaneChangeState.off, LaneChangeState.laneChangeStarting):
      self.keep_pulse_timer = 0.0
    elif self.lane_change_state == LaneChangeState.preLaneChange:
      self.keep_pulse_timer += DT_MDL
      if self.keep_pulse_timer > 1.0:
        self.keep_pulse_timer = 0.0
      elif self.desire in (log.LateralPlan.Desire.keepLeft, log.LateralPlan.Desire.keepRight):
        self.desire = log.LateralPlan.Desire.none

    # Turn off lanes during lane change
    if self.desire == log.LateralPlan.Desire.laneChangeRight or self.desire == log.LateralPlan.Desire.laneChangeLeft:
      self.LP.lll_prob *= self.lane_change_ll_prob
      self.LP.rll_prob *= self.lane_change_ll_prob
    if self.use_lanelines:
      d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
      self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, ntune_common_get('steerRateCost'))
    else:
      d_path_xyz = self.path_xyz
      path_cost = np.clip(abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5, 1.5) * MPC_COST_LAT.PATH
      # Heading cost is useful at low speed, otherwise end of plan can be off-heading
      heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.0])
      self.lat_mpc.set_weights(path_cost, heading_cost, ntune_common_get('steerRateCost'))

    y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:,1])
    heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw)
    self.y_pts = y_pts

    assert len(y_pts) == LAT_MPC_N + 1
    assert len(heading_pts) == LAT_MPC_N + 1
    # self.x0[4] = v_ego
    p = np.array([v_ego, CAR_ROTATION_RADIUS])
    self.lat_mpc.run(self.x0,
                     p,
                     y_pts,
                     heading_pts)
    # init state for next
    self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1], self.lat_mpc.x_sol[:, 3])

    #  Check for infeasible MPC solution
    mpc_nans = np.isnan(self.lat_mpc.x_sol[:, 3]).any()
    t = sec_since_boot()
    if mpc_nans or self.lat_mpc.solution_status != 0:
      self.reset_mpc()
      self.x0[3] = measured_curvature
      if t > self.last_cloudlog_t + 5.0:
        self.last_cloudlog_t = t
        cloudlog.warning("Lateral mpc - nan: True")

    if self.lat_mpc.cost > 20000. or mpc_nans:
      self.solution_invalid_cnt += 1
    else:
      self.solution_invalid_cnt = 0

  def publish(self, sm, pm):
    plan_solution_valid = self.solution_invalid_cnt < 2
    plan_send = messaging.new_message('lateralPlan')
    plan_send.valid = sm.all_alive_and_valid(service_list=['carState', 'controlsState', 'modelV2'])

    lateralPlan = plan_send.lateralPlan
    lateralPlan.laneWidth = float(self.LP.lane_width)
    lateralPlan.dPathPoints = self.y_pts.tolist()
    lateralPlan.psis = self.lat_mpc.x_sol[0:CONTROL_N, 2].tolist()
    lateralPlan.curvatures = self.lat_mpc.x_sol[0:CONTROL_N, 3].tolist()
    lateralPlan.curvatureRates = [float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0]
    lateralPlan.lProb = float(self.LP.lll_prob)
    lateralPlan.rProb = float(self.LP.rll_prob)
    lateralPlan.dProb = float(self.LP.d_prob)

    lateralPlan.mpcSolutionValid = bool(plan_solution_valid)
    lateralPlan.solverExecutionTime = self.lat_mpc.solve_time

    lateralPlan.desire = self.desire
    lateralPlan.useLaneLines = self.use_lanelines
    lateralPlan.laneChangeState = self.lane_change_state
    lateralPlan.laneChangeDirection = self.lane_change_direction
    lateralPlan.autoLaneChangeEnabled = self.auto_lane_change_enabled
    lateralPlan.autoLaneChangeTimer = int(AUTO_LCA_START_TIME) - int(self.auto_lane_change_timer)

    pm.send('lateralPlan', plan_send)
Esempio n. 11
0
class LateralPlanner():
    def __init__(self, CP, use_lanelines=True, wide_camera=False):
        self.use_lanelines = use_lanelines
        self.LP = LanePlanner(wide_camera)

        self.last_cloudlog_t = 0
        self.steer_rate_cost = CP.steerRateCost

        self.setup_mpc()
        self.solution_invalid_cnt = 0

        self.laneless_mode = int(Params().get("LanelessMode", encoding="utf8"))
        self.laneless_mode_status = False
        self.laneless_mode_status_buffer = False

        self.lane_change_delay = int(Params().get("OpkrAutoLaneChangeDelay",
                                                  encoding="utf8"))
        self.lane_change_auto_delay = 0.0 if self.lane_change_delay == 0 else 0.2 if self.lane_change_delay == 1 else 0.5 if self.lane_change_delay == 2 \
         else 1.0 if self.lane_change_delay == 3 else 1.5 if self.lane_change_delay == 4 else 2.0

        self.lane_change_wait_timer = 0.0
        self.lane_change_state = LaneChangeState.off
        self.lane_change_direction = LaneChangeDirection.none
        self.lane_change_timer = 0.0
        self.lane_change_ll_prob = 1.0
        self.prev_one_blinker = False
        self.desire = log.LateralPlan.Desire.none

        self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3))
        self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3))
        self.plan_yaw = np.zeros((TRAJECTORY_SIZE, ))
        self.t_idxs = np.arange(TRAJECTORY_SIZE)
        self.y_pts = np.zeros(TRAJECTORY_SIZE)

        self.lane_change_adjust = [
            float(
                Decimal(Params().get("LCTimingFactor30", encoding="utf8")) *
                Decimal('0.01')),
            float(
                Decimal(Params().get("LCTimingFactor60", encoding="utf8")) *
                Decimal('0.01')),
            float(
                Decimal(Params().get("LCTimingFactor80", encoding="utf8")) *
                Decimal('0.01')),
            float(
                Decimal(Params().get("LCTimingFactor110", encoding="utf8")) *
                Decimal('0.01'))
        ]
        self.lane_change_adjust_vel = [
            30 * CV.KPH_TO_MS, 60 * CV.KPH_TO_MS, 80 * CV.KPH_TO_MS,
            110 * CV.KPH_TO_MS
        ]
        self.lane_change_adjust_new = 2
        self.lane_change_adjust_enable = Params().get_bool(
            "LCTimingFactorEnable")

        self.standstill_elapsed_time = 0.0
        self.v_cruise_kph = 0
        self.stand_still = False

        self.output_scale = 0.0
        self.second = 0.0

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init()

        self.mpc_solution = libmpc_py.ffi.new("log_t *")
        self.cur_state = libmpc_py.ffi.new("state_t *")
        self.cur_state[0].x = 0.0
        self.cur_state[0].y = 0.0
        self.cur_state[0].psi = 0.0
        self.cur_state[0].curvature = 0.0

        self.desired_curvature = 0.0
        self.safe_desired_curvature = 0.0
        self.desired_curvature_rate = 0.0
        self.safe_desired_curvature_rate = 0.0

    def update(self, sm, CP):
        self.second += DT_MDL
        if self.second > 1.0:
            self.use_lanelines = not Params().get_bool("EndToEndToggle")
            self.laneless_mode = int(Params().get("LanelessMode",
                                                  encoding="utf8"))
            self.second = 0.0
        self.v_cruise_kph = sm['controlsState'].vCruise
        self.stand_still = sm['carState'].standStill
        try:
            if CP.lateralTuning.which() == 'pid':
                self.output_scale = sm[
                    'controlsState'].lateralControlState.pidState.output
            elif CP.lateralTuning.which() == 'indi':
                self.output_scale = sm[
                    'controlsState'].lateralControlState.indiState.output
            elif CP.lateralTuning.which() == 'lqr':
                self.output_scale = sm[
                    'controlsState'].lateralControlState.lqrState.output
        except:
            pass

        v_ego = sm['carState'].vEgo
        active = sm['controlsState'].active
        measured_curvature = sm['controlsState'].curvature

        md = sm['modelV2']
        self.LP.parse_model(sm['modelV2'], sm, v_ego)
        if len(md.position.x) == TRAJECTORY_SIZE and len(
                md.orientation.x) == TRAJECTORY_SIZE:
            self.path_xyz = np.column_stack(
                [md.position.x, md.position.y, md.position.z])
            self.t_idxs = np.array(md.position.t)
            self.plan_yaw = list(md.orientation.z)
        if len(md.orientation.xStd) == TRAJECTORY_SIZE:
            self.path_xyz_stds = np.column_stack(
                [md.position.xStd, md.position.yStd, md.position.zStd])

        # Lane change logic
        one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
        below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN

        if sm['carState'].leftBlinker:
            self.lane_change_direction = LaneChangeDirection.left
        elif sm['carState'].rightBlinker:
            self.lane_change_direction = LaneChangeDirection.right

        if (not active) or (self.lane_change_timer > LANE_CHANGE_TIME_MAX) or (
                abs(self.output_scale) >=
            (CP.steerMaxV[0] - 0.15) and self.lane_change_timer > 1):
            self.lane_change_state = LaneChangeState.off
            self.lane_change_direction = LaneChangeDirection.none
        else:
            torque_applied = sm['carState'].steeringPressed and \
                             ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                              (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))

            blindspot_detected = (
                (sm['carState'].leftBlindspot
                 and self.lane_change_direction == LaneChangeDirection.left) or
                (sm['carState'].rightBlindspot
                 and self.lane_change_direction == LaneChangeDirection.right))

            # LaneChangeState.off
            if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
                self.lane_change_state = LaneChangeState.preLaneChange
                self.lane_change_ll_prob = 1.0
                self.lane_change_wait_timer = 0
                if self.lane_change_adjust_enable:
                    self.lane_change_adjust_new = interp(
                        v_ego, self.lane_change_adjust_vel,
                        self.lane_change_adjust)

            # LaneChangeState.preLaneChange
            elif self.lane_change_state == LaneChangeState.preLaneChange:
                self.lane_change_wait_timer += DT_MDL
                if not one_blinker or below_lane_change_speed:
                    self.lane_change_state = LaneChangeState.off
                elif not blindspot_detected and (
                        torque_applied or (self.lane_change_auto_delay
                                           and self.lane_change_wait_timer >
                                           self.lane_change_auto_delay)):
                    self.lane_change_state = LaneChangeState.laneChangeStarting

            # LaneChangeState.laneChangeStarting
            elif self.lane_change_state == LaneChangeState.laneChangeStarting:
                # fade out over .5s
                self.lane_change_ll_prob = max(
                    self.lane_change_ll_prob -
                    self.lane_change_adjust_new * DT_MDL, 0.0)

                # 98% certainty
                lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob
                if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
                    self.lane_change_state = LaneChangeState.laneChangeFinishing

            # LaneChangeState.laneChangeFinishing
            elif self.lane_change_state == LaneChangeState.laneChangeFinishing:
                # fade in laneline over 1s
                self.lane_change_ll_prob = min(
                    self.lane_change_ll_prob + DT_MDL, 1.0)
                if one_blinker and self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.preLaneChange
                elif self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.off

        if self.lane_change_state in [
                LaneChangeState.off, LaneChangeState.preLaneChange
        ]:
            self.lane_change_timer = 0.0
        else:
            self.lane_change_timer += DT_MDL

        self.prev_one_blinker = one_blinker

        self.desire = DESIRES[self.lane_change_direction][
            self.lane_change_state]

        # Turn off lanes during lane change
        if self.desire == log.LateralPlan.Desire.laneChangeRight or self.desire == log.LateralPlan.Desire.laneChangeLeft:
            self.LP.lll_prob *= self.lane_change_ll_prob
            self.LP.rll_prob *= self.lane_change_ll_prob
        if self.use_lanelines:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                    CP.steerRateCost)
            self.laneless_mode_status = False
        elif self.laneless_mode == 0:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                    CP.steerRateCost)
            self.laneless_mode_status = False
        elif self.laneless_mode == 1:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                5.0) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.libmpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
            self.laneless_mode_status = True
        elif self.laneless_mode == 2 and (
            (self.LP.lll_prob + self.LP.rll_prob) / 2 <
                0.3) and self.lane_change_state == LaneChangeState.off:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                5.0) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.libmpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
            self.laneless_mode_status = True
            self.laneless_mode_status_buffer = True
        elif self.laneless_mode == 2 and ((self.LP.lll_prob + self.LP.rll_prob)/2 > 0.5) and \
         self.laneless_mode_status_buffer and self.lane_change_state == LaneChangeState.off:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                    CP.steerRateCost)
            self.laneless_mode_status = False
            self.laneless_mode_status_buffer = False
        elif self.laneless_mode == 2 and self.laneless_mode_status_buffer == True and self.lane_change_state == LaneChangeState.off:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                5.0) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.libmpc.set_weights(path_cost, heading_cost, CP.steerRateCost)
            self.laneless_mode_status = True
        else:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.libmpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                    CP.steerRateCost)
            self.laneless_mode_status = False
            self.laneless_mode_status_buffer = False

        y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1],
                          np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1])
        heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1],
                                np.linalg.norm(self.path_xyz, axis=1),
                                self.plan_yaw)
        self.y_pts = y_pts

        assert len(y_pts) == LAT_MPC_N + 1
        assert len(heading_pts) == LAT_MPC_N + 1
        # for now CAR_ROTATION_RADIUS is disabled
        # to use it, enable it in the MPC
        assert abs(CAR_ROTATION_RADIUS) < 1e-3
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            float(v_ego), CAR_ROTATION_RADIUS, list(y_pts),
                            list(heading_pts))
        # init state for next
        self.cur_state.x = 0.0
        self.cur_state.y = 0.0
        self.cur_state.psi = 0.0
        self.cur_state.curvature = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1],
                                          self.mpc_solution.curvature)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution.curvature)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init()
            self.cur_state.curvature = measured_curvature

            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning("Lateral mpc - nan: True")

        if self.mpc_solution[
                0].cost > 20000. or mpc_nans:  # TODO: find a better way to detect when MPC did not converge
            self.solution_invalid_cnt += 1
        else:
            self.solution_invalid_cnt = 0

    def publish(self, sm, pm):
        plan_solution_valid = self.solution_invalid_cnt < 2
        plan_send = messaging.new_message('lateralPlan')
        plan_send.valid = sm.all_alive_and_valid(
            service_list=['carState', 'controlsState', 'modelV2'])
        plan_send.lateralPlan.laneWidth = float(self.LP.lane_width)
        plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts]
        plan_send.lateralPlan.psis = [
            float(x) for x in self.mpc_solution.psi[0:CONTROL_N]
        ]
        plan_send.lateralPlan.curvatures = [
            float(x) for x in self.mpc_solution.curvature[0:CONTROL_N]
        ]
        plan_send.lateralPlan.curvatureRates = [
            float(x) for x in self.mpc_solution.curvature_rate[0:CONTROL_N - 1]
        ] + [0.0]
        plan_send.lateralPlan.lProb = float(self.LP.lll_prob)
        plan_send.lateralPlan.rProb = float(self.LP.rll_prob)
        plan_send.lateralPlan.dProb = float(self.LP.d_prob)

        plan_send.lateralPlan.mpcSolutionValid = bool(plan_solution_valid)

        plan_send.lateralPlan.desire = self.desire
        plan_send.lateralPlan.laneChangeState = self.lane_change_state
        plan_send.lateralPlan.laneChangeDirection = self.lane_change_direction

        plan_send.lateralPlan.steerRateCost = float(self.steer_rate_cost)
        plan_send.lateralPlan.outputScale = float(self.output_scale)
        plan_send.lateralPlan.vCruiseSet = float(self.v_cruise_kph)
        plan_send.lateralPlan.vCurvature = float(sm['controlsState'].curvature)
        plan_send.lateralPlan.lanelessMode = bool(self.laneless_mode_status)

        if self.stand_still:
            self.standstill_elapsed_time += DT_MDL
        else:
            self.standstill_elapsed_time = 0.0
        plan_send.lateralPlan.standstillElapsedTime = int(
            self.standstill_elapsed_time)

        pm.send('lateralPlan', plan_send)

        if LOG_MPC:
            dat = messaging.new_message('liveMpc')
            dat.liveMpc.x = list(self.mpc_solution.x)
            dat.liveMpc.y = list(self.mpc_solution.y)
            dat.liveMpc.psi = list(self.mpc_solution.psi)
            dat.liveMpc.curvature = list(self.mpc_solution.curvature)
            dat.liveMpc.cost = self.mpc_solution.cost
            pm.send('liveMpc', dat)