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)
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)
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)
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)
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)
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)
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)
class LateralPlanner: def __init__(self, use_lanelines=True, wide_camera=False): self.use_lanelines = use_lanelines self.LP = LanePlanner(wide_camera) self.DH = DesireHelper() self.last_cloudlog_t = 0 self.solution_invalid_cnt = 0 self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3)) self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3)) self.plan_yaw = np.zeros((TRAJECTORY_SIZE, )) self.t_idxs = np.arange(TRAJECTORY_SIZE) self.y_pts = np.zeros(TRAJECTORY_SIZE) self.lat_mpc = LateralMpc() self.reset_mpc(np.zeros(4)) def reset_mpc(self, x0=np.zeros(4)): self.x0 = x0 self.lat_mpc.reset(x0=self.x0) def update(self, sm): v_ego = sm['carState'].vEgo measured_curvature = sm['controlsState'].curvature # Parse model predictions md = sm['modelV2'] self.LP.parse_model(md) if len(md.position.x) == TRAJECTORY_SIZE and len( md.orientation.x) == TRAJECTORY_SIZE: self.path_xyz = np.column_stack( [md.position.x, md.position.y, md.position.z]) self.t_idxs = np.array(md.position.t) self.plan_yaw = list(md.orientation.z) if len(md.position.xStd) == TRAJECTORY_SIZE: self.path_xyz_stds = np.column_stack( [md.position.xStd, md.position.yStd, md.position.zStd]) # Lane change logic lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob self.DH.update(sm['carState'], sm['controlsState'].active, lane_change_prob) # Turn off lanes during lane change if self.DH.desire == log.LateralPlan.Desire.laneChangeRight or self.DH.desire == log.LateralPlan.Desire.laneChangeLeft: self.LP.lll_prob *= self.DH.lane_change_ll_prob self.LP.rll_prob *= self.DH.lane_change_ll_prob # Calculate final driving path and set MPC costs if self.use_lanelines: d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz) self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, MPC_COST_LAT.STEER_RATE) else: d_path_xyz = self.path_xyz # Heading cost is useful at low speed, otherwise end of plan can be off-heading heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.15]) self.lat_mpc.set_weights(MPC_COST_LAT.PATH, heading_cost, MPC_COST_LAT.STEER_RATE) y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1]) heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw) self.y_pts = y_pts assert len(y_pts) == LAT_MPC_N + 1 assert len(heading_pts) == LAT_MPC_N + 1 # self.x0[4] = v_ego p = np.array([v_ego, CAR_ROTATION_RADIUS]) self.lat_mpc.run(self.x0, p, y_pts, heading_pts) # init state for next # mpc.u_sol is the desired curvature rate given x0 curv state. # with x0[3] = measured_curvature, this would be the actual desired rate. # instead, interpolate x_sol so that x0[3] is the desired curvature for lat_control. self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1], self.lat_mpc.x_sol[:, 3]) # Check for infeasible MPC solution mpc_nans = np.isnan(self.lat_mpc.x_sol[:, 3]).any() t = sec_since_boot() if mpc_nans or self.lat_mpc.solution_status != 0: self.reset_mpc() self.x0[3] = measured_curvature if t > self.last_cloudlog_t + 5.0: self.last_cloudlog_t = t cloudlog.warning("Lateral mpc - nan: True") if self.lat_mpc.cost > 20000. or mpc_nans: self.solution_invalid_cnt += 1 else: self.solution_invalid_cnt = 0 def publish(self, sm, pm): plan_solution_valid = self.solution_invalid_cnt < 2 plan_send = messaging.new_message('lateralPlan') plan_send.valid = sm.all_checks( service_list=['carState', 'controlsState', 'modelV2']) lateralPlan = plan_send.lateralPlan lateralPlan.modelMonoTime = sm.logMonoTime['modelV2'] lateralPlan.laneWidth = float(self.LP.lane_width) lateralPlan.dPathPoints = self.y_pts.tolist() lateralPlan.psis = self.lat_mpc.x_sol[0:CONTROL_N, 2].tolist() lateralPlan.curvatures = self.lat_mpc.x_sol[0:CONTROL_N, 3].tolist() lateralPlan.curvatureRates = [ float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1] ] + [0.0] lateralPlan.lProb = float(self.LP.lll_prob) lateralPlan.rProb = float(self.LP.rll_prob) lateralPlan.dProb = float(self.LP.d_prob) lateralPlan.mpcSolutionValid = bool(plan_solution_valid) lateralPlan.solverExecutionTime = self.lat_mpc.solve_time lateralPlan.desire = self.DH.desire lateralPlan.useLaneLines = self.use_lanelines lateralPlan.laneChangeState = self.DH.lane_change_state lateralPlan.laneChangeDirection = self.DH.lane_change_direction pm.send('lateralPlan', plan_send)
class LateralPlanner(): def __init__(self, CP, use_lanelines=True, wide_camera=False): self.use_lanelines = use_lanelines self.LP = LanePlanner(wide_camera) self.last_cloudlog_t = 0 self.steer_rate_cost = CP.steerRateCost self.solution_invalid_cnt = 0 self.lane_change_state = LaneChangeState.off self.lane_change_direction = LaneChangeDirection.none self.lane_change_timer = 0.0 self.lane_change_ll_prob = 1.0 self.keep_pulse_timer = 0.0 self.prev_one_blinker = False self.desire = log.LateralPlan.Desire.none self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3)) self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3)) self.plan_yaw = np.zeros((TRAJECTORY_SIZE, )) self.t_idxs = np.arange(TRAJECTORY_SIZE) self.y_pts = np.zeros(TRAJECTORY_SIZE) self.d_path_w_lines_xyz = np.zeros((TRAJECTORY_SIZE, 3)) self.lat_mpc = LateralMpc() self.reset_mpc(np.zeros(6)) self.d_path_w_lines_xyz = np.zeros((TRAJECTORY_SIZE, 3)) # dp self.dp_torque_apply_length = 1.5 # secs of torque we apply for self.dp_lc_auto_start = 0. # time to start alc self.dp_lc_auto_start_in = 0. # remaining time to start alc self.dp_lc_auto_torque_end = 0. # time to end applying torque self.dp_torque_apply = False # should we apply torque? self.laneless_mode = 2 # AUTO self.laneless_mode_status = False self.laneless_mode_status_buffer = False def reset_mpc(self, x0=np.zeros(6)): self.x0 = x0 self.lat_mpc.reset(x0=self.x0) self.desired_curvature = 0.0 self.safe_desired_curvature = 0.0 self.desired_curvature_rate = 0.0 self.safe_desired_curvature_rate = 0.0 def update(self, sm, CP): self.use_lanelines = not sm['dragonConf'].dpLaneLessModeCtrl self.laneless_mode = sm['dragonConf'].dpLaneLessMode v_ego = sm['carState'].vEgo active = sm['controlsState'].active measured_curvature = sm['controlsState'].curvature self.LP.update_dp_set_offsets(sm['dragonConf'].dpCameraOffset, sm['dragonConf'].dpPathOffset) md = sm['modelV2'] self.LP.parse_model(sm['modelV2']) if len(md.position.x) == TRAJECTORY_SIZE and len( md.orientation.x) == TRAJECTORY_SIZE: self.path_xyz = np.column_stack( [md.position.x, md.position.y, md.position.z]) self.t_idxs = np.array(md.position.t) self.plan_yaw = list(md.orientation.z) if len(md.position.xStd) == TRAJECTORY_SIZE: self.path_xyz_stds = np.column_stack( [md.position.xStd, md.position.yStd, md.position.zStd]) # Lane change logic one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker below_lane_change_speed = v_ego < (sm['dragonConf'].dpLcMinMph * CV.MPH_TO_MS) if (not active) or (self.lane_change_timer > LANE_CHANGE_TIME_MAX): self.lane_change_state = LaneChangeState.off self.lane_change_direction = LaneChangeDirection.none else: reset = False if one_blinker: cur_time = sec_since_boot() # reach auto lc condition if not below_lane_change_speed and sm[ 'dragonConf'].dpLateralMode == 2 and v_ego >= ( sm['dragonConf'].dpLcAutoMinMph * CV.MPH_TO_MS): # work out alc start time and torque apply end time if self.dp_lc_auto_start == 0.: self.dp_lc_auto_start = cur_time + sm[ 'dragonConf'].dpLcAutoDelay self.dp_lc_auto_torque_end = self.dp_lc_auto_start + self.dp_torque_apply_length else: # work out how long til alc start # for display only self.dp_lc_auto_start_in = self.dp_lc_auto_start - cur_time self.dp_torque_apply = True if self.dp_lc_auto_start < cur_time <= self.dp_lc_auto_torque_end else False else: reset = True # reset all vals if not active or reset: self.dp_lc_auto_start = 0. self.dp_lc_auto_start_in = 0. self.dp_lc_auto_torque_end = 0. self.dp_torque_apply = False # LaneChangeState.off if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed: self.lane_change_state = LaneChangeState.preLaneChange self.lane_change_ll_prob = 1.0 # LaneChangeState.preLaneChange elif self.lane_change_state == LaneChangeState.preLaneChange: # Set lane change direction if sm['carState'].leftBlinker: self.lane_change_direction = LaneChangeDirection.left elif sm['carState'].rightBlinker: self.lane_change_direction = LaneChangeDirection.right else: # If there are no blinkers we will go back to LaneChangeState.off self.lane_change_direction = LaneChangeDirection.none torque_applied = sm['carState'].steeringPressed and \ ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right)) blindspot_detected = ( (sm['carState'].leftBlindspot and self.lane_change_direction == LaneChangeDirection.left) or (sm['carState'].rightBlindspot and self.lane_change_direction == LaneChangeDirection.right)) # if human made lane change prior alca, we should stop alca until new blinker (off -> on) self.dp_lc_auto_start = self.dp_lc_auto_torque_end if torque_applied else self.dp_lc_auto_start torque_applied = self.dp_torque_apply if self.dp_torque_apply else torque_applied if not one_blinker or below_lane_change_speed: self.lane_change_state = LaneChangeState.off elif torque_applied and not blindspot_detected: self.lane_change_state = LaneChangeState.laneChangeStarting # LaneChangeState.laneChangeStarting elif self.lane_change_state == LaneChangeState.laneChangeStarting: # fade out over .5s self.lane_change_ll_prob = max( self.lane_change_ll_prob - 2 * DT_MDL, 0.0) # 98% certainty lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01: self.lane_change_state = LaneChangeState.laneChangeFinishing # LaneChangeState.laneChangeFinishing elif self.lane_change_state == LaneChangeState.laneChangeFinishing: # fade in laneline over 1s self.lane_change_ll_prob = min( self.lane_change_ll_prob + DT_MDL, 1.0) if one_blinker and self.lane_change_ll_prob > 0.99: self.lane_change_state = LaneChangeState.preLaneChange elif self.lane_change_ll_prob > 0.99: self.lane_change_state = LaneChangeState.off if self.lane_change_state in [ LaneChangeState.off, LaneChangeState.preLaneChange ]: self.lane_change_timer = 0.0 else: self.lane_change_timer += DT_MDL self.prev_one_blinker = one_blinker self.desire = DESIRES[self.lane_change_direction][ self.lane_change_state] # Send keep pulse once per second during LaneChangeStart.preLaneChange if self.lane_change_state in [ LaneChangeState.off, LaneChangeState.laneChangeStarting ]: self.keep_pulse_timer = 0.0 elif self.lane_change_state == LaneChangeState.preLaneChange: self.keep_pulse_timer += DT_MDL if self.keep_pulse_timer > 1.0: self.keep_pulse_timer = 0.0 elif self.desire in [ log.LateralPlan.Desire.keepLeft, log.LateralPlan.Desire.keepRight ]: self.desire = log.LateralPlan.Desire.none # Turn off lanes during lane change if self.desire == log.LateralPlan.Desire.laneChangeRight or self.desire == log.LateralPlan.Desire.laneChangeLeft: self.LP.lll_prob *= self.lane_change_ll_prob self.LP.rll_prob *= self.lane_change_ll_prob self.d_path_w_lines_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz) if self.use_lanelines: d_path_xyz = self.d_path_w_lines_xyz self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost) self.laneless_mode_status = False elif self.laneless_mode == 0: d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz) self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost) self.laneless_mode_status = False elif self.laneless_mode == 1: d_path_xyz = self.path_xyz path_cost = np.clip( abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5, 5.0) * MPC_COST_LAT.PATH # Heading cost is useful at low speed, otherwise end of plan can be off-heading heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.0]) self.lat_mpc.set_weights(path_cost, heading_cost, CP.steerRateCost) self.laneless_mode_status = True elif self.laneless_mode == 2 and ( (self.LP.lll_prob + self.LP.rll_prob) / 2 < 0.3) and self.lane_change_state == LaneChangeState.off: d_path_xyz = self.path_xyz path_cost = np.clip( abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5, 5.0) * MPC_COST_LAT.PATH # Heading cost is useful at low speed, otherwise end of plan can be off-heading heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.0]) self.lat_mpc.set_weights(path_cost, heading_cost, CP.steerRateCost) self.laneless_mode_status = True self.laneless_mode_status_buffer = True elif self.laneless_mode == 2 and ((self.LP.lll_prob + self.LP.rll_prob)/2 > 0.5) and \ self.laneless_mode_status_buffer and self.lane_change_state == LaneChangeState.off: d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz) self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost) self.laneless_mode_status = False self.laneless_mode_status_buffer = False elif self.laneless_mode == 2 and self.laneless_mode_status_buffer == True and self.lane_change_state == LaneChangeState.off: d_path_xyz = self.path_xyz path_cost = np.clip( abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5, 5.0) * MPC_COST_LAT.PATH # Heading cost is useful at low speed, otherwise end of plan can be off-heading heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.0]) self.lat_mpc.set_weights(path_cost, heading_cost, CP.steerRateCost) self.laneless_mode_status = True else: d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz) self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost) self.laneless_mode_status = False self.laneless_mode_status_buffer = False y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1]) heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw) self.y_pts = y_pts assert len(y_pts) == LAT_MPC_N + 1 assert len(heading_pts) == LAT_MPC_N + 1 self.x0[4] = v_ego self.lat_mpc.run(self.x0, v_ego, CAR_ROTATION_RADIUS, y_pts, heading_pts) # init state for next self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1], self.lat_mpc.x_sol[:, 3]) # Check for infeasable MPC solution mpc_nans = any(math.isnan(x) for x in self.lat_mpc.x_sol[:, 3]) t = sec_since_boot() if mpc_nans or self.lat_mpc.solution_status != 0: self.reset_mpc() self.x0[3] = measured_curvature if t > self.last_cloudlog_t + 5.0: self.last_cloudlog_t = t cloudlog.warning("Lateral mpc - nan: True") if self.lat_mpc.cost > 20000. or mpc_nans: self.solution_invalid_cnt += 1 else: self.solution_invalid_cnt = 0 def publish(self, sm, pm): plan_solution_valid = self.solution_invalid_cnt < 2 plan_send = messaging.new_message('lateralPlan') plan_send.valid = sm.all_alive_and_valid(service_list=[ 'carState', 'controlsState', 'modelV2', 'dragonConf' ]) plan_send.lateralPlan.laneWidth = float(self.LP.lane_width) plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts] plan_send.lateralPlan.psis = [ float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 2] ] plan_send.lateralPlan.curvatures = [ float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 3] ] plan_send.lateralPlan.curvatureRates = [ float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1] ] + [0.0] plan_send.lateralPlan.lProb = float(self.LP.lll_prob) plan_send.lateralPlan.rProb = float(self.LP.rll_prob) plan_send.lateralPlan.dProb = float(self.LP.d_prob) plan_send.lateralPlan.mpcSolutionValid = bool(plan_solution_valid) plan_send.lateralPlan.desire = self.desire plan_send.lateralPlan.useLaneLines = self.use_lanelines plan_send.lateralPlan.laneChangeState = self.lane_change_state plan_send.lateralPlan.laneChangeDirection = self.lane_change_direction # dp plan_send.lateralPlan.dpLaneLessModeStatus = bool( self.laneless_mode_status) plan_send.lateralPlan.dpALCAStartIn = self.dp_lc_auto_start_in plan_send.lateralPlan.dPathWLinesX = [ float(x) for x in self.d_path_w_lines_xyz[:, 0] ] plan_send.lateralPlan.dPathWLinesY = [ float(y) for y in self.d_path_w_lines_xyz[:, 1] ] pm.send('lateralPlan', plan_send)
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)
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)