From 46b0b80f4725c8f42f6038e8eb37a2e8742aabe5 Mon Sep 17 00:00:00 2001 From: FrogAi <91348155+FrogAi@users.noreply.github.com> Date: Fri, 12 Jan 2024 22:39:30 -0700 Subject: [PATCH] Map Turn Speed Control Added toggle for "Map Turn Speed Control". Credit goes to Pfeiferj! https: //github.com/pfeiferj Co-Authored-By: Jacob Pfeifer --- cereal/custom.capnp | 1 + common/params.cc | 3 + release/files_common | 1 + .../assets/toggle_icons/icon_speed_map.png | Bin 0 -> 64396 bytes .../frogpilot/functions/frogpilot_planner.py | 23 ++- .../functions/map_turn_speed_controller.py | 152 ++++++++++++++++++ selfdrive/frogpilot/ui/control_settings.cc | 2 + selfdrive/ui/qt/onroad.cc | 14 +- selfdrive/ui/qt/onroad.h | 1 + selfdrive/ui/ui.cc | 1 + selfdrive/ui/ui.h | 1 + 11 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 selfdrive/frogpilot/assets/toggle_icons/icon_speed_map.png create mode 100644 selfdrive/frogpilot/functions/map_turn_speed_controller.py diff --git a/cereal/custom.capnp b/cereal/custom.capnp index b3e6f71..a89883f 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -28,6 +28,7 @@ struct FrogPilotLateralPlan @0xda96579883444c35 { } struct FrogPilotLongitudinalPlan @0x80ae746ee2596b11 { + adjustedCruise @0: Float32; conditionalExperimental @1 :Bool; desiredFollowDistance @2 :Int16; distances @3 :List(Float32); diff --git a/common/params.cc b/common/params.cc index 447574c..5f7430b 100644 --- a/common/params.cc +++ b/common/params.cc @@ -262,7 +262,10 @@ std::unordered_map keys = { {"LongitudinalTune", PERSISTENT}, {"LongPitch", PERSISTENT}, {"LowerVolt", PERSISTENT}, + {"MapTargetVelocities", PERSISTENT}, {"ModelUI", PERSISTENT}, + {"MTSCAggressiveness", PERSISTENT}, + {"MTSCEnabled", PERSISTENT}, {"MuteDM", PERSISTENT}, {"MuteDoor", PERSISTENT}, {"MuteOverheated", PERSISTENT}, diff --git a/release/files_common b/release/files_common index bae3be1..f792335 100644 --- a/release/files_common +++ b/release/files_common @@ -559,3 +559,4 @@ tinygrad_repo/tinygrad/*.py selfdrive/frogpilot/functions/conditional_experimental_mode.py selfdrive/frogpilot/functions/frogpilot_planner.py +selfdrive/frogpilot/functions/map_turn_speed_controller.py diff --git a/selfdrive/frogpilot/assets/toggle_icons/icon_speed_map.png b/selfdrive/frogpilot/assets/toggle_icons/icon_speed_map.png new file mode 100644 index 0000000000000000000000000000000000000000..60b87eb97e8a696f3d526009f8772c4859a37d6d GIT binary patch literal 64396 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

