Example #1
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

        # dp
        self.dragon_auto_lc_allowed = False
        self.dragon_auto_lc_timer = None
        self.dragon_auto_lc_delay = 2.
        self.dp_continuous_auto_lc = False
        self.dp_did_auto_lc = False

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                         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].delta = 0.0

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

    def update(self, sm, pm, CP, VM):
        v_ego = sm['carState'].vEgo
        angle_steers = sm['carState'].steeringAngle
        active = sm['controlsState'].active

        angle_offset = sm['liveParameters'].angleOffset

        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc

        # 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)

        self.LP.parse_model(sm['model'])

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

        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

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

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

            if self.dragon_auto_lc_allowed:
                if self.dragon_auto_lc_timer is None:
                    # we only set timer when in preLaneChange state, dragon_auto_lc_delay delay
                    if self.lane_change_state == LaneChangeState.preLaneChange:
                        self.dragon_auto_lc_timer = cur_time + sm[
                            'dragonConf'].dpAutoLcDelay
                elif cur_time >= self.dragon_auto_lc_timer:
                    # if timer is up, we set torque_applied to True to fake user input
                    torque_applied = True
                    self.dp_did_auto_lc = True

            # we reset the timers when torque is applied regardless
            if torque_applied:
                self.dragon_auto_lc_timer = None

            # 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

                # dp when finishing, we reset timer to none.
                self.dragon_auto_lc_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_did_auto_lc = False

        self.prev_one_blinker = one_blinker

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

        # Turn off lanes during lane change
        if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.l_prob *= self.lane_change_ll_prob
            self.LP.r_prob *= self.lane_change_ll_prob
        self.LP.update_d_poly(v_ego)

        # account for actuation delay
        self.cur_state = calc_states_after_delay(self.cur_state, v_ego,
                                                 angle_steers - angle_offset,
                                                 curvature_factor, VM.sR,
                                                 CP.steerActuatorDelay)

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
        else:
            delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired

        self.angle_steers_des_mpc = float(
            math.degrees(delta_desired * VM.sR) + angle_offset)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, CP.steerRateCost)
            self.cur_state[0].delta = math.radians(angle_steers -
                                                   angle_offset) / VM.sR

            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
        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', 'model',
            'dragonConf'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

        plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
        plan_send.pathPlan.rateSteers = float(rate_desired)
        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 = desire
        plan_send.pathPlan.laneChangeState = self.lane_change_state
        plan_send.pathPlan.laneChangeDirection = self.lane_change_direction
        plan_send.pathPlan.dpALCAllowed = self.dragon_auto_lc_allowed

        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.delta = list(self.mpc_solution[0].delta)
            dat.liveMpc.cost = self.mpc_solution[0].cost
            pm.send('liveMpc', dat)
Example #2
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)
Example #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.path_offset_i = 0.0
        self.lane_change_state = LaneChangeState.off
        self.lane_change_timer = 0.0
        self.prev_one_blinker = False

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                         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].delta = 0.0

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

    def update(self, sm, pm, CP, VM):
        v_ego = sm['carState'].vEgo
        angle_steers = sm['carState'].steeringAngle
        active = sm['controlsState'].active

        angle_offset = sm['liveParameters'].angleOffset

        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc
        VM.update_params(sm['liveParameters'].stiffnessFactor,
                         sm['liveParameters'].steerRatio)
        curvature_factor = VM.curvature_factor(v_ego)

        self.LP.parse_model(sm['model'])

        # Lane change logic
        lane_change_direction = LaneChangeDirection.none
        one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker

        if not active or self.lane_change_timer > 10.0:
            self.lane_change_state = LaneChangeState.off
        else:
            if sm['carState'].leftBlinker:
                lane_change_direction = LaneChangeDirection.left
            elif sm['carState'].rightBlinker:
                lane_change_direction = LaneChangeDirection.right

            if lane_change_direction == LaneChangeDirection.left:
                torque_applied = sm['carState'].steeringTorque > 0 and sm[
                    'carState'].steeringPressed
            else:
                torque_applied = sm['carState'].steeringTorque < 0 and sm[
                    'carState'].steeringPressed

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

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

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

            # starting
            elif self.lane_change_state == LaneChangeState.laneChangeStarting and lane_change_prob > 0.5:
                self.lane_change_state = LaneChangeState.laneChangeFinishing

            # finishing
            elif self.lane_change_state == LaneChangeState.laneChangeFinishing and lane_change_prob < 0.2:
                self.lane_change_state = LaneChangeState.preLaneChange

            # Don't allow starting lane change below 45 mph
            if (v_ego < 45 * CV.MPH_TO_MS) and (
                    self.lane_change_state == LaneChangeState.preLaneChange):
                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

        desire = DESIRES[lane_change_direction][self.lane_change_state]

        # Turn off lanes during lane change
        if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.l_prob = 0.
            self.LP.r_prob = 0.
            self.libmpc.init_weights(MPC_COST_LAT.PATH / 10.0,
                                     MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING,
                                     self.steer_rate_cost)
        else:
            self.libmpc.init_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                                     MPC_COST_LAT.HEADING,
                                     self.steer_rate_cost)

        self.LP.update_d_poly(v_ego)

        # TODO: Check for active, override, and saturation
        # if active:
        #   self.path_offset_i += self.LP.d_poly[3] / (60.0 * 20.0)
        #   self.path_offset_i = clip(self.path_offset_i, -0.5,  0.5)
        #   self.LP.d_poly[3] += self.path_offset_i
        # else:
        #   self.path_offset_i = 0.0

        # account for actuation delay
        self.cur_state = calc_states_after_delay(self.cur_state, v_ego,
                                                 angle_steers - angle_offset,
                                                 curvature_factor, VM.sR,
                                                 CP.steerActuatorDelay)

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
        else:
            delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired

        self.angle_steers_des_mpc = float(
            math.degrees(delta_desired * VM.sR) + angle_offset)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, CP.steerRateCost)
            self.cur_state[0].delta = math.radians(angle_steers -
                                                   angle_offset) / VM.sR

            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
        plan_solution_valid = self.solution_invalid_cnt < 2

        plan_send = messaging.new_message()
        plan_send.init('pathPlan')
        plan_send.valid = sm.all_alive_and_valid(service_list=[
            'carState', 'controlsState', 'liveParameters', 'model'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

        plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
        plan_send.pathPlan.rateSteers = float(rate_desired)
        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.sensorValid = bool(sm['liveParameters'].sensorValid)
        plan_send.pathPlan.posenetValid = bool(
            sm['liveParameters'].posenetValid)

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

        pm.send('pathPlan', plan_send)

        if LOG_MPC:
            dat = messaging.new_message()
            dat.init('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.delta = list(self.mpc_solution[0].delta)
            dat.liveMpc.cost = self.mpc_solution[0].cost
            pm.send('liveMpc', dat)
Example #4
0
class PathPlanner():
    def __init__(self, CP):
        self.PathPlan = trace1.Loger("077_R3_LQR_parhplan")

        self.LP = LanePlanner()

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

        self.setup_mpc()
        self.solution_invalid_cnt = 0

        self.params = Params()

        # Lane change
        self.lane_change_enabled = self.params.get('LaneChangeEnabled') == b'1'
        self.lane_change_auto_delay = self.params.get_OpkrAutoLanechangedelay()

        self.lane_change_state = LaneChangeState.off
        self.lane_change_direction = LaneChangeDirection.none
        self.lane_change_run_timer = 0.0
        self.lane_change_wait_timer = 0.0
        self.lane_change_ll_prob = 1.0
        self.prev_one_blinker = False

    def limit_ctrl(self, value, limit, offset):
        p_limit = offset + limit
        m_limit = offset - limit
        if value > p_limit:
            value = p_limit
        elif value < m_limit:
            value = m_limit
        return value

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                         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].delta = 0.0

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

    def update(self, sm, pm, CP, VM):
        v_ego = sm['carState'].vEgo
        v_ego_kph = v_ego * 3.61
        angle_steers = sm['carState'].steeringAngle
        steeringTorque = sm['carState'].steeringTorque
        steeringPressed = sm['carState'].steeringPressed
        active = sm['controlsState'].active

        angle_offset = sm['liveParameters'].angleOffset

        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc

        # 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)

        self.LP.parse_model(sm['model'])

        # 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_run_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))

            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
                # ATOM logic add
                xp = [40, 60, 70, 80]
                fp2 = [0.5, 1, 1.5, 2]
                lane_time = interp(v_ego_kph, xp, fp2)
                # <==
                self.lane_change_ll_prob = max(
                    self.lane_change_ll_prob - lane_time * 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.laneChangeDone

            # done
            elif self.lane_change_state == LaneChangeState.laneChangeDone:
                if not one_blinker:
                    self.lane_change_state = LaneChangeState.off

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

        self.prev_one_blinker = one_blinker

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

        # Turn off lanes during lane change
        if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.l_prob *= self.lane_change_ll_prob
            self.LP.r_prob *= self.lane_change_ll_prob
        self.LP.update_d_poly(v_ego)

        # account for actuation delay
        self.cur_state = calc_states_after_delay(self.cur_state, v_ego,
                                                 angle_steers - angle_offset,
                                                 curvature_factor, VM.sR,
                                                 CP.steerActuatorDelay)

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
        else:
            delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired
        self.angle_steers_des_mpc = float(
            math.degrees(delta_desired * VM.sR) + angle_offset)
        org_angle_steers_des = self.angle_steers_des_mpc

        # atom
        if steeringPressed:
            delta_steer = self.angle_steers_des_mpc - angle_steers
            xp = [-450, 0, 450]
            fp2 = [5, 0, 5]
            limit_steers = interp(steeringTorque, xp, fp2)
            if steeringTorque < 0:  # right
                if delta_steer > 0:
                    self.angle_steers_des_mpc = self.limit_ctrl(
                        org_angle_steers_des, limit_steers, angle_steers)
            elif steeringTorque > 0:  # left
                if delta_steer < 0:
                    self.angle_steers_des_mpc = self.limit_ctrl(
                        org_angle_steers_des, limit_steers, angle_steers)

        elif v_ego_kph < 30:  # 30
            xp = [5, 15, 30]
            fp2 = [1, 3, 5]
            limit_steers = interp(v_ego_kph, xp, fp2)
            self.angle_steers_des_mpc = self.limit_ctrl(
                org_angle_steers_des, limit_steers, angle_steers)
        # 가변 sR rate_cost
        # self.atom_sr_boost_bp = [ 1.5,  5.0, 10.0, 15.0, 20.0, 30.0, 50.0, 60.0, 100.0, 300.0]
        # self.sR_Cost          = [0.50, 0.41, 0.34, 0.28, 0.24, 0.18, 0.12, 0.10,  0.05,  0.01]
        # self.steer_rate_cost  = interp(abs(angle_steers), self.atom_sr_boost_bp, self.sR_Cost)


# # 설정값 분석을 위한 임시 코드

#     self.scale = CP.lateralTuning.lqr.scale
#     self.ki = CP.lateralTuning.lqr.ki
#     self.dc_gain = CP.lateralTuning.lqr.dcGain
#     self.steerRatio = VM.sR
#     self.laneWidth = float(self.LP.lane_width)
#     self.dPoly = [float(x) for x in self.LP.d_poly]
#     self.lPoly = [float(x) for x in self.LP.l_poly]
#     self.lProb = float(self.LP.l_prob)
#     self.rPoly = [float(x) for x in self.LP.r_poly]
#     self.rProb = float(self.LP.r_prob)

#     str2 = '/{} /{} /{} /{} /{} /{} /{} /{} /{} /{} /{} /{} /{} /{} /{}'.format(
#               v_ego_kph, angle_steers, self.angle_steers_des_mpc, angle_offset, steeringTorque, self.scale, self.ki, self.dc_gain, self.steerRatio, self.laneWidth, self.dPoly, self.lPoly, self.lProb, self.rPoly, self.rProb )
#     self.PathPlan.add( str2 )
# ###############################

#  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, self.steer_rate_cost)
            self.cur_state[0].delta = math.radians(angle_steers -
                                                   angle_offset) / VM.sR

            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
        plan_solution_valid = self.solution_invalid_cnt < 3

        plan_send = messaging.new_message('pathPlan')
        plan_send.valid = sm.all_alive_and_valid(service_list=[
            'carState', 'controlsState', 'liveParameters', 'model'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

        plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
        plan_send.pathPlan.rateSteers = float(rate_desired)
        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 = 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.delta = list(self.mpc_solution[0].delta)
            dat.liveMpc.cost = self.mpc_solution[0].cost
            pm.send('liveMpc', dat)
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)
Example #6
0
class PathPlanner():
    def __init__(self, CP):
        self.LP = LanePlanner()

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

        self.setup_mpc()
        self.solution_invalid_cnt = 0

        self.steerRatio_last = 0

        self.params = Params()

        # Lane change
        self.lane_change_enabled = self.params.get('LaneChangeEnabled') == b'1'
        self.lane_change_auto_delay = self.params.get_OpkrAutoLanechangedelay(
        )  #int( self.params.get('OpkrAutoLanechangedelay') )

        self.lane_change_state = LaneChangeState.off
        self.lane_change_direction = LaneChangeDirection.none
        self.lane_change_run_timer = 0.0
        self.lane_change_wait_timer = 0.0
        self.lane_change_ll_prob = 1.0
        self.prev_one_blinker = False

        # atom
        self.trPATH = trace1.Loger("path")
        self.trLearner = trace1.Loger("Learner")
        self.trpathPlan = trace1.Loger("pathPlan")

        self.atom_timer_cnt = 0
        self.atom_steer_ratio = None
        self.atom_sr_boost_bp = [0., 0.]
        self.atom_sr_boost_range = [0., 0.]

        self.carParams_valid = False

        self.m_avg = ma.MoveAvg()

    def limit_ctrl(self, value, limit, offset):
        p_limit = offset + limit
        m_limit = offset - limit
        if value > p_limit:
            value = p_limit
        elif value < m_limit:
            value = m_limit
        return value

    def limit_ctrl1(self, value, limit1, limit2, offset):
        p_limit = offset + limit1
        m_limit = offset - limit2
        if value > p_limit:
            value = p_limit
        elif value < m_limit:
            value = m_limit
        return value

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                         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].delta = 0.0

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

    def atom_tune(self, v_ego_kph, sr_value, atomTuning):  # 조향각에 따른 변화.
        self.sr_KPH = atomTuning.sRKPH
        self.sr_BPV = atomTuning.sRBPV
        self.sr_steerRatioV = atomTuning.sRsteerRatioV
        self.sr_SteerRatio = []

        nPos = 0
        for steerRatio in self.sr_BPV:  # steerRatio
            self.sr_SteerRatio.append(
                interp(sr_value, steerRatio, self.sr_steerRatioV[nPos]))
            nPos += 1
            if nPos > 20:
                break

        steerRatio = interp(v_ego_kph, self.sr_KPH, self.sr_SteerRatio)

        return steerRatio

    def atom_actuatorDelay(self, v_ego_kph, sr_value, atomTuning):
        self.sr_KPH = atomTuning.sRKPH
        self.sr_BPV = atomTuning.sRBPV
        self.sr_ActuatorDelayV = atomTuning.sRsteerActuatorDelayV
        self.sr_ActuatorDelay = []

        nPos = 0
        for steerRatio in self.sr_BPV:
            self.sr_ActuatorDelay.append(
                interp(sr_value, steerRatio, self.sr_ActuatorDelayV[nPos]))
            nPos += 1
            if nPos > 10:
                break

        actuatorDelay = interp(v_ego_kph, self.sr_KPH, self.sr_ActuatorDelay)

        return actuatorDelay

    def atom_steer(self, sr_value, sr_up, sr_dn):
        delta = sr_value - self.steerRatio_last

        sr_up = min(abs(delta), sr_up)
        sr_dn = min(abs(delta), sr_dn)
        steerRatio = self.steerRatio_last
        if delta > 0:
            steerRatio += sr_up
        elif delta < 0:
            steerRatio -= sr_dn

        self.steerRatio_last = steerRatio
        return steerRatio

    def update(self, sm, pm, CP, VM):
        self.atom_timer_cnt += 1
        if self.atom_timer_cnt > 1000:
            self.atom_timer_cnt = 0

        cruiseState = sm['carState'].cruiseState
        leftBlindspot = sm['carState'].leftBlindspot
        rightBlindspot = sm['carState'].rightBlindspot

        lateralsRatom = CP.lateralsRatom
        atomTuning = CP.atomTuning

        #if atomTuning is None or lateralsRatom is None:
        #print('carparams={} steerRatio={}  carParams_valid={}'.format(sm.updated['carParams'], sm['carParams'].steerRatio, self.carParams_valid ) )

        if not self.carParams_valid and sm[
                'carParams'].steerRatio:  # sm.updated['carParams']:
            self.carParams_valid = True

        if self.carParams_valid:
            lateralsRatom = sm['carParams'].lateralsRatom
            atomTuning = sm['carParams'].atomTuning

        v_ego = sm['carState'].vEgo
        angle_steers = sm['carState'].steeringAngle
        steeringPressed = sm['carState'].steeringPressed
        steeringTorque = sm['carState'].steeringTorque
        active = sm['controlsState'].active
        model_sum = sm['controlsState'].modelSum

        v_ego_kph = v_ego * CV.MS_TO_KPH

        self.steerRatio = sm['liveParameters'].steerRatio
        angle_offset = sm['liveParameters'].angleOffset
        angleOffsetAverage = sm['liveParameters'].angleOffsetAverage
        stiffnessFactor = sm['liveParameters'].stiffnessFactor

        #if (self.atom_timer_cnt % 100) == 0:
        #  str_log3 = 'angleOffset={:.1f} angleOffsetAverage={:.3f} steerRatio={:.2f} stiffnessFactor={:.3f} '.format( angle_offset, angleOffsetAverage, self.steerRatio, stiffnessFactor )
        #  self.trLearner.add( 'LearnerParam {}  carParams={}'.format( str_log3, self.carParams_valid ) )

        if lateralsRatom.learnerParams:
            pass
        else:
            # atom
            if self.carParams_valid:
                self.steer_rate_cost = sm['carParams'].steerRateCost
                self.steerRatio = sm['carParams'].steerRatio
            else:
                self.steer_rate_cost = CP.steerRateCost
                self.steerRatio = CP.steerRatio

            #xp = [-5,0,5]
            #fp = [0.4, 0.7, 0.4]
            #self.steer_rate_cost = interp( angle_steers, xp, fp )
            steerRatio = self.atom_tune(v_ego_kph, angle_steers, atomTuning)
            self.steerRatio = self.atom_steer(steerRatio, 2, 1)

        #actuatorDelay = CP.steerActuatorDelay
        steerActuatorDelay = self.atom_actuatorDelay(v_ego_kph, angle_steers,
                                                     atomTuning)

        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc
        VM.update_params(stiffnessFactor, self.steerRatio)
        curvature_factor = VM.curvature_factor(v_ego)

        self.LP.parse_model(sm['model'])

        # 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_run_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:
            l_poly = self.LP.l_poly[3]
            r_poly = self.LP.r_poly[3]
            c_prob = l_poly + r_poly
            torque_applied = steeringPressed and \
                              ((steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or \
                                (steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))

            blindspot_detected = (
                (leftBlindspot
                 and self.lane_change_direction == LaneChangeDirection.left) or
                (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 cruiseState.cruiseSwState == Buttons.CANCEL:
                self.lane_change_state = LaneChangeState.off
                self.lane_change_ll_prob = 1.0
                self.lane_change_wait_timer = 0

            elif 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
                xp = [40, 50, 60, 70]
                fp2 = [0.2, 0.6, 1.2, 1.5]
                lane_time = interp(v_ego_kph, xp, fp2)
                self.lane_change_ll_prob = max(
                    self.lane_change_ll_prob - lane_time * 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 self.lane_change_ll_prob > 0.99 and abs(c_prob) < 0.3:
                    self.lane_change_state = LaneChangeState.laneChangeDone

            # done
            elif self.lane_change_state == LaneChangeState.laneChangeDone:
                if not one_blinker:
                    self.lane_change_state = LaneChangeState.off

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

        self.prev_one_blinker = one_blinker

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

        # Turn off lanes during lane change
        if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.l_prob *= self.lane_change_ll_prob
            self.LP.r_prob *= self.lane_change_ll_prob
        self.LP.update_d_poly(v_ego, lateralsRatom.cameraOffset)

        # account for actuation delay
        self.cur_state = calc_states_after_delay(self.cur_state, v_ego,
                                                 angle_steers - angle_offset,
                                                 curvature_factor, VM.sR,
                                                 steerActuatorDelay)

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
        else:
            delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired

        self.angle_steers_des_mpc = float(
            math.degrees(delta_desired * VM.sR) + angle_offset)
        org_angle_steers_des = self.angle_steers_des_mpc

        # atom
        if steeringPressed:
            delta_steer = org_angle_steers_des - angle_steers
            xp = [-255, 0, 255]
            fp2 = [5, 0, 5]
            limit_steers = interp(steeringTorque, xp, fp2)
            if steeringTorque < 0:  # right
                if delta_steer > 0:
                    self.angle_steers_des_mpc = self.limit_ctrl(
                        org_angle_steers_des, limit_steers, angle_steers)
            elif steeringTorque > 0:  # left
                if delta_steer < 0:
                    self.angle_steers_des_mpc = self.limit_ctrl(
                        org_angle_steers_des, limit_steers, angle_steers)

        elif v_ego_kph < 15:  # 30
            xp = [3, 10, 15]
            fp2 = [3, 5, 7]
            limit_steers = interp(v_ego_kph, xp, fp2)
            self.angle_steers_des_mpc = self.limit_ctrl(
                org_angle_steers_des, limit_steers, angle_steers)
        elif v_ego_kph > 60:
            pass
        elif abs(angle_steers) > 10:  # angle steer > 10
            # 2.
            xp = [-10, -5, 0, 5, 10]  # 5  10=>28 15=>35, 30=>52
            fp1 = [3, 8, 10, 20, 10]  # +
            fp2 = [10, 20, 10, 8, 3]  # -
            limit_steers1 = interp(model_sum, xp, fp1)  # +
            limit_steers2 = interp(model_sum, xp, fp2)  # -
            self.angle_steers_des_mpc = self.limit_ctrl1(
                org_angle_steers_des, limit_steers1, limit_steers2,
                angle_steers)

        delta_steer = self.angle_steers_des_mpc - angle_steers
        ANGLE_LIMIT = 8
        if delta_steer > ANGLE_LIMIT:
            p_angle_steers = angle_steers + ANGLE_LIMIT
            self.angle_steers_des_mpc = p_angle_steers
        elif delta_steer < -ANGLE_LIMIT:
            m_angle_steers = angle_steers - ANGLE_LIMIT
            self.angle_steers_des_mpc = m_angle_steers

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, self.steer_rate_cost)
            self.cur_state[0].delta = math.radians(angle_steers -
                                                   angle_offset) / VM.sR

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

        #self.trPATH.add( 'mpc_nans ={}  libmpc  steer_rate_cost={}  delta={}   angle_steers={}'.format( mpc_nans, self.steer_rate_cost, self.cur_state[0].delta, angle_steers ) )

        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
        plan_solution_valid = self.solution_invalid_cnt < 3

        plan_send = messaging.new_message('pathPlan')
        plan_send.valid = sm.all_alive_and_valid(service_list=[
            'carState', 'controlsState', 'liveParameters', 'model'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

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

        plan_send.pathPlan.desire = desire
        plan_send.pathPlan.laneChangeState = self.lane_change_state
        plan_send.pathPlan.laneChangeDirection = self.lane_change_direction
        plan_send.pathPlan.steerRatio = self.steerRatio
        plan_send.pathPlan.steerActuatorDelay = steerActuatorDelay
        pm.send('pathPlan', plan_send)

        #if self.solution_invalid_cnt > 0:
        #  str_log3 = 'v_ego_kph={:.1f} angle_steers_des_mpc={:.1f} angle_steers={:.1f} solution_invalid_cnt={:.0f} mpc_solution={:.1f}/{:.0f}'.format( v_ego_kph, self.angle_steers_des_mpc, angle_steers, self.solution_invalid_cnt, self.mpc_solution[0].cost, mpc_nans )
        #  self.trpathPlan.add( 'pathPlan {}  LOG_MPC={}'.format( str_log3, LOG_MPC ) )

        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.delta = list(self.mpc_solution[0].delta)
            dat.liveMpc.cost = self.mpc_solution[0].cost
            pm.send('liveMpc', dat)
Example #7
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)
Example #8
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)
Example #9
0
class PathPlanner():
    def __init__(self, CP):
        self.LP = LanePlanner()

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

        self.setup_mpc()
        self.solution_invalid_cnt = 0

        self.params = Params()

        # Lane change
        self.lane_change_enabled = self.params.get('LaneChangeEnabled') == b'1'
        self.lane_change_auto_delay = self.params.get_OpkrAutoLanechangedelay(
        )  #int( self.params.get('OpkrAutoLanechangedelay') )

        self.lane_change_state = LaneChangeState.off
        self.lane_change_direction = LaneChangeDirection.none
        self.lane_change_run_timer = 0.0
        self.lane_change_wait_timer = 0.0
        self.lane_change_ll_prob = 1.0
        self.prev_one_blinker = False

        # atom
        self.trPATH = trace1.Loger("path")
        self.trLearner = trace1.Loger("Learner")
        self.trpathPlan = trace1.Loger("pathPlan")

        self.atom_timer_cnt = 0
        self.atom_steer_ratio = None
        self.atom_sr_boost_bp = [0., 0.]
        self.atom_sr_boost_range = [0., 0.]

    def limit_ctrl(self, value, limit, offset):
        p_limit = offset + limit
        m_limit = offset - limit
        if value > p_limit:
            value = p_limit
        elif value < m_limit:
            value = m_limit
        return value

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                         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].delta = 0.0

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

    def update(self, sm, pm, CP, VM):
        self.atom_timer_cnt += 1
        if self.atom_timer_cnt > 1000:
            self.atom_timer_cnt = 0

        leftBlindspot = sm['carState'].leftBlindspot
        rightBlindspot = sm['carState'].rightBlindspot

        v_ego = sm['carState'].vEgo
        angle_steers = sm['carState'].steeringAngle
        steeringPressed = sm['carState'].steeringPressed
        steeringTorque = sm['carState'].steeringTorque
        active = sm['controlsState'].active
        v_ego_kph = v_ego * CV.MS_TO_KPH

        self.steerRatio = sm['liveParameters'].steerRatio
        angle_offset = sm['liveParameters'].angleOffset
        angleOffsetAverage = sm['liveParameters'].angleOffsetAverage
        stiffnessFactor = sm['liveParameters'].stiffnessFactor

        if (self.atom_timer_cnt % 100) == 0:
            str_log3 = 'angleOffset={:.1f} angleOffsetAverage={:.3f} steerRatio={:.2f} stiffnessFactor={:.3f} '.format(
                angle_offset, angleOffsetAverage, self.steerRatio,
                stiffnessFactor)
            self.trLearner.add('LearnerParam {}'.format(str_log3))

        if CP.lateralsRatom.learnerParams:
            pass
        else:
            #angle_offset = 0
            #angleOffsetAverage = 0

            # atom
            self.steer_rate_cost = sm['carParams'].steerRateCost
            self.steerRatio = sm['carParams'].steerRatio
            if self.steer_rate_cost == 0:
                self.steer_rate_cost = CP.steerRateCost

            if self.steerRatio == 0:
                self.steerRatio = CP.steerRatio

            self.steerRatio = interp(angle_steers, CP.atomTuning.sRBPV,
                                     CP.atomTuning.sRsteerRatioV)

            str_log1 = 'steerRatio={:.1f}/{:.1f} bp={} range={}'.format(
                self.steerRatio, CP.steerRatio, CP.atomTuning.sRBPV,
                CP.atomTuning.sRsteerRatioV)
            str_log2 = 'steerRateCost={:.2f}'.format(self.steer_rate_cost)
            self.trPATH.add('{} {}'.format(str_log1, str_log2))

        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc
        VM.update_params(stiffnessFactor,
                         self.steerRatio)  # sm['liveParameters'].steerRatio)
        curvature_factor = VM.curvature_factor(v_ego)

        self.LP.parse_model(sm['model'])

        # 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_run_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 = steeringPressed and \
                              ((steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or \
                                (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_ll_prob = max(
                    self.lane_change_ll_prob - 1.5 * 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 self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.laneChangeDone

            # done
            elif self.lane_change_state == LaneChangeState.laneChangeDone:
                if not one_blinker:
                    self.lane_change_state = LaneChangeState.off

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

        self.prev_one_blinker = one_blinker

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

        # Turn off lanes during lane change
        if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.l_prob *= self.lane_change_ll_prob
            self.LP.r_prob *= self.lane_change_ll_prob
        self.LP.update_d_poly(v_ego)

        # account for actuation delay
        self.cur_state = calc_states_after_delay(self.cur_state, v_ego,
                                                 angle_steers - angle_offset,
                                                 curvature_factor, VM.sR,
                                                 CP.steerActuatorDelay)

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
        else:
            delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired

        self.angle_steers_des_mpc = float(
            math.degrees(delta_desired * VM.sR) + angle_offset)
        org_angle_steers_des = self.angle_steers_des_mpc

        # atom
        if steeringPressed:
            delta_steer = self.angle_steers_des_mpc - angle_steers
            xp = [-255, 0, 255]
            fp2 = [5, 0, 5]
            limit_steers = interp(steeringTorque, xp, fp2)
            if steeringTorque < 0:  # right
                if delta_steer > 0:
                    self.angle_steers_des_mpc = self.limit_ctrl(
                        org_angle_steers_des, limit_steers, angle_steers)
            elif steeringTorque > 0:  # left
                if delta_steer < 0:
                    self.angle_steers_des_mpc = self.limit_ctrl(
                        org_angle_steers_des, limit_steers, angle_steers)

        elif v_ego_kph < 30:
            xp = [5, 15, 30]
            fp2 = [3, 5, 9]
            limit_steers = interp(v_ego_kph, xp, fp2)
            self.angle_steers_des_mpc = self.limit_ctrl(
                org_angle_steers_des, limit_steers, angle_steers)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, self.steer_rate_cost)
            self.cur_state[0].delta = math.radians(angle_steers -
                                                   angle_offset) / VM.sR

            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
        plan_solution_valid = self.solution_invalid_cnt < 3

        plan_send = messaging.new_message('pathPlan')
        plan_send.valid = sm.all_alive_and_valid(service_list=[
            'carState', 'controlsState', 'liveParameters', 'model'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

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

        plan_send.pathPlan.desire = desire
        plan_send.pathPlan.laneChangeState = self.lane_change_state
        plan_send.pathPlan.laneChangeDirection = self.lane_change_direction
        pm.send('pathPlan', plan_send)

        if self.solution_invalid_cnt > 0:
            str_log3 = 'v_ego_kph={:.1f} angle_steers_des_mpc={:.1f} angle_steers={:.1f} solution_invalid_cnt={:.0f} mpc_solution={:.1f}/{:.0f}'.format(
                v_ego_kph, self.angle_steers_des_mpc, angle_steers,
                self.solution_invalid_cnt, self.mpc_solution[0].cost, mpc_nans)
            self.trpathPlan.add('pathPlan {}'.format(str_log3))

        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.delta = list(self.mpc_solution[0].delta)
            dat.liveMpc.cost = self.mpc_solution[0].cost
            pm.send('liveMpc', dat)
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)
Example #11
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)
Example #12
0
class PathPlanner():
    def __init__(self, CP):
        self.LP = LanePlanner()
        if not travis:
            self.arne_sm = messaging_arne.SubMaster(['arne182Status'])
            self.arne_pm = messaging_arne.PubMaster(['latControl'])
        self.last_cloudlog_t = 0
        self.steer_rate_cost = CP.steerRateCost
        self.blindspotwait = 30
        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.blindspotTrueCounterleft = 0
        self.blindspotTrueCounterright = 0
        self.posenetValid = True
        self.op_params = opParams()
        self.alca_nudge_required = self.op_params.get('alca_nudge_required',
                                                      default=True)
        self.alca_min_speed = self.op_params.get('alca_min_speed',
                                                 default=20.0)

        self.mpc_frame = 0
        self.sR_delay_counter = 0
        self.steerRatio_new = 0.0
        self.sR_time = 1
        self.sR_Boost = [3.5, 2.5]
        self.sR_BoostBP = [10., 22.2]
        self.sR_Boost_new = 0.0

        self.steerRatio = self.op_params.get('steer_ratio', default=12.0)
        self.sR = [self.steerRatio, self.steerRatio + self.sR_Boost_new]
        self.sRBP = [2.5, 10.0]

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                         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].delta = 0.0

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

    def update(self, sm, pm, CP, VM):
        if not travis:
            self.arne_sm.update(0)
            gas_button_status = self.arne_sm['arne182Status'].gasbuttonstatus
            if gas_button_status == 1:
                self.blindspotwait = 10
            elif gas_button_status == 2:
                self.blindspotwait = 30
            else:
                self.blindspotwait = 20
            if self.arne_sm['arne182Status'].rightBlindspot:
                self.blindspotTrueCounterright = 0
            else:
                self.blindspotTrueCounterright = self.blindspotTrueCounterright + 1
            if self.arne_sm['arne182Status'].leftBlindspot:
                self.blindspotTrueCounterleft = 0
            else:
                self.blindspotTrueCounterleft = self.blindspotTrueCounterleft + 1
        else:
            self.blindspotwait = 10
            self.blindspotTrueCounterleft = 0
            self.blindspotTrueCounterright = 0
        v_ego = sm['carState'].vEgo
        angle_steers = sm['carState'].steeringAngle
        active = sm['controlsState'].active

        angle_offset = sm['liveParameters'].angleOffset

        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc
        #VM.update_params(sm['liveParameters'].stiffnessFactor, sm['liveParameters'].steerRatio)
        #VM.update_params(sm['liveParameters'].stiffnessFactor, CP.steerRatio)
        VM.update_params(sm['liveParameters'].stiffnessFactor, self.steerRatio)
        curvature_factor = VM.curvature_factor(v_ego)

        # Get steerRatio and steerRateCost from kegman.json every x seconds
        self.mpc_frame += 1
        if self.mpc_frame % 500 == 0:
            # live tuning through /data/openpilot/tune.py overrides interface.py settings
            self.sR_Boost_new = interp(v_ego, self.sR_BoostBP, self.sR_Boost)
            self.steerRatio = self.op_params.get('steer_ratio', default=12.0)
            self.sR = [self.steerRatio, self.steerRatio + self.sR_Boost_new]
            self.sR_time = 100

            self.mpc_frame = 0

        if v_ego > 5.0:
            # boost steerRatio by boost amount if desired steer angle is high
            self.steerRatio_new = interp(abs(angle_steers), self.sRBP, self.sR)

            self.sR_delay_counter += 1
            if self.sR_delay_counter % self.sR_time != 0:
                if self.steerRatio_new > self.steerRatio:
                    self.steerRatio = self.steerRatio_new
            else:
                self.steerRatio = self.steerRatio_new
                self.sR_delay_counter = 0
        else:
            self.steerRatio = self.sR[0]

        print("steerRatio = ", self.steerRatio)

        self.LP.parse_model(sm['model'])

        # Lane change logic
        one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
        below_lane_change_speed = v_ego < self.alca_min_speed * CV.MPH_TO_MS

        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 one_blinker
        ) or (not self.lane_change_enabled) or (
                sm['carState'].steeringPressed and
            ((sm['carState'].steeringTorque > 0
              and self.lane_change_direction == LaneChangeDirection.right) or
             (sm['carState'].steeringTorque < 0
              and self.lane_change_direction == LaneChangeDirection.left))):
            self.lane_change_state = LaneChangeState.off
            self.lane_change_direction = LaneChangeDirection.none
        else:
            if sm['carState'].leftBlinker:
                lane_change_direction = LaneChangeDirection.left
            elif sm['carState'].rightBlinker:
                lane_change_direction = LaneChangeDirection.right

            #if self.alca_nudge_required:
            torque_applied = (sm['carState'].steeringPressed and \
                             ((sm['carState'].steeringTorque > 0 and lane_change_direction == LaneChangeDirection.left and not sm['carState'].leftBlindspot) or \
                              (sm['carState'].steeringTorque < 0 and lane_change_direction == LaneChangeDirection.right and not sm['carState'].rightBlindspot))) or \
                             (not self.alca_nudge_required and self.blindspotTrueCounterleft > self.blindspotwait and lane_change_direction == LaneChangeDirection.left) or \
                             (not self.alca_nudge_required and self.blindspotTrueCounterright > self.blindspotwait and lane_change_direction == LaneChangeDirection.right)
            #else:
            #  torque_applied = True

            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.blindspotTrueCounterleft = 0
                self.blindspotTrueCounterright = 0
                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
                    self.blindspotTrueCounterleft = 0
                    self.blindspotTrueCounterright = 0
                elif torque_applied:
                    self.lane_change_state = LaneChangeState.laneChangeStarting

            # starting
            elif self.lane_change_state == LaneChangeState.laneChangeStarting:
                # fade out over .2s
                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
                if (self.arne_sm['arne182Status'].rightBlindspot
                        and lane_change_direction == LaneChangeDirection.right
                    ) or (self.arne_sm['arne182Status'].leftBlindspot and
                          lane_change_direction == LaneChangeDirection.left):
                    self.lane_change_state = LaneChangeState.preLaneChange
                    self.blindspotTrueCounterleft = 0
                    self.blindspotTrueCounterright = 0

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

        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

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

        # Turn off lanes during lane change
        if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.l_prob *= self.lane_change_ll_prob
            self.LP.r_prob *= self.lane_change_ll_prob

        self.LP.update_d_poly(v_ego)

        # account for actuation delay
        self.cur_state = calc_states_after_delay(self.cur_state, v_ego,
                                                 angle_steers - angle_offset,
                                                 curvature_factor,
                                                 self.steerRatio,
                                                 CP.steerActuatorDelay)

        #if abs(angle_steers - angle_offset) > 4 and CP.lateralTuning.which() == 'pid': #check if this causes laggy steering
        #  self.cur_state[0].delta = math.radians(angle_steers - angle_offset) / VM.sR

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            rate_desired = math.degrees(self.mpc_solution[0].rate[0] *
                                        self.steerRatio)
        else:
            delta_desired = math.radians(angle_steers -
                                         angle_offset) / self.steerRatio
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired

        self.angle_steers_des_mpc = float(
            math.degrees(delta_desired * self.steerRatio) + angle_offset)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, CP.steerRateCost)
            self.cur_state[0].delta = math.radians(
                angle_steers - angle_offset) / self.steerRatio

            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
        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', 'model'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

        plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
        plan_send.pathPlan.rateSteers = float(rate_desired)
        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.sensorValid = bool(sm['liveParameters'].sensorValid)
        plan_send.pathPlan.posenetValid = bool(
            sm['liveParameters'].posenetValid) or self.posenetValid
        self.posenetValid = bool(sm['liveParameters'].posenetValid)

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

        pm.send('pathPlan', plan_send)

        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.delta = list(self.mpc_solution[0].delta)
        dat.liveMpc.cost = self.mpc_solution[0].cost
        pm.send('liveMpc', dat)

        msg = messaging_arne.new_message('latControl')
        msg.latControl.anglelater = math.degrees(
            list(self.mpc_solution[0].delta)[-1])
        if not travis:
            self.arne_pm.send('latControl', msg)
Example #13
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'

    if int(Params().get('OpkrAutoLaneChangeDelay')) == 0:
      self.lane_change_auto_delay = 0.0
    elif int(Params().get('OpkrAutoLaneChangeDelay')) == 1:
      self.lane_change_auto_delay = 0.5
    elif int(Params().get('OpkrAutoLaneChangeDelay')) == 2:
      self.lane_change_auto_delay = 1.0
    elif int(Params().get('OpkrAutoLaneChangeDelay')) == 3:
      self.lane_change_auto_delay = 1.5
    elif int(Params().get('OpkrAutoLaneChangeDelay')) == 4:
      self.lane_change_auto_delay = 2.0
    self.trRapidCurv = trace1.Loger("079_OPKR_RapidCurv")   
    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.mpc_frame = 0

    self.lane_change_adjust = [0.2, 1.3]
    self.lane_change_adjust_vel = [16, 30]
    self.lane_change_adjust_new = 0.0

    self.new_steerRatio = CP.steerRatio

  def limit_ctrl(self, value, limit, offset ):
      p_limit = offset + limit
      m_limit = offset - limit
      if value > p_limit:
          value = p_limit
      elif  value < m_limit:
          value = m_limit
      return value

  def limit_ctrl1(self, value, limit1, limit2, offset ):
      p_limit = offset + limit1
      m_limit = offset - limit2
      if value > p_limit:
          value = p_limit
      elif  value < m_limit:
          value = m_limit
      return value     

  def setup_mpc(self):
    self.libmpc = libmpc_py.libmpc
    self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, 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].delta = 0.0

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

  def update(self, sm, pm, CP, VM):
    limit_steers1 = 0
    limit_steers2 = 0
    debug_status = 0
    v_ego = sm['carState'].vEgo
    angle_steers = sm['carState'].steeringAngle
    active = sm['controlsState'].active
    steeringPressed = sm['carState'].steeringPressed 
    saturated_steering = sm['controlsState'].steerSaturated
    live_steerratio = sm['liveParameters'].steerRatio
    mode_select = sm['carState'].cruiseState.modeSel
    steeringTorque = sm['carState'].steeringTorque    
    angle_offset = sm['liveParameters'].angleOffset
    model_sum = sm['controlsState'].modelSum
    v_ego_kph = v_ego * CV.MS_TO_KPH    

    # Run MPC
    self.angle_steers_des_prev = self.angle_steers_des_mpc

    if saturated_steering:
      self.mpc_frame += 1
      if self.mpc_frame % 10 == 0:
        self.new_steerRatio += 0.1
        if self.new_steerRatio >= 16.5:
          self.new_steerRatio = 16.5
        self.mpc_frame = 0
    else:
      self.mpc_frame += 1
      if self.mpc_frame % 20 == 0:
        self.new_steerRatio -= 0.1
        if self.new_steerRatio <= CP.steerRatio:
          self.new_steerRatio = CP.steerRatio
        self.mpc_frame = 0

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

    curvature_factor = VM.curvature_factor(v_ego)

    self.LP.parse_model(sm['model'])

    # 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 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))

      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.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.laneChangeDone

      # done
      elif self.lane_change_state == LaneChangeState.laneChangeDone:
        if not one_blinker:
          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

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

    # Turn off lanes during lane change
    if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
      self.LP.l_prob *= self.lane_change_ll_prob
      self.LP.r_prob *= self.lane_change_ll_prob
    self.LP.update_d_poly(v_ego, sm)

    # account for actuation delay
    if mode_select == 3:
      self.cur_state = calc_states_after_delay(self.cur_state, v_ego, angle_steers - angle_offset, curvature_factor, VM.sR, (CP.steerActuatorDelay + 0.05))
    else:
      self.cur_state = calc_states_after_delay(self.cur_state, v_ego, angle_steers - angle_offset, curvature_factor, VM.sR, CP.steerActuatorDelay)

    v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
    self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                        list(self.LP.l_poly), list(self.LP.r_poly), list(self.LP.d_poly),
                        self.LP.l_prob, self.LP.r_prob, curvature_factor, v_ego_mpc, self.LP.lane_width)

    # reset to current steer angle if not active or overriding
    if active:
      delta_desired = self.mpc_solution[0].delta[1]
      rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
    else:
      delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
      rate_desired = 0.0

    self.cur_state[0].delta = delta_desired

    self.angle_steers_des_mpc = float(math.degrees(delta_desired * VM.sR) + angle_offset)

    # Atom 
    # org_angle_steers_des = self.angle_steers_des_mpc
    # delta_steer = org_angle_steers_des - angle_steers

    # if self.lane_change_state == LaneChangeState.laneChangeStarting:
    #   debug_status = 0
    #   xp = [40,70]
    #   fp2 = [5,10]
    #   limit_steers = interp( v_ego_kph, xp, fp2 )
    #   self.angle_steers_des_mpc = self.limit_ctrl( org_angle_steers_des, limit_steers, angle_steers )      
    # elif steeringPressed:
    #   debug_status = 1
    #   if angle_steers > 10 and steeringTorque > 0:
    #     delta_steer = max( delta_steer, 0 )
    #     delta_steer = min( delta_steer, DST_ANGLE_LIMIT )
    #     self.angle_steers_des_mpc = angle_steers + delta_steer
    #   elif angle_steers < -10  and steeringTorque < 0:
    #     delta_steer = min( delta_steer, 0 )
    #     delta_steer = max( delta_steer, -DST_ANGLE_LIMIT )        
    #     self.angle_steers_des_mpc = angle_steers + delta_steer
    #   else:
    #     debug_status = 2
    #     if steeringTorque < 0:  # right
    #       if delta_steer > 0:
    #         self.angle_steers_des_mpc = self.limit_ctrl( org_angle_steers_des, DST_ANGLE_LIMIT, angle_steers )
    #     elif steeringTorque > 0:  # left
    #       if delta_steer < 0:
    #         self.angle_steers_des_mpc = self.limit_ctrl( org_angle_steers_des, DST_ANGLE_LIMIT, angle_steers )
    # elif v_ego_kph < 20 : #and abs(angle_steers) < 5.0 : 
    #   debug_status = 3
    # # 저속 와리가리 제어.  
    #   xp = [5,10,20]
    #   fp2 = [1,3,7]
    #   limit_steers = interp( v_ego_kph, xp, fp2 )
    #   self.angle_steers_des_mpc = self.limit_ctrl( org_angle_steers_des, limit_steers, angle_steers )
    # elif v_ego_kph > 85: 
    #   debug_status = 4
    #   pass
    # elif abs(angle_steers) > 25: 
    # # #최대 허용 조향각 제어 로직 1.  
    #   debug_status = 5
    #   xp = [-30,-20,-10,-5,0,5,10,20,30]    # 5=>약12도, 10=>28 15=>35, 30=>52
    #   fp1 = [ 3, 5, 7, 9,11,13,15,13,11,9]    # +
    #   fp2 = [ 9,11,13,15,13,11, 9, 7, 5,3]    # -
    #   # fp1 = [ 5, 7, 9,11,13,15,17,15,12]    # +
    #   # fp2 = [12,15,17,15,13,11, 9, 7, 5]    # -
    #   # xp = [-40,-30,-20,-10,-5,0,5,10,20,30,40]    # 5=>약12도, 10=>28 15=>35, 30=>52
    #   # fp1 = [ 3, 5, 7, 9,11,13,15,17,15,12,10]    # +
    #   # fp2 = [10,12,15,17,15,13,11, 9, 7, 5, 3]    # -      
    #   limit_steers1 = interp( model_sum, xp, fp1 )  # +
    #   limit_steers2 = interp( model_sum, xp, fp2 )  # -
    #   self.angle_steers_des_mpc = self.limit_ctrl1( org_angle_steers_des, limit_steers1, limit_steers2, angle_steers )
      
    # str1 = '#/{} CVs/{} LS1/{} LS2/{} Ang/{} oDES/{} delta1/{} fDES/{} '.format(   
    #           debug_status, model_sum, limit_steers1, limit_steers2, angle_steers, org_angle_steers_des, delta_steer, self.angle_steers_des_mpc)

    # # Conan : 최대 허용 조향각 제어 로직 2.  
    # delta_steer2 = self.angle_steers_des_mpc - angle_steers
    # if delta_steer2 > DST_ANGLE_LIMIT:   
    #   p_angle_steers = angle_steers + DST_ANGLE_LIMIT
    #   self.angle_steers_des_mpc = p_angle_steers
    # elif delta_steer2 < -DST_ANGLE_LIMIT:
    #   m_angle_steers = angle_steers - DST_ANGLE_LIMIT
    #   self.angle_steers_des_mpc = m_angle_steers

    # str2 = 'delta2/{} fDES2/{}'.format(   
    #         delta_steer2, self.angle_steers_des_mpc)
    # self.trRapidCurv.add( str1 + str2 )        

    # Hoya : 가변 sR rate_cost 
    # self.sr_boost_bp = [ 10.0, 15.0, 20.0, 30.0]
    # self.sR_Cost     = [ 1.00, 0.75, 0.60, 0.30] 
    # steerRateCost  = interp(abs(angle_steers), self.sr_boost_bp, self.sR_Cost)

    #  Check for infeasable MPC solution
    mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
    t = sec_since_boot()
    if mpc_nans:
      self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, CP.steerRateCost) # CP.steerRateCost < steerRateCost
      self.cur_state[0].delta = math.radians(angle_steers - angle_offset) / VM.sR

      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
    plan_solution_valid = self.solution_invalid_cnt < 3

    plan_send = messaging.new_message('pathPlan')
    plan_send.valid = sm.all_alive_and_valid(service_list=['carState', 'controlsState', 'liveParameters', 'model'])
    plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
    plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
    plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
    plan_send.pathPlan.lProb = float(self.LP.l_prob)
    plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
    plan_send.pathPlan.rProb = float(self.LP.r_prob)

    plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
    plan_send.pathPlan.rateSteers = float(rate_desired)
    plan_send.pathPlan.angleOffset = float(sm['liveParameters'].angleOffset)
    #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 = desire
    plan_send.pathPlan.laneChangeState = self.lane_change_state
    plan_send.pathPlan.laneChangeDirection = self.lane_change_direction
    plan_send.pathPlan.steerRatio = VM.sR
    plan_send.pathPlan.steerActuatorDelay = CP.steerActuatorDelay    

    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.delta = list(self.mpc_solution[0].delta)
      dat.liveMpc.cost = self.mpc_solution[0].cost
      pm.send('liveMpc', dat)
Example #14
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)
Example #15
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)
Example #16
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

        # dragonpilot
        self.params = Params()
        self.dragon_auto_lc_enabled = False
        self.dragon_auto_lc_allowed = False
        self.dragon_auto_lc_timer = None
        self.dragon_assisted_lc_min_mph = LANE_CHANGE_SPEED_MIN
        self.dragon_auto_lc_min_mph = 60 * CV.MPH_TO_MS
        self.dragon_auto_lc_delay = 2.
        self.last_ts = 0.
        self.dp_last_modified = None
        self.dp_enable_sr_boost = False

        self.dp_steer_ratio = 0.
        self.dp_sr_boost_bp = None
        self.dp_sr_boost_range = None

    def limit_ctrl(self, value, limit, offset):
        p_limit = offset + limit
        m_limit = offset - limit
        if value > p_limit:
            value = p_limit
        elif value < m_limit:
            value = m_limit
        return value

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                         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].delta = 0.0

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

    def update(self, sm, pm, CP, VM):
        # dragonpilot
        cur_time = sec_since_boot()
        if cur_time - self.last_ts >= 5.:
            modified = get_last_modified()
            if self.dp_last_modified != modified:
                self.lane_change_enabled = True if self.params.get(
                    "LaneChangeEnabled", encoding='utf8') == "1" else False
                if self.lane_change_enabled:
                    self.dragon_auto_lc_enabled = True if self.params.get(
                        "DragonEnableAutoLC",
                        encoding='utf8') == "1" else False
                    # adjustable assisted lc min speed
                    try:
                        self.dragon_assisted_lc_min_mph = float(
                            self.params.get("DragonAssistedLCMinMPH",
                                            encoding='utf8'))
                    except (TypeError, ValueError):
                        self.dragon_assisted_lc_min_mph = 45
                    self.dragon_assisted_lc_min_mph *= CV.MPH_TO_MS
                    if self.dragon_assisted_lc_min_mph < 0:
                        self.dragon_assisted_lc_min_mph = 0
                    if self.dragon_auto_lc_enabled:
                        # adjustable auto lc min speed
                        try:
                            self.dragon_auto_lc_min_mph = float(
                                self.params.get("DragonAutoLCMinMPH",
                                                encoding='utf8'))
                        except (TypeError, ValueError):
                            self.dragon_auto_lc_min_mph = 60
                        self.dragon_auto_lc_min_mph *= CV.MPH_TO_MS
                        if self.dragon_auto_lc_min_mph < 0:
                            self.dragon_auto_lc_min_mph = 0
                        # when auto lc is smaller than assisted lc, we set assisted lc to the same speed as auto lc
                        if self.dragon_auto_lc_min_mph < self.dragon_assisted_lc_min_mph:
                            self.dragon_assisted_lc_min_mph = self.dragon_auto_lc_min_mph
                        # adjustable auto lc delay
                        try:
                            self.dragon_auto_lc_delay = float(
                                self.params.get("DragonAutoLCDelay",
                                                encoding='utf8'))
                        except (TypeError, ValueError):
                            self.dragon_auto_lc_delay = 2.
                        if self.dragon_auto_lc_delay < 0:
                            self.dragon_auto_lc_delay = 0
                else:
                    self.dragon_auto_lc_enabled = False

                self.dp_enable_sr_boost = True if self.params.get(
                    "DragonEnableSteerBoost",
                    encoding='utf8') == "1" else False
                if self.dp_enable_sr_boost:
                    try:
                        sr_boost_min = float(
                            self.params.get("DragonSteerBoostMin",
                                            encoding='utf8'))
                        sr_boost_Max = float(
                            self.params.get("DragonSteerBoostMax",
                                            encoding='utf8'))
                        self.dp_sr_boost_range = [sr_boost_min, sr_boost_Max]

                        boost_min_at = float(
                            self.params.get("DragonSteerBoostMinAt",
                                            encoding='utf8'))
                        boost_max_at = float(
                            self.params.get("DragonSteerBoostMaxAt",
                                            encoding='utf8'))
                        self.dp_sr_boost_bp = [boost_min_at, boost_max_at]
                    except (TypeError, ValueError):
                        put_nonblocking("DragonEnableSteerBoost", '0')
                        self.dp_enable_sr_boost = False
                if not self.dp_enable_sr_boost:
                    self.dp_sr_boost_range = [0., 0.]
                    self.dp_sr_boost_bp = [0., 0.]

                self.dp_last_modified = modified
            self.last_ts = cur_time

        v_ego = sm['carState'].vEgo
        angle_steers = sm['carState'].steeringAngle
        active = sm['controlsState'].active

        angle_offset = sm['liveParameters'].angleOffset

        # atom
        v_ego_kph = v_ego * CV.MS_TO_KPH

        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc
        VM.update_params(sm['liveParameters'].stiffnessFactor,
                         sm['liveParameters'].steerRatio)
        curvature_factor = VM.curvature_factor(v_ego)
        boost_rate = (1 + (interp(abs(angle_steers), self.dp_sr_boost_bp,
                                  self.dp_sr_boost_range) /
                           100)) if self.dp_enable_sr_boost else 1
        self.dp_steer_ratio = VM.sR * boost_rate

        self.LP.parse_model(sm['model'])

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

        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 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))

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

            # dragonpilot auto lc
            if not below_lane_change_speed and self.dragon_auto_lc_enabled and v_ego >= self.dragon_auto_lc_min_mph:
                # we allow auto lc when speed reached dragon_auto_lc_min_mph
                self.dragon_auto_lc_allowed = True

                if self.dragon_auto_lc_timer is None:
                    # we only set timer when in preLaneChange state, dragon_auto_lc_delay delay
                    if self.lane_change_state == LaneChangeState.preLaneChange:
                        self.dragon_auto_lc_timer = cur_time + self.dragon_auto_lc_delay
                elif cur_time >= self.dragon_auto_lc_timer:
                    # if timer is up, we set torque_applied to True to fake user input
                    torque_applied = True
            else:
                # if too slow, we reset all the variables
                self.dragon_auto_lc_allowed = False
                self.dragon_auto_lc_timer = None

            # we reset the timers when torque is applied regardless
            if torque_applied:
                self.dragon_auto_lc_timer = None

            # 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:
                    self.lane_change_state = LaneChangeState.laneChangeStarting

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

                # fade out over .2s
                self.lane_change_ll_prob = max(
                    self.lane_change_ll_prob - DT_MDL / 5, 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

                # when finishing, we reset timer to none.
                self.dragon_auto_lc_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

        self.prev_one_blinker = one_blinker

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

        # Turn off lanes during lane change
        if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.l_prob *= self.lane_change_ll_prob
            self.LP.r_prob *= self.lane_change_ll_prob
        self.LP.update_d_poly(v_ego)

        # account for actuation delay
        self.cur_state = calc_states_after_delay(self.cur_state, v_ego,
                                                 angle_steers - angle_offset,
                                                 curvature_factor,
                                                 self.dp_steer_ratio,
                                                 CP.steerActuatorDelay)

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            rate_desired = math.degrees(self.mpc_solution[0].rate[0] *
                                        self.dp_steer_ratio)
        else:
            delta_desired = math.radians(angle_steers -
                                         angle_offset) / self.dp_steer_ratio
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired

        #self.angle_steers_des_mpc = float(math.degrees(delta_desired * self.dp_steer_ratio) + angle_offset)
        #old_angle_steers_des = self.angle_steers_des_mpc
        org_angle_steers_des = float(
            math.degrees(delta_desired * self.steerRatio) + angle_offset)
        self.angle_steers_des_mpc = org_angle_steers_des

        # atom
        if v_ego_kph < 40:
            xp = [0, 5, 20, 40]
            fp2 = [0.3, 0.5, 1, 1.5]
            limit_steers = interp(v_ego_kph, xp, fp2)
            angle_steers_des = self.limit_ctrl(org_angle_steers_des,
                                               limit_steers, angle_steers)
            self.angle_steers_des_mpc = angle_steers_des
        elif self.lane_change_state != LaneChangeState.off:
            self.angle_steers_des_mpc = self.limit_ctrl(
                org_angle_steers_des, 10, angle_steers)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, CP.steerRateCost)
            self.cur_state[0].delta = math.radians(
                angle_steers - angle_offset) / self.dp_steer_ratio

            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
        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', 'model'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

        plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
        plan_send.pathPlan.rateSteers = float(rate_desired)
        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.sensorValid = bool(sm['liveParameters'].sensorValid)
        plan_send.pathPlan.posenetValid = bool(
            sm['liveParameters'].posenetValid)

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

        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.delta = list(self.mpc_solution[0].delta)
            dat.liveMpc.cost = self.mpc_solution[0].cost
            pm.send('liveMpc', dat)