R$ugIB-{tuHC!kgwdJOcJ0ko&ZcuoUx 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⋙UY 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 D-Z>As literal 0 HcmV?d00001 diff --git a/selfdrive/frogpilot/functions/frogpilot_planner.py b/selfdrive/frogpilot/functions/frogpilot_planner.py index d934ff1..918bbdd 100644 --- a/selfdrive/frogpilot/functions/frogpilot_planner.py +++ b/selfdrive/frogpilot/functions/frogpilot_planner.py @@ -2,13 +2,14 @@ import cereal.messaging as messaging import numpy as np from openpilot.common.conversions import Conversions as CV -from openpilot.common.numpy_fast import interp +from openpilot.common.numpy_fast import clip, interp from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, V_CRUISE_MAX from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.longitudinal_planner import A_CRUISE_MIN, A_CRUISE_MAX_VALS, A_CRUISE_MAX_BP, get_max_accel from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.frogpilot.functions.conditional_experimental_mode import ConditionalExperimentalMode +from openpilot.selfdrive.frogpilot.functions.map_turn_speed_controller import MapTurnSpeedController # Acceleration profiles - Credit goes to the DragonPilot team! # MPH = [0., 35, 35, 40, 40, 45, 45, 67, 67, 67, 123] @@ -51,7 +52,9 @@ def calculate_lane_width(lane, current_lane, road_edge): class FrogPilotPlanner: def __init__(self, params): self.cem = ConditionalExperimentalMode() + self.mtsc = MapTurnSpeedController() + self.mtsc_target = 0 self.v_cruise = 0 self.x_desired_trajectory = np.zeros(CONTROL_N) @@ -68,7 +71,10 @@ class FrogPilotPlanner: v_ego = carState.vEgo # Acceleration profiles - if self.acceleration_profile == 1: + v_cruise_changed = (self.mtsc_target) + 1 < v_cruise # Use stock acceleration profiles to handle MTSC more precisely + if v_cruise_changed: + self.accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)] + elif self.acceleration_profile == 1: self.accel_limits = [get_min_accel_eco_tune(v_ego), get_max_accel_eco_tune(v_ego)] elif self.acceleration_profile in (2, 3): self.accel_limits = [get_min_accel_sport_tune(v_ego), get_max_accel_sport_tune(v_ego)] @@ -85,8 +91,16 @@ class FrogPilotPlanner: self.x_desired_trajectory = self.x_desired_trajectory_full[:CONTROL_N] def update_v_cruise(self, carState, controlsState, modelData, enabled, v_cruise, v_ego): + # Pfeiferj's Map Turn Speed Controller + if self.map_turn_speed_controller: + self.mtsc_target = np.clip(self.mtsc.target_speed(v_ego, carState.aEgo), MIN_TARGET_V, v_cruise) + if self.mtsc_target == MIN_TARGET_V: + self.mtsc_target = v_cruise + else: + self.mtsc_target = v_cruise + v_ego_diff = max(carState.vEgoRaw - carState.vEgoCluster, 0) - return v_cruise - v_ego_diff + return min(v_cruise, self.mtsc_target) - v_ego_diff def publish_lateral(self, sm, pm, DH): frogpilot_lateral_plan_send = messaging.new_message('frogpilotLateralPlan') @@ -103,6 +117,7 @@ class FrogPilotPlanner: frogpilot_longitudinal_plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState']) frogpilotLongitudinalPlan = frogpilot_longitudinal_plan_send.frogpilotLongitudinalPlan + frogpilotLongitudinalPlan.adjustedCruise = float(min(self.mtsc_target) * (CV.MS_TO_KPH if self.is_metric else CV.MS_TO_MPH)) frogpilotLongitudinalPlan.conditionalExperimental = self.cem.experimental_mode frogpilotLongitudinalPlan.distances = self.x_desired_trajectory.tolist() frogpilotLongitudinalPlan.redLight = bool(self.cem.red_light_detected) @@ -140,3 +155,5 @@ class FrogPilotPlanner: self.acceleration_profile = params.get_int("AccelerationProfile") if longitudinal_tune else 0 self.aggressive_acceleration = params.get_bool("AggressiveAcceleration") and longitudinal_tune self.increased_stopping_distance = params.get_int("StoppingDistance") * (1 if self.is_metric else CV.FOOT_TO_METER) if longitudinal_tune else 0 + + self.map_turn_speed_controller = params.get_bool("MTSCEnabled") diff --git a/selfdrive/frogpilot/functions/map_turn_speed_controller.py b/selfdrive/frogpilot/functions/map_turn_speed_controller.py new file mode 100644 index 0000000..d05c008 --- /dev/null +++ b/selfdrive/frogpilot/functions/map_turn_speed_controller.py @@ -0,0 +1,152 @@ +import json +import math + +from openpilot.common.conversions import Conversions as CV +from openpilot.common.numpy_fast import interp +from openpilot.common.params import Params + +params_memory = Params("/dev/shm/params") + +R = 6373000.0 # approximate radius of earth in meters +TO_RADIANS = math.pi / 180 +TO_DEGREES = 180 / math.pi +TARGET_JERK = -0.6 # m/s^3 There's some jounce limits that are not consistent so we're fudging this some +TARGET_ACCEL = -1.2 # m/s^2 should match up with the long planner limit +TARGET_OFFSET = 1.0 # seconds - This controls how soon before the curve you reach the target velocity. It also helps + # reach the target velocity when innacuracies in the distance modeling logic would cause overshoot. + # The value is multiplied against the target velocity to determine the additional distance. This is + # done to keep the distance calculations consistent but results in the offset actually being less + # time than specified depending on how much of a speed diffrential there is between v_ego and the + # target velocity. + +def calculate_accel(t, target_jerk, a_ego): + return a_ego + target_jerk * t + +def calculate_distance(t, target_jerk, a_ego, v_ego): + return t * v_ego + a_ego/2 * (t ** 2) + target_jerk/6 * (t ** 3) + +def calculate_velocity(t, target_jerk, a_ego, v_ego): + return v_ego + a_ego * t + target_jerk/2 * (t ** 2) + + +# points should be in radians +# output is meters +def distance_to_point(ax, ay, bx, by): + a = math.sin((bx-ax)/2)*math.sin((bx-ax)/2) + math.cos(ax) * math.cos(bx)*math.sin((by-ay)/2)*math.sin((by-ay)/2) + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) + + return R * c # in meters + +class MapTurnSpeedController: + def __init__(self): + self.target_lat = 0.0 + self.target_lon = 0.0 + self.target_v = 0.0 + + def target_speed(self, v_ego, a_ego) -> float: + lat = 0.0 + lon = 0.0 + try: + position = json.loads(params_memory.get("LastGPSPosition")) + lat = position["latitude"] + lon = position["longitude"] + except: return 0.0 + + try: + target_velocities = json.loads(params_memory.get("MapTargetVelocities")) + except: return 0.0 + + min_dist = 1000 + min_idx = 0 + distances = [] + + # find our location in the path + for i in range(len(target_velocities)): + target_velocity = target_velocities[i] + tlat = target_velocity["latitude"] + tlon = target_velocity["longitude"] + d = distance_to_point(lat * TO_RADIANS, lon * TO_RADIANS, tlat * TO_RADIANS, tlon * TO_RADIANS) + distances.append(d) + if d < min_dist: + min_dist = d + min_idx = i + + # only look at values from our current position forward + forward_points = target_velocities[min_idx:] + forward_distances = distances[min_idx:] + + # find velocities that we are within the distance we need to adjust for + valid_velocities = [] + for i in range(len(forward_points)): + target_velocity = forward_points[i] + tlat = target_velocity["latitude"] + tlon = target_velocity["longitude"] + tv = target_velocity["velocity"] + if tv > v_ego: + continue + + d = forward_distances[i] + + a_diff = (a_ego - TARGET_ACCEL) + accel_t = abs(a_diff / TARGET_JERK) + min_accel_v = calculate_velocity(accel_t, TARGET_JERK, a_ego, v_ego) + + max_d = 0 + if tv > min_accel_v: + # calculate time needed based on target jerk + a = 0.5 * TARGET_JERK + b = a_ego + c = v_ego - tv + t_a = -1 * ((b**2 - 4 * a * c) ** 0.5 + b) / 2 * a + t_b = ((b**2 - 4 * a * c) ** 0.5 - b) / 2 * a + if not isinstance(t_a, complex) and t_a > 0: + t = t_a + else: + t = t_b + if isinstance(t, complex): + continue + + max_d = max_d + calculate_distance(t, TARGET_JERK, a_ego, v_ego) + + else: + t = accel_t + max_d = calculate_distance(t, TARGET_JERK, a_ego, v_ego) + + # calculate additional time needed based on target accel + t = abs((min_accel_v - tv) / TARGET_ACCEL) + max_d += calculate_distance(t, 0, TARGET_ACCEL, min_accel_v) + + if d < max_d + tv * TARGET_OFFSET: + valid_velocities.append((float(tv), tlat, tlon)) + + # Find the smallest velocity we need to adjust for + min_v = 100.0 + target_lat = 0.0 + target_lon = 0.0 + for tv, lat, lon in valid_velocities: + if tv < min_v: + min_v = tv + target_lat = lat + target_lon = lon + + if self.target_v < min_v and not (self.target_lat == 0 and self.target_lon == 0): + for i in range(len(forward_points)): + target_velocity = forward_points[i] + tlat = target_velocity["latitude"] + tlon = target_velocity["longitude"] + tv = target_velocity["velocity"] + if tv > v_ego: + continue + + if tlat == self.target_lat and tlon == self.target_lon and tv == self.target_v: + return float(self.target_v) + # not found so lets reset + self.target_v = 0.0 + self.target_lat = 0.0 + self.target_lon = 0.0 + + self.target_v = min_v + self.target_lat = target_lat + self.target_lon = target_lon + + return min_v diff --git a/selfdrive/frogpilot/ui/control_settings.cc b/selfdrive/frogpilot/ui/control_settings.cc index d2fefd1..c7ffdca 100644 --- a/selfdrive/frogpilot/ui/control_settings.cc +++ b/selfdrive/frogpilot/ui/control_settings.cc @@ -30,6 +30,8 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil {"AccelerationProfile", "Acceleration Profile", "Change the acceleration rate to be either sporty or eco-friendly.", ""}, {"AggressiveAcceleration", "Aggressive Acceleration With Lead", "Increase acceleration aggressiveness when following a lead vehicle from a stop.", ""}, {"StoppingDistance", "Increased Stopping Distance", "Increase the stopping distance for a more comfortable stop.", ""}, + + {"MTSCEnabled", "Map Turn Speed Control", "Slow down for anticipated curves detected by your downloaded maps.", "../frogpilot/assets/toggle_icons/icon_speed_map.png"}, }; for (const auto &[param, title, desc, icon] : controlToggles) { diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 34a2e6d..5dfa020 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -493,7 +493,7 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { QString speedLimitStr = (speedLimit > 1) ? QString::number(std::nearbyint(speedLimit)) : "–"; QString speedStr = QString::number(std::nearbyint(speed)); - QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(setSpeed)) : "–"; + QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(setSpeed - cruiseAdjustment)) : "–"; // Draw outer box + border to contain set speed and speed limit const int sign_margin = 12; @@ -512,7 +512,16 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { int bottom_radius = has_eu_speed_limit ? 100 : 32; QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size); - if (reverseCruise) { + if (is_cruise_set && cruiseAdjustment) { + float transition = qBound(0.0f, 4.0f * (cruiseAdjustment / setSpeed), 1.0f); + QColor min = whiteColor(75), max = redColor(75); + + p.setPen(QPen(QColor::fromRgbF( + min.redF() + transition * (max.redF() - min.redF()), + min.greenF() + transition * (max.greenF() - min.greenF()), + min.blueF() + transition * (max.blueF() - min.blueF()) + ), 6)); + } else if (reverseCruise) { p.setPen(QPen(QColor(0, 150, 255), 6)); } else { p.setPen(QPen(whiteColor(75), 6)); @@ -1085,6 +1094,7 @@ void AnnotatedCameraWidget::updateFrogPilotWidgets(QPainter &p) { conditionalSpeed = scene.conditional_speed; conditionalSpeedLead = scene.conditional_speed_lead; conditionalStatus = scene.conditional_status; + cruiseAdjustment = fmax((0.1 * fmax(setSpeed - scene.adjusted_cruise, 0) + 0.9 * cruiseAdjustment) - 1, 0); customColors = scene.custom_colors; desiredFollow = scene.desired_follow; experimentalMode = scene.experimental_mode; diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 1f7b462..c3e1fc5 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -143,6 +143,7 @@ private: bool turnSignalRight; bool useSI; double maxAcceleration; + float cruiseAdjustment; float laneWidthLeft; float laneWidthRight; int cameraView; diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 20d9a27..2e04a43 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -252,6 +252,7 @@ static void update_state(UIState *s) { scene.obstacle_distance_stock = frogpilotLongitudinalPlan.getSafeObstacleDistanceStock(); scene.stopped_equivalence = frogpilotLongitudinalPlan.getStoppedEquivalenceFactor(); } + scene.adjusted_cruise = frogpilotLongitudinalPlan.getAdjustedCruise(); } if (sm.updated("liveLocationKalman")) { auto liveLocationKalman = sm["liveLocationKalman"].getLiveLocationKalman(); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index bcbba67..973d484 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -193,6 +193,7 @@ typedef struct UIScene { bool turn_signal_right; bool unlimited_road_ui_length; bool use_si; + float adjusted_cruise; float lane_line_width; float lane_width_left; float lane_width_right;