Example #17
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.path_offset_i = 0.0

    self.mpc_frame = 0
    self.sR_delay_counter = 0
    self.steerRatio_new = 0.0
    self.sR_time = 1

    kegman = kegman_conf(CP)
    if kegman.conf['steerRatio'] == "-1":
      self.steerRatio = CP.steerRatio
    else:
      self.steerRatio = float(kegman.conf['steerRatio'])

    if kegman.conf['steerRateCost'] == "-1":
      self.steerRateCost = CP.steerRateCost
    else:
      self.steerRateCost = float(kegman.conf['steerRateCost'])

    self.sR = [float(kegman.conf['steerRatio']), (float(kegman.conf['steerRatio']) + float(kegman.conf['sR_boost']))]
    self.sRBP = [float(kegman.conf['sR_BP0']), float(kegman.conf['sR_BP1'])]

    self.steerRateCost_prev = self.steerRateCost
    self.setup_mpc()

    self.lane_change_state = LaneChangeState.off
    self.lane_change_direction = LaneChangeDirection.none
    self.lane_change_timer = 0.0
    self.prev_one_blinker = False
    self.pre_auto_LCA_timer = 0.0
    self.lane_change_BSM = LaneChangeBSM.off
    self.prev_torque_applied = False

  def setup_mpc(self):
    self.libmpc = libmpc_py.libmpc
    self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, 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].delta = 0.0

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

  def update(self, sm, pm, CP, VM):
    v_ego = sm['carState'].vEgo
    angle_steers = sm['carState'].steeringAngle
    active = sm['controlsState'].active

    angle_offset = sm['liveParameters'].angleOffset

    lca_left = sm['carState'].lcaLeft
    lca_right = sm['carState'].lcaRight

    # Run MPC
    self.angle_steers_des_prev = self.angle_steers_des_mpc
    VM.update_params(sm['liveParameters'].stiffnessFactor, sm['liveParameters'].steerRatio)
    curvature_factor = VM.curvature_factor(v_ego)

    # Get steerRatio and steerRateCost from kegman.json every x seconds
    self.mpc_frame += 1
    if self.mpc_frame % 500 == 0:
      # live tuning through /data/openpilot/tune.py overrides interface.py settings
      kegman = kegman_conf()
      if kegman.conf['tuneGernby'] == "1":
        self.steerRateCost = float(kegman.conf['steerRateCost'])
        if self.steerRateCost != self.steerRateCost_prev:
          self.setup_mpc()
          self.steerRateCost_prev = self.steerRateCost

        self.sR = [float(kegman.conf['steerRatio']), (float(kegman.conf['steerRatio']) + float(kegman.conf['sR_boost']))]
        self.sRBP = [float(kegman.conf['sR_BP0']), float(kegman.conf['sR_BP1'])]
        self.sR_time = int(float(kegman.conf['sR_time'])) * 100

      self.mpc_frame = 0

    if v_ego > 11.111:
      # boost steerRatio by boost amount if desired steer angle is high
      self.steerRatio_new = interp(abs(angle_steers), self.sRBP, self.sR)

      self.sR_delay_counter += 1
      if self.sR_delay_counter % self.sR_time != 0:
        if self.steerRatio_new > self.steerRatio:
          self.steerRatio = self.steerRatio_new
      else:
        self.steerRatio = self.steerRatio_new
        self.sR_delay_counter = 0
    else:
      self.steerRatio = self.sR[0]

    print("steerRatio = ", self.steerRatio)

    self.LP.parse_model(sm['model'])

    # Lane change logic
    one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
    below_lane_change_speed = v_ego < 60 * CV.KPH_TO_MS

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

    # cancel LCA when driver takeover during lane change 2020-03-11
    # if (not active) or (self.lane_change_timer > 10.0) or (not one_blinker) or (not self.lane_change_enabled):
    if (not active) or (self.lane_change_timer > 10.0) or (not one_blinker) or (not self.lane_change_enabled) or (sm['carState'].steeringPressed and ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.right) or (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.left))):
      self.lane_change_state = LaneChangeState.off
      self.lane_change_direction = LaneChangeDirection.none
    else:
      if sm['carState'].leftBlinker:
        self.lane_change_direction = LaneChangeDirection.left
      elif sm['carState'].rightBlinker:
        self.lane_change_direction = LaneChangeDirection.right

      if self.lane_change_direction == LaneChangeDirection.left:
        torque_applied = sm['carState'].steeringTorque > 0 and sm['carState'].steeringPressed
        if CP.autoLcaEnabled and 0.6 > self.pre_auto_LCA_timer > 0.1 and not lca_left:
          torque_applied = True # Enable auto LCA only once after 1 sec 
      else:
        torque_applied = sm['carState'].steeringTorque < 0 and sm['carState'].steeringPressed
        if CP.autoLcaEnabled and 0.6 > self.pre_auto_LCA_timer > 0.1 and not lca_right:
          torque_applied = True # Enable auto LCA only once after 1 sec 

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

      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

      # 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:
          if self.prev_torque_applied or self.lane_change_direction == LaneChangeDirection.left and not lca_left or \
                  self.lane_change_direction == LaneChangeDirection.right and not lca_right:
            self.lane_change_state = LaneChangeState.laneChangeStarting
          else:
            if self.pre_auto_LCA_timer < 10.:
              self.pre_auto_LCA_timer = 10.
        else:
          if self.pre_auto_LCA_timer > 10.3:
            self.prev_torque_applied = True

      # bsm
      elif self.lane_change_state == LaneChangeState.laneChangeStarting:
        if lca_left and self.lane_change_direction == LaneChangeDirection.left and not self.prev_torque_applied:
          self.lane_change_BSM = LaneChangeBSM.left
          self.lane_change_state = LaneChangeState.preLaneChange
        elif lca_right and self.lane_change_direction == LaneChangeDirection.right and not self.prev_torque_applied:
          self.lane_change_BSM = LaneChangeBSM.right
          self.lane_change_state = LaneChangeState.preLaneChange
        else:
          # starting
          self.lane_change_BSM = LaneChangeBSM.off
          if self.lane_change_state == LaneChangeState.laneChangeStarting and lane_change_prob > 0.5:
            self.lane_change_state = LaneChangeState.laneChangeFinishing

      # starting
      #elif self.lane_change_state == LaneChangeState.laneChangeStarting and lane_change_prob > 0.5:
        #self.lane_change_state = LaneChangeState.laneChangeFinishing

      # finishing
      elif self.lane_change_state == LaneChangeState.laneChangeFinishing and lane_change_prob < 0.2:
        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
      if self.lane_change_BSM == LaneChangeBSM.right:
        if not lca_right:
          self.lane_change_BSM = LaneChangeBSM.off
      if self.lane_change_BSM == LaneChangeBSM.left:
        if not lca_left:
          self.lane_change_BSM = LaneChangeBSM.off
    else:
      self.lane_change_timer += DT_MDL

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

    self.prev_one_blinker = one_blinker

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

    # Turn off lanes during lane change
    if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
      self.LP.l_prob = 0.
      self.LP.r_prob = 0.
      self.libmpc.init_weights(MPC_COST_LAT.PATH / 10.0, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, self.steer_rate_cost)
    else:
      self.libmpc.init_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, self.steer_rate_cost)

    self.LP.update_d_poly(v_ego)


    # TODO: Check for active, override, and saturation
    # if active:
    #   self.path_offset_i += self.LP.d_poly[3] / (60.0 * 20.0)
    #   self.path_offset_i = clip(self.path_offset_i, -0.5,  0.5)
    #   self.LP.d_poly[3] += self.path_offset_i
    # else:
    #   self.path_offset_i = 0.0

    # account for actuation delay
    self.cur_state = calc_states_after_delay(self.cur_state, v_ego, angle_steers - angle_offset, curvature_factor, self.steerRatio, CP.steerActuatorDelay)

    v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
    self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                        list(self.LP.l_poly), list(self.LP.r_poly), list(self.LP.d_poly),
                        self.LP.l_prob, self.LP.r_prob, curvature_factor, v_ego_mpc, self.LP.lane_width)

    # reset to current steer angle if not active or overriding
    if active:
      delta_desired = self.mpc_solution[0].delta[1]
      rate_desired = math.degrees(self.mpc_solution[0].rate[0] * self.steerRatio)
    else:
      delta_desired = math.radians(angle_steers - angle_offset) / self.steerRatio
      rate_desired = 0.0

    self.cur_state[0].delta = delta_desired

    self.angle_steers_des_mpc = float(math.degrees(delta_desired * self.steerRatio) + angle_offset)

    #  Check for infeasable MPC solution
    mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
    t = sec_since_boot()
    if mpc_nans:
      self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, self.steerRateCost)
      self.cur_state[0].delta = math.radians(angle_steers - angle_offset) / self.steerRatio

      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
    plan_solution_valid = self.solution_invalid_cnt < 2

    plan_send = messaging.new_message()
    plan_send.init('pathPlan')
    plan_send.valid = sm.all_alive_and_valid(service_list=['carState', 'controlsState', 'liveParameters', 'model'])
    plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
    plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
    plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
    plan_send.pathPlan.lProb = float(self.LP.l_prob)
    plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
    plan_send.pathPlan.rProb = float(self.LP.r_prob)

    plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
    plan_send.pathPlan.rateSteers = float(rate_desired)
    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.sensorValid = bool(sm['liveParameters'].sensorValid)
    plan_send.pathPlan.posenetValid = bool(sm['liveParameters'].posenetValid)

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

    pm.send('pathPlan', plan_send)

    if LOG_MPC:
      dat = messaging.new_message()
      dat.init('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.delta = list(self.mpc_solution[0].delta)
      dat.liveMpc.cost = self.mpc_solution[0].cost
      pm.send('liveMpc', dat)
Example #18
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.params = Params()
        kyd = kyd_conf(CP)
        if kyd.conf['steerRatio'] == "-1":
            self.steerRatio = CP.steerRatio
        else:
            self.steerRatio = float(kyd.conf['steerRatio'])

        if kyd.conf['steerRateCost'] == "-1":
            self.steer_rate_cost = CP.steerRateCost
        else:
            self.steer_rate_cost = float(kyd.conf['steerRateCost'])

        self.kyd_steerRatio = None
        self.sRBP = [0., 0.]
        self.sRBoost = [0., 0.]

        # Lane change
        self.lane_change_enabled = self.params.get('LaneChangeEnabled') == b'1'
        self.lane_change_auto_delay = self.params.get_OpkrAutoLanechangedelay(
        )  #int( self.params.get('OpkrAutoLanechangedelay') )

        self.lane_change_state = LaneChangeState.off
        self.lane_change_direction = LaneChangeDirection.none
        self.lane_change_run_timer = 0.0
        self.lane_change_wait_timer = 0.0
        self.lane_change_ll_prob = 1.0
        self.prev_one_blinker = False

        self.param_OpkrEnableLearner = int(
            self.params.get('OpkrEnableLearner'))

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                         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].delta = 0.0

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

    def update(self, sm, pm, CP, VM):

        v_ego = sm['carState'].vEgo
        angle_steers = sm['carState'].steeringAngle
        active = sm['controlsState'].active

        angle_offset = sm['liveParameters'].angleOffset

        if not self.param_OpkrEnableLearner:
            kyd = kyd_conf()
            #self.steer_rate_cost = float(kyd.conf['steerRateCost'])
            self.sRBP = kyd.conf['sR_BP']
            self.sRBoost = kyd.conf['sR_Boost']
            boost_rate = interp(abs(angle_steers), self.sRBP, self.sRBoost)
            self.kyd_steerRatio = self.steerRatio + boost_rate

            self.sR_Cost = [
                1.0, 0.90, 0.81, 0.73, 0.66, 0.60, 0.54, 0.48, 0.36, 0.275,
                0.20, 0.175, 0.15, 0.11, 0.05
            ]
            #self.sR_Cost = [0.75,0.67,0.60,0.54,0.48,0.425,0.37,0.32,0.24,0.19,0.15,0.14,0.13,0.11,0.05]
            #self.sR_Cost = [0.50,0.46,0.425,0.395,0.37,0.34,0.315,0.29,0.23,0.185,0.15,0.14,0.13,0.11,0.05]
            #steerRatio = 10.0
            #"sR_BP": [0.0,2.0,4.0,6.0,8.0,10.0,12.0,14.0,20.0,27.0,35.0,40.0,45.0,60.0,100],
            #"sR_Boost": [0.0,0.7,1.3,1.9,2.5,3.05,3.55,4.0,5.0,5.7,6.2,6.35,6.4,6.5,8.0],
            self.steer_rate_cost = interp(abs(angle_steers), self.sRBP,
                                          self.sR_Cost)

        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc

        # 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)

        self.LP.parse_model(sm['model'])

        # 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_run_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))

            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_ll_prob = max(
                    self.lane_change_ll_prob - 1.5 * 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.laneChangeDone

            # done
            elif self.lane_change_state == LaneChangeState.laneChangeDone:
                if not one_blinker:
                    self.lane_change_state = LaneChangeState.off

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

        self.prev_one_blinker = one_blinker

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

        # Turn off lanes during lane change
        if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.l_prob *= self.lane_change_ll_prob
            self.LP.r_prob *= self.lane_change_ll_prob
        self.LP.update_d_poly(v_ego)

        # account for actuation delay
        if self.param_OpkrEnableLearner:
            self.cur_state = calc_states_after_delay(
                self.cur_state, v_ego, angle_steers - angle_offset,
                curvature_factor, VM.sR, CP.steerActuatorDelay)
        else:
            self.cur_state = calc_states_after_delay(
                self.cur_state, v_ego, angle_steers - angle_offset,
                curvature_factor, self.kyd_steerRatio, CP.steerActuatorDelay)

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            if self.param_OpkrEnableLearner:
                rate_desired = math.degrees(self.mpc_solution[0].rate[0] *
                                            VM.sR)
            else:
                rate_desired = math.degrees(self.mpc_solution[0].rate[0] *
                                            self.kyd_steerRatio)
        else:
            if self.param_OpkrEnableLearner:
                delta_desired = math.radians(angle_steers -
                                             angle_offset) / VM.sR
            else:
                delta_desired = math.radians(
                    angle_steers - angle_offset) / self.kyd_steerRatio
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired
        if self.param_OpkrEnableLearner:
            self.angle_steers_des_mpc = float(
                math.degrees(delta_desired * VM.sR) + angle_offset)
        else:
            self.angle_steers_des_mpc = float(
                math.degrees(delta_desired * self.kyd_steerRatio) +
                angle_offset)
        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, self.steer_rate_cost)
            if self.param_OpkrEnableLearner:
                self.cur_state[0].delta = math.radians(angle_steers -
                                                       angle_offset) / VM.sR
            else:
                self.cur_state[0].delta = math.radians(
                    angle_steers - angle_offset) / self.kyd_steerRatio

            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
        plan_solution_valid = self.solution_invalid_cnt < 3

        plan_send = messaging.new_message('pathPlan')
        plan_send.valid = sm.all_alive_and_valid(service_list=[
            'carState', 'controlsState', 'liveParameters', 'model'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

        plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
        plan_send.pathPlan.rateSteers = float(rate_desired)
        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 = desire
        plan_send.pathPlan.laneChangeState = self.lane_change_state
        plan_send.pathPlan.laneChangeDirection = self.lane_change_direction

        if not self.param_OpkrEnableLearner:
            plan_send.pathPlan.steerRatio = float(self.kyd_steerRatio)

        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.delta = list(self.mpc_solution[0].delta)
            dat.liveMpc.cost = self.mpc_solution[0].cost
            pm.send('liveMpc', dat)
Example #19
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)
Example #20
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'

        if int(Params().get('OpkrAutoLaneChangeDelay')) == 0:
            self.lane_change_auto_delay = 0.0
        elif int(Params().get('OpkrAutoLaneChangeDelay')) == 1:
            self.lane_change_auto_delay = 0.5
        elif int(Params().get('OpkrAutoLaneChangeDelay')) == 2:
            self.lane_change_auto_delay = 1.0
        elif int(Params().get('OpkrAutoLaneChangeDelay')) == 3:
            self.lane_change_auto_delay = 1.5
        elif int(Params().get('OpkrAutoLaneChangeDelay')) == 4:
            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.mpc_frame = 0

        self.lane_change_adjust = [0.2, 1.3]
        self.lane_change_adjust_vel = [16, 30]
        self.lane_change_adjust_new = 0.0

        self.new_steerRatio = CP.steerRatio

        self.angle_offset_select = int(Params().get('OpkrAngleOffsetSelect'))

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                         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].delta = 0.0

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

    def update(self, sm, pm, CP, VM):
        v_ego = sm['carState'].vEgo
        angle_steers = sm['carState'].steeringAngle
        active = sm['controlsState'].active

        #saturated_steering = sm['controlsState'].steerSaturated
        #live_steerratio = sm['liveParameters'].steerRatio
        mode_select = sm['carState'].cruiseState.modeSel
        lateral_control_method = sm['controlsState'].lateralControlMethod
        if lateral_control_method == 0:
            output_scale = sm[
                'controlsState'].lateralControlState.pidState.output
        elif lateral_control_method == 1:
            output_scale = sm[
                'controlsState'].lateralControlState.indiState.output
        elif lateral_control_method == 2:
            output_scale = sm[
                'controlsState'].lateralControlState.lqrState.output
        angle_offset = sm['liveParameters'].angleOffset

        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc

        if abs(output_scale) >= 1 and v_ego > 8:
            self.mpc_frame += 1
            if self.mpc_frame % 5 == 0:
                self.new_steerRatio += (round(v_ego, 1) * 0.01)
                if self.new_steerRatio >= 18.0:
                    self.new_steerRatio = 18.0
                self.mpc_frame = 0
        else:
            self.mpc_frame += 1
            if self.mpc_frame % 5 == 0:
                self.new_steerRatio -= 0.2
                if self.new_steerRatio <= CP.steerRatio:
                    self.new_steerRatio = CP.steerRatio
                self.mpc_frame = 0

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

        curvature_factor = VM.curvature_factor(v_ego)

        self.LP.parse_model(sm['model'])

        # 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 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))

            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.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.laneChangeDone

            # done
            elif self.lane_change_state == LaneChangeState.laneChangeDone:
                if not one_blinker:
                    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

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

        # Turn off lanes during lane change
        if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.l_prob *= self.lane_change_ll_prob
            self.LP.r_prob *= self.lane_change_ll_prob
        self.LP.update_d_poly(v_ego, sm)

        # account for actuation delay
        if mode_select == 3:
            self.cur_state = calc_states_after_delay(
                self.cur_state, v_ego, angle_steers - angle_offset,
                curvature_factor, VM.sR, (CP.steerActuatorDelay + 0.05))
        else:
            self.cur_state = calc_states_after_delay(
                self.cur_state, v_ego, angle_steers - angle_offset,
                curvature_factor, VM.sR, CP.steerActuatorDelay)

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
        else:
            delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired

        self.angle_steers_des_mpc = float(
            math.degrees(delta_desired * VM.sR) + angle_offset)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, CP.steerRateCost)
            self.cur_state[0].delta = math.radians(angle_steers -
                                                   angle_offset) / VM.sR

            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
        plan_solution_valid = self.solution_invalid_cnt < 3

        plan_send = messaging.new_message('pathPlan')
        plan_send.valid = sm.all_alive_and_valid(service_list=[
            'carState', 'controlsState', 'liveParameters', 'model'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

        plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
        plan_send.pathPlan.rateSteers = float(rate_desired)
        if self.angle_offset_select == 0:
            plan_send.pathPlan.angleOffset = float(
                sm['liveParameters'].angleOffsetAverage)
        else:
            plan_send.pathPlan.angleOffset = float(
                sm['liveParameters'].angleOffset)
        plan_send.pathPlan.mpcSolutionValid = bool(plan_solution_valid)
        plan_send.pathPlan.paramsValid = bool(sm['liveParameters'].valid)

        plan_send.pathPlan.desire = desire
        plan_send.pathPlan.laneChangeState = self.lane_change_state
        plan_send.pathPlan.laneChangeDirection = self.lane_change_direction
        plan_send.pathPlan.steerRatio = VM.sR
        plan_send.pathPlan.steerActuatorDelay = CP.steerActuatorDelay
        plan_send.pathPlan.outputScale = output_scale

        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.delta = list(self.mpc_solution[0].delta)
            dat.liveMpc.cost = self.mpc_solution[0].cost
            pm.send('liveMpc', dat)
Example #21
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)
Example #22
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.path_offset_i = 0.0

    self.mpc_frame = 0
    self.sR_delay_counter = 0
    self.steerRatio_new = 0.0
    self.sR_time = 1

    kegman = kegman_conf(CP)
    if kegman.conf['steerRatio'] == "-1":
      self.steerRatio = CP.steerRatio
    else:
      self.steerRatio = float(kegman.conf['steerRatio'])

    if kegman.conf['steerRateCost'] == "-1":
      self.steerRateCost = CP.steerRateCost
    else:
      self.steerRateCost = float(kegman.conf['steerRateCost'])

    self.sR = [float(kegman.conf['steerRatio']), (float(kegman.conf['steerRatio']) + float(kegman.conf['sR_boost']))]
    self.sRBP = [float(kegman.conf['sR_BP0']), float(kegman.conf['sR_BP1'])]

    self.steerRateCost_prev = self.steerRateCost
    self.setup_mpc()

    self.alc_nudge_less = bool(int(kegman.conf['ALCnudgeLess']))
    self.alc_min_speed = float(kegman.conf['ALCminSpeed'])
    self.alc_timer = float(kegman.conf['ALCtimer'])

    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



  def setup_mpc(self):
    self.libmpc = libmpc_py.libmpc
    self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, 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].delta = 0.0

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


  def update(self, sm, pm, CP, VM):

    v_ego = sm['carState'].vEgo
    angle_steers = sm['carState'].steeringAngle
    active = sm['controlsState'].active

    angle_offset = sm['liveParameters'].angleOffset

    # Run MPC
    self.angle_steers_des_prev = self.angle_steers_des_mpc

    # 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)

    # Get steerRatio and steerRateCost from kegman.json every x seconds
    self.mpc_frame += 1
    if self.mpc_frame % 500 == 0:
      # live tuning through /data/openpilot/tune.py overrides interface.py settings
      kegman = kegman_conf()
      if kegman.conf['tuneGernby'] == "1":
        self.steerRateCost = float(kegman.conf['steerRateCost'])
        if self.steerRateCost != self.steerRateCost_prev:
          self.setup_mpc()
          self.steerRateCost_prev = self.steerRateCost

        self.sR = [float(kegman.conf['steerRatio']), (float(kegman.conf['steerRatio']) + float(kegman.conf['sR_boost']))]
        self.sRBP = [float(kegman.conf['sR_BP0']), float(kegman.conf['sR_BP1'])]
        self.sR_time = int(float(kegman.conf['sR_time'])) * 100

      self.mpc_frame = 0

    if v_ego > 11.111:
      # boost steerRatio by boost amount if desired steer angle is high
      self.steerRatio_new = interp(abs(angle_steers), self.sRBP, self.sR)

      self.sR_delay_counter += 1
      if self.sR_delay_counter % self.sR_time != 0:
        if self.steerRatio_new > self.steerRatio:
          self.steerRatio = self.steerRatio_new
      else:
        self.steerRatio = self.steerRatio_new
        self.sR_delay_counter = 0
    else:
      self.steerRatio = self.sR[0]

    #print("steerRatio = ", self.steerRatio)

    self.LP.parse_model(sm['model'])

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

    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

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

    # Turn off lanes during lane change
    if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
      self.LP.l_prob *= self.lane_change_ll_prob
      self.LP.r_prob *= self.lane_change_ll_prob
      self.libmpc.init_weights(MPC_COST_LAT.PATH / 3.0, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, self.steer_rate_cost)
    else:
      self.libmpc.init_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, self.steer_rate_cost)

    self.LP.update_d_poly(v_ego)


    # TODO: Check for active, override, and saturation
    # if active:
    #   self.path_offset_i += self.LP.d_poly[3] / (60.0 * 20.0)
    #   self.path_offset_i = clip(self.path_offset_i, -0.5,  0.5)
    #   self.LP.d_poly[3] += self.path_offset_i
    # else:
    #   self.path_offset_i = 0.0

    # account for actuation delay
    self.cur_state = calc_states_after_delay(self.cur_state, v_ego, angle_steers - angle_offset, curvature_factor, VM.sR, CP.steerActuatorDelay)

    v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
    self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                        list(self.LP.l_poly), list(self.LP.r_poly), list(self.LP.d_poly),
                        self.LP.l_prob, self.LP.r_prob, curvature_factor, v_ego_mpc, self.LP.lane_width)

    # reset to current steer angle if not active or overriding
    if active:
      delta_desired = self.mpc_solution[0].delta[1]
      rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
    else:
      delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
      rate_desired = 0.0

    self.cur_state[0].delta = delta_desired

    self.angle_steers_des_mpc = float(math.degrees(delta_desired * VM.sR) + angle_offset)

    #  Check for infeasable MPC solution
    mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
    t = sec_since_boot()
    if mpc_nans:
      self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, self.steerRateCost)
      self.cur_state[0].delta = math.radians(angle_steers - angle_offset) / VM.sR

      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
    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', 'model'])
    plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
    plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
    plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
    plan_send.pathPlan.lProb = float(self.LP.l_prob)
    plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
    plan_send.pathPlan.rProb = float(self.LP.r_prob)

    plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
    plan_send.pathPlan.rateSteers = float(rate_desired)
    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 = 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.delta = list(self.mpc_solution[0].delta)
      dat.liveMpc.cost = self.mpc_solution[0].cost
      pm.send('liveMpc', dat)
Example #23
0
class PathPlanner():
    def __init__(self, CP):
        self.LP = LanePlanner()

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

        self.steerRatio = CP.steerRatio
        self.steerActuatorDelay = CP.steerActuatorDelay

        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.steerRatio_last = 0
        self.params = Params()
        self.lane_change_auto_delay = 0
        self.lane_change_run_timer = 0.0
        self.lane_change_wait_timer = 0.0

        # atom
        self.trPATH = trace1.Loger("path")
        self.trLearner = trace1.Loger("Learner")
        self.trpathPlan = trace1.Loger("pathPlan")

        self.atom_timer_cnt = 0
        self.atom_steer_ratio = None
        self.atom_sr_boost_bp = [0., 0.]
        self.atom_sr_boost_range = [0., 0.]

        self.carParams_valid = False

        self.m_avg = ma.MoveAvg()

    def limit_ctrl(self, value, limit, offset):
        p_limit = offset + limit
        m_limit = offset - limit
        if value > p_limit:
            value = p_limit
        elif value < m_limit:
            value = m_limit
        return value

    def limit_ctrl1(self, value, limit1, limit2, offset):
        p_limit = offset + limit1
        m_limit = offset - limit2
        if value > p_limit:
            value = p_limit
        elif value < m_limit:
            value = m_limit
        return value

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                         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].delta = 0.0

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

    def atom_tune(self, v_ego_kph, sr_value, atomTuning):
        self.sr_KPH = atomTuning.sRKPH
        self.sr_BPV = atomTuning.sRBPV
        self.sr_steerRatioV = atomTuning.sRsteerRatioV
        self.sr_SteerRatio = []

        nPos = 0
        for steerRatio in self.sr_BPV:  # steerRatio
            self.sr_SteerRatio.append(
                interp(sr_value, steerRatio, self.sr_steerRatioV[nPos]))
            nPos += 1
            if nPos > 20:
                break

        steerRatio = interp(v_ego_kph, self.sr_KPH, self.sr_SteerRatio)

        return steerRatio

    def atom_actuatorDelay(self, v_ego_kph, sr_value, atomTuning):
        self.sr_KPH = atomTuning.sRKPH
        self.sr_BPV = atomTuning.sRBPV
        self.sr_ActuatorDelayV = atomTuning.sRsteerActuatorDelayV
        self.sr_ActuatorDelay = []

        nPos = 0
        for steerRatio in self.sr_BPV:
            self.sr_ActuatorDelay.append(
                interp(sr_value, steerRatio, self.sr_ActuatorDelayV[nPos]))
            nPos += 1
            if nPos > 10:
                break

        actuatorDelay = interp(v_ego_kph, self.sr_KPH, self.sr_ActuatorDelay)

        return actuatorDelay

    def atom_steer(self, sr_value, sr_up, sr_dn):
        delta = sr_value - self.steerRatio_last

        sr_up = min(abs(delta), sr_up)
        sr_dn = min(abs(delta), sr_dn)
        steerRatio = self.steerRatio_last
        if delta > 0:
            steerRatio += sr_up
        elif delta < 0:
            steerRatio -= sr_dn

        self.steerRatio_last = steerRatio
        return steerRatio

    def update(self, sm, pm, CP, VM):
        atomTuning = CP.atomTuning
        lateralsRatom = CP.lateralsRatom

        laneLineProbs = sm['modelV2'].laneLineProbs

        leftLaneProb = False  # laneLineProbs[0] < 0.01
        rightLaneProb = False  # laneLineProbs[3] < 0.01

        #if laneLineProbs[0] < 0.01:
        #  leftLaneProb =  True

        #if laneLineProbs[3] < 0.01:
        #  rightLaneProb =  True

        cruiseState = sm['carState'].cruiseState
        leftBlindspot = sm['carState'].leftBlindspot
        rightBlindspot = sm['carState'].rightBlindspot

        v_ego = sm['carState'].vEgo
        angle_steers = sm['carState'].steeringAngle
        steeringPressed = sm['carState'].steeringPressed
        steeringTorque = sm['carState'].steeringTorque
        active = sm['controlsState'].active

        angle_offset = sm['liveParameters'].angleOffset

        v_ego_kph = v_ego * CV.MS_TO_KPH
        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc

        # 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)

        self.LP.parse_model(sm['model'])

        # 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 = steeringPressed and \
                             ((steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                              (steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))

            blindspot_detected = (
                (leftBlindspot
                 and self.lane_change_direction == LaneChangeDirection.left) or
                (rightBlindspot
                 and self.lane_change_direction == LaneChangeDirection.right))

            laneLineProbs_detected = (
                (leftLaneProb
                 and self.lane_change_direction == LaneChangeDirection.left) or
                (rightLaneProb
                 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 cruiseState.cruiseSwState == Buttons.CANCEL:
                self.lane_change_state = LaneChangeState.off
                self.lane_change_ll_prob = 1.0

            elif 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 and not laneLineProbs_detected:
                    self.lane_change_state = LaneChangeState.laneChangeStarting

            # starting
            elif self.lane_change_state == LaneChangeState.laneChangeStarting:
                # fade out over .5s
                xp = [40, 70]
                fp2 = [1, 2]
                lane_time = interp(v_ego_kph, xp, fp2)
                self.lane_change_ll_prob = max(
                    self.lane_change_ll_prob - lane_time * 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

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

        # Turn off lanes during lane change
        if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.l_prob *= self.lane_change_ll_prob
            self.LP.r_prob *= self.lane_change_ll_prob
        self.LP.update_d_poly(v_ego, lateralsRatom.cameraOffset)

        # account for actuation delay
        self.cur_state = calc_states_after_delay(self.cur_state, v_ego,
                                                 angle_steers - angle_offset,
                                                 curvature_factor, VM.sR,
                                                 CP.steerActuatorDelay)

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
        else:
            delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired

        self.angle_steers_des_mpc = float(
            math.degrees(delta_desired * VM.sR) + angle_offset)

        # atom
        org_angle_steers_des = self.angle_steers_des_mpc
        if self.lane_change_state == LaneChangeState.laneChangeStarting:
            xp = [40, 70]
            fp2 = [5, 8]
            limit_steers = interp(v_ego_kph, xp, fp2)
            self.angle_steers_des_mpc = self.limit_ctrl(
                org_angle_steers_des, limit_steers, angle_steers)
        elif steeringPressed:
            delta_steer = org_angle_steers_des - angle_steers
            if angle_steers > 10 and steeringTorque > 0:
                delta_steer = max(delta_steer, 0)
                delta_steer = min(delta_steer, DST_ANGLE_LIMIT)
                self.angle_steers_des_mpc = angle_steers + delta_steer
            elif angle_steers < -10 and steeringTorque < 0:
                delta_steer = min(delta_steer, 0)
                delta_steer = max(delta_steer, -DST_ANGLE_LIMIT)
                self.angle_steers_des_mpc = angle_steers + delta_steer
            else:
                if steeringTorque < 0:  # right
                    if delta_steer > 0:
                        self.angle_steers_des_mpc = self.limit_ctrl(
                            org_angle_steers_des, DST_ANGLE_LIMIT,
                            angle_steers)
                elif steeringTorque > 0:  # left
                    if delta_steer < 0:
                        self.angle_steers_des_mpc = self.limit_ctrl(
                            org_angle_steers_des, DST_ANGLE_LIMIT,
                            angle_steers)

        elif v_ego_kph < 30:  # 30
            xp = [3, 10, 30]
            fp2 = [1, 3, 5]
            limit_steers = interp(v_ego_kph, xp, fp2)
            self.angle_steers_des_mpc = self.limit_ctrl(
                org_angle_steers_des, limit_steers, angle_steers)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, CP.steerRateCost)
            self.cur_state[0].delta = math.radians(angle_steers -
                                                   angle_offset) / VM.sR

            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
        plan_solution_valid = self.solution_invalid_cnt < 3

        plan_send = messaging.new_message('pathPlan')
        plan_send.valid = sm.all_alive_and_valid(service_list=[
            'carState', 'controlsState', 'liveParameters', 'model'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

        plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
        plan_send.pathPlan.rateSteers = float(rate_desired)
        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 = 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.delta = list(self.mpc_solution[0].delta)
            dat.liveMpc.cost = self.mpc_solution[0].cost
            pm.send('liveMpc', dat)
Example #24
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.pre_auto_LCA_timer = 0.0
        self.lane_change_Blocked = LaneChangeBlocked.off
        self.prev_torque_applied = False

    def setup_mpc(self):
        self.libmpc = libmpc_py.libmpc
        self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                         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].delta = 0.0

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

    def update(self, sm, pm, CP, VM):
        v_ego = sm['carState'].vEgo
        angle_steers = sm['carState'].steeringAngle
        active = sm['controlsState'].active

        angle_offset = sm['liveParameters'].angleOffset

        lca_left = sm['carState'].leftBlindspot
        lca_right = sm['carState'].rightBlindspot

        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc
        VM.update_params(sm['liveParameters'].stiffnessFactor,
                         sm['liveParameters'].steerRatio)
        curvature_factor = VM.curvature_factor(v_ego)

        self.LP.parse_model(sm['model'])

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

        left_BlindSpot = sm['carState'].leftBlindspot
        right_BlindSpot = sm['carState'].rightBlindspot

        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:
            if sm['carState'].leftBlinker:
                self.lane_change_direction = LaneChangeDirection.left
            elif sm['carState'].rightBlinker:
                self.lane_change_direction = LaneChangeDirection.right

            if self.lane_change_direction == LaneChangeDirection.left:
                torque_applied = sm['carState'].steeringTorque > 0 and sm[
                    'carState'].steeringPressed
                if CP.autoLcaEnabled and 2.5 > self.pre_auto_LCA_timer > 2.0 and not left_BlindSpot:
                    torque_applied = True  # Enable auto LCA only once after 2 sec
            else:
                torque_applied = sm['carState'].steeringTorque < 0 and sm[
                    'carState'].steeringPressed
                if CP.autoLcaEnabled and 2.5 > self.pre_auto_LCA_timer > 2.0 and not right_BlindSpot:
                    torque_applied = True  # Enable auto LCA only once after 2 sec

            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
                    self.lane_change_Blocked = LaneChangeBlocked.off
                elif torque_applied:
                    if self.prev_torque_applied or self.lane_change_direction == LaneChangeDirection.left and not left_BlindSpot or \
                            self.lane_change_direction == LaneChangeDirection.right and not right_BlindSpot:
                        self.lane_change_state = LaneChangeState.laneChangeStarting
                        self.lane_change_Blocked = LaneChangeBlocked.off
                    else:
                        if not self.prev_torque_applied:
                            if left_BlindSpot:
                                self.lane_change_Blocked = LaneChangeBlocked.left
                            elif right_BlindSpot:
                                self.lane_change_Blocked = LaneChangeBlocked.right
                        if self.pre_auto_LCA_timer < 10.:
                            self.pre_auto_LCA_timer = 10.
                else:
                    if not (left_BlindSpot or right_BlindSpot):
                        self.lane_change_Blocked = LaneChangeBlocked.off
                    if self.pre_auto_LCA_timer > 10.3:
                        self.prev_torque_applied = True

            # 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

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

        self.prev_one_blinker = one_blinker

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

        # Turn off lanes during lane change
        if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.l_prob *= self.lane_change_ll_prob
            self.LP.r_prob *= self.lane_change_ll_prob

        self.LP.update_d_poly(v_ego)

        # account for actuation delay
        self.cur_state = calc_states_after_delay(self.cur_state, v_ego,
                                                 angle_steers - angle_offset,
                                                 curvature_factor, VM.sR,
                                                 CP.steerActuatorDelay)

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
        else:
            delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired

        self.angle_steers_des_mpc = float(
            math.degrees(delta_desired * VM.sR) + angle_offset)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, CP.steerRateCost)
            self.cur_state[0].delta = math.radians(angle_steers -
                                                   angle_offset) / VM.sR

            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
        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', 'model'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

        plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
        plan_send.pathPlan.rateSteers = float(rate_desired)
        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 = desire
        plan_send.pathPlan.laneChangeState = self.lane_change_state
        plan_send.pathPlan.laneChangeDirection = self.lane_change_direction
        plan_send.pathPlan.laneChangeBlocked = self.lane_change_Blocked

        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.delta = list(self.mpc_solution[0].delta)
            dat.liveMpc.cost = self.mpc_solution[0].cost
            pm.send('liveMpc', dat)
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)