Move navigation into separate daemon (#22767)
	
		
	
				
					
				
			* Add navd folder
* prints route instructions to console
* broadcast NavInstuction without lanes
* show basic instructions and eta
* parse out lane info
* use swaglog
* clip distance on ui side
* draw lane directions
* show route
* add to process config
* add to release files
* small cleanup
* show route without gps
* pop open map on initial route
* fix error messages around no gps
* done
* make persistent process
* handle end of route
* clear route on offroad
* only one timer
* fix layout hacks
* explicit rendering at 20Hz
old-commit-hash: bef686f275
			
			
				vw-mqb-aeb
			
			
		
							parent
							
								
									08a11cdf84
								
							
						
					
					
						commit
						dc3075790e
					
				
				 15 changed files with 567 additions and 334 deletions
			
			
		@ -0,0 +1 @@ | 
				
			||||
_navd | 
				
			||||
@ -0,0 +1,36 @@ | 
				
			||||
#include <QApplication> | 
				
			||||
#include <QCommandLineParser> | 
				
			||||
#include <QDebug> | 
				
			||||
#include <QThread> | 
				
			||||
#include <csignal> | 
				
			||||
 | 
				
			||||
#include "selfdrive/ui/qt/util.h" | 
				
			||||
#include "selfdrive/ui/navd/route_engine.h" | 
				
			||||
 | 
				
			||||
RouteEngine* route_engine = nullptr; | 
				
			||||
 | 
				
			||||
void sigHandler(int s) { | 
				
			||||
  qInfo() << "Shutting down"; | 
				
			||||
  std::signal(s, SIG_DFL); | 
				
			||||
 | 
				
			||||
  qApp->quit(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
int main(int argc, char *argv[]) { | 
				
			||||
  qInstallMessageHandler(swagLogMessageHandler); | 
				
			||||
 | 
				
			||||
  QApplication app(argc, argv); | 
				
			||||
  std::signal(SIGINT, sigHandler); | 
				
			||||
  std::signal(SIGTERM, sigHandler); | 
				
			||||
 | 
				
			||||
  QCommandLineParser parser; | 
				
			||||
  parser.setApplicationDescription("Navigation server. Runs stand-alone, or using pre-computer route"); | 
				
			||||
  parser.addHelpOption(); | 
				
			||||
  parser.process(app); | 
				
			||||
  const QStringList args = parser.positionalArguments(); | 
				
			||||
 | 
				
			||||
  route_engine = new RouteEngine(); | 
				
			||||
 | 
				
			||||
  return app.exec(); | 
				
			||||
} | 
				
			||||
@ -0,0 +1,4 @@ | 
				
			||||
#!/bin/sh | 
				
			||||
cd "$(dirname "$0")" | 
				
			||||
export QT_PLUGIN_PATH="../../../third_party/qt-plugins/$(uname -m)" | 
				
			||||
exec ./_navd | 
				
			||||
@ -0,0 +1,323 @@ | 
				
			||||
#include "selfdrive/ui/navd/route_engine.h" | 
				
			||||
 | 
				
			||||
#include <QDebug> | 
				
			||||
 | 
				
			||||
#include "selfdrive/ui/qt/maps/map.h" | 
				
			||||
#include "selfdrive/ui/qt/maps/map_helpers.h" | 
				
			||||
#include "selfdrive/ui/qt/api.h" | 
				
			||||
 | 
				
			||||
#include "selfdrive/common/params.h" | 
				
			||||
 | 
				
			||||
const qreal REROUTE_DISTANCE = 25; | 
				
			||||
const float MANEUVER_TRANSITION_THRESHOLD = 10; | 
				
			||||
 | 
				
			||||
static float get_time_typical(const QGeoRouteSegment &segment) { | 
				
			||||
  auto maneuver = segment.maneuver(); | 
				
			||||
  auto attrs = maneuver.extendedAttributes(); | 
				
			||||
  return attrs.contains("mapbox.duration_typical") ? attrs["mapbox.duration_typical"].toDouble() : segment.travelTime(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static cereal::NavInstruction::Direction string_to_direction(QString d) { | 
				
			||||
  if (d.contains("left")) { | 
				
			||||
    return cereal::NavInstruction::Direction::LEFT; | 
				
			||||
  } else if (d.contains("right")) { | 
				
			||||
    return cereal::NavInstruction::Direction::RIGHT; | 
				
			||||
  } else if (d.contains("straight")) { | 
				
			||||
    return cereal::NavInstruction::Direction::STRAIGHT; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  return cereal::NavInstruction::Direction::NONE; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static void parse_banner(cereal::NavInstruction::Builder &instruction, const QMap<QString, QVariant> &banner, bool full) { | 
				
			||||
  QString primary_str, secondary_str; | 
				
			||||
 | 
				
			||||
  auto p = banner["primary"].toMap(); | 
				
			||||
  primary_str += p["text"].toString(); | 
				
			||||
 | 
				
			||||
  instruction.setShowFull(full); | 
				
			||||
 | 
				
			||||
  if (p.contains("type")) { | 
				
			||||
    instruction.setManeuverType(p["type"].toString().toStdString()); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  if (p.contains("modifier")) { | 
				
			||||
    instruction.setManeuverModifier(p["modifier"].toString().toStdString()); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  if (banner.contains("secondary")) { | 
				
			||||
    auto s = banner["secondary"].toMap(); | 
				
			||||
    secondary_str += s["text"].toString(); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  instruction.setManeuverPrimaryText(primary_str.toStdString()); | 
				
			||||
  instruction.setManeuverSecondaryText(secondary_str.toStdString()); | 
				
			||||
 | 
				
			||||
  if (banner.contains("sub")) { | 
				
			||||
    auto s = banner["sub"].toMap(); | 
				
			||||
    auto components = s["components"].toList(); | 
				
			||||
 | 
				
			||||
    size_t num_lanes = 0; | 
				
			||||
    for (auto &c : components) { | 
				
			||||
      auto cc = c.toMap(); | 
				
			||||
      if (cc["type"].toString() == "lane") { | 
				
			||||
        num_lanes += 1; | 
				
			||||
      } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    auto lanes = instruction.initLanes(num_lanes); | 
				
			||||
 | 
				
			||||
    size_t i = 0; | 
				
			||||
    for (auto &c : components) { | 
				
			||||
      auto cc = c.toMap(); | 
				
			||||
      if (cc["type"].toString() == "lane") { | 
				
			||||
        auto lane = lanes[i]; | 
				
			||||
        lane.setActive(cc["active"].toBool()); | 
				
			||||
 | 
				
			||||
        if (cc.contains("active_direction")) { | 
				
			||||
          lane.setActiveDirection(string_to_direction(cc["active_direction"].toString())); | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        auto directions = lane.initDirections(cc["directions"].toList().size()); | 
				
			||||
 | 
				
			||||
        size_t j = 0; | 
				
			||||
        for (auto &dir : cc["directions"].toList()) { | 
				
			||||
          directions.set(j, string_to_direction(dir.toString())); | 
				
			||||
          j++; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
 | 
				
			||||
        i++; | 
				
			||||
      } | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
} | 
				
			||||
 | 
				
			||||
RouteEngine::RouteEngine() { | 
				
			||||
  sm = new SubMaster({"liveLocationKalman"}); | 
				
			||||
  pm = new PubMaster({"navInstruction", "navRoute"}); | 
				
			||||
 | 
				
			||||
  // Timers
 | 
				
			||||
  timer = new QTimer(this); | 
				
			||||
  QObject::connect(timer, SIGNAL(timeout()), this, SLOT(timerUpdate())); | 
				
			||||
  timer->start(1000); | 
				
			||||
 | 
				
			||||
  // Build routing engine
 | 
				
			||||
  QVariantMap parameters; | 
				
			||||
  QString token = MAPBOX_TOKEN.isEmpty() ? CommaApi::create_jwt({}, 4 * 7 * 24 * 3600) : MAPBOX_TOKEN; | 
				
			||||
  parameters["mapbox.access_token"] = token; | 
				
			||||
  parameters["mapbox.directions_api_url"] = MAPS_HOST + "/directions/v5/mapbox/"; | 
				
			||||
 | 
				
			||||
  geoservice_provider = new QGeoServiceProvider("mapbox", parameters); | 
				
			||||
  routing_manager = geoservice_provider->routingManager(); | 
				
			||||
  if (routing_manager == nullptr) { | 
				
			||||
    qWarning() << geoservice_provider->errorString(); | 
				
			||||
    assert(routing_manager); | 
				
			||||
  } | 
				
			||||
  QObject::connect(routing_manager, &QGeoRoutingManager::finished, this, &RouteEngine::routeCalculated); | 
				
			||||
 | 
				
			||||
  // Get last gps position from params
 | 
				
			||||
  auto last_gps_position = coordinate_from_param("LastGPSPosition"); | 
				
			||||
  if (last_gps_position) { | 
				
			||||
    last_position = *last_gps_position; | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void RouteEngine::timerUpdate() { | 
				
			||||
  sm->update(0); | 
				
			||||
  if (!sm->updated("liveLocationKalman")) { | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  auto location = (*sm)["liveLocationKalman"].getLiveLocationKalman(); | 
				
			||||
  gps_ok = location.getGpsOK(); | 
				
			||||
 | 
				
			||||
  localizer_valid = location.getStatus() == cereal::LiveLocationKalman::Status::VALID; | 
				
			||||
 | 
				
			||||
  if (localizer_valid) { | 
				
			||||
    auto pos = location.getPositionGeodetic(); | 
				
			||||
    auto orientation = location.getCalibratedOrientationNED(); | 
				
			||||
 | 
				
			||||
    last_bearing = RAD2DEG(orientation.getValue()[2]); | 
				
			||||
    last_position = QMapbox::Coordinate(pos.getValue()[0], pos.getValue()[1]); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  recomputeRoute(); | 
				
			||||
 | 
				
			||||
  MessageBuilder msg; | 
				
			||||
  cereal::Event::Builder evt = msg.initEvent(segment.isValid()); | 
				
			||||
  cereal::NavInstruction::Builder instruction = evt.initNavInstruction(); | 
				
			||||
 | 
				
			||||
  // Show route instructions
 | 
				
			||||
  if (segment.isValid()) { | 
				
			||||
    auto cur_maneuver = segment.maneuver(); | 
				
			||||
    auto attrs = cur_maneuver.extendedAttributes(); | 
				
			||||
    if (cur_maneuver.isValid() && attrs.contains("mapbox.banner_instructions")) { | 
				
			||||
      float along_geometry = distance_along_geometry(segment.path(), to_QGeoCoordinate(*last_position)); | 
				
			||||
      float distance_to_maneuver_along_geometry = segment.distance() - along_geometry; | 
				
			||||
 | 
				
			||||
      auto banners = attrs["mapbox.banner_instructions"].toList(); | 
				
			||||
      if (banners.size()) { | 
				
			||||
        auto banner = banners[0].toMap(); | 
				
			||||
 | 
				
			||||
        for (auto &b : banners) { | 
				
			||||
          auto bb = b.toMap(); | 
				
			||||
          if (distance_to_maneuver_along_geometry < bb["distance_along_geometry"].toDouble()) { | 
				
			||||
            banner = bb; | 
				
			||||
          } | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        instruction.setManeuverDistance(distance_to_maneuver_along_geometry); | 
				
			||||
        parse_banner(instruction, banner, distance_to_maneuver_along_geometry < banner["distance_along_geometry"].toDouble()); | 
				
			||||
 | 
				
			||||
        // ETA
 | 
				
			||||
        float progress = distance_along_geometry(segment.path(), to_QGeoCoordinate(*last_position)) / segment.distance(); | 
				
			||||
        float total_distance = segment.distance() * (1.0 - progress); | 
				
			||||
        float total_time = segment.travelTime() * (1.0 - progress); | 
				
			||||
        float total_time_typical = get_time_typical(segment) * (1.0 - progress); | 
				
			||||
 | 
				
			||||
        auto s = segment.nextRouteSegment(); | 
				
			||||
        while (s.isValid()) { | 
				
			||||
          total_distance += s.distance(); | 
				
			||||
          total_time += s.travelTime(); | 
				
			||||
          total_time_typical += get_time_typical(s); | 
				
			||||
 | 
				
			||||
          s = s.nextRouteSegment(); | 
				
			||||
        } | 
				
			||||
        instruction.setTimeRemaining(total_time); | 
				
			||||
        instruction.setTimeRemainingTypical(total_time_typical); | 
				
			||||
        instruction.setDistanceRemaining(total_distance); | 
				
			||||
      } | 
				
			||||
 | 
				
			||||
      // Transition to next route segment
 | 
				
			||||
      if (distance_to_maneuver_along_geometry < -MANEUVER_TRANSITION_THRESHOLD) { | 
				
			||||
        auto next_segment = segment.nextRouteSegment(); | 
				
			||||
        if (next_segment.isValid()) { | 
				
			||||
          segment = next_segment; | 
				
			||||
 | 
				
			||||
          recompute_backoff = std::max(0, recompute_backoff - 1); | 
				
			||||
          recompute_countdown = 0; | 
				
			||||
        } else { | 
				
			||||
          qWarning() << "Destination reached"; | 
				
			||||
          Params().remove("NavDestination"); | 
				
			||||
 | 
				
			||||
          // Clear route if driving away from destination
 | 
				
			||||
          float d = segment.maneuver().position().distanceTo(to_QGeoCoordinate(*last_position)); | 
				
			||||
          if (d > REROUTE_DISTANCE) { | 
				
			||||
            clearRoute(); | 
				
			||||
          } | 
				
			||||
        } | 
				
			||||
      } | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  pm->send("navInstruction", msg); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void RouteEngine::clearRoute() { | 
				
			||||
  segment = QGeoRouteSegment(); | 
				
			||||
  nav_destination = QMapbox::Coordinate(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
bool RouteEngine::shouldRecompute() { | 
				
			||||
  if (!segment.isValid()) { | 
				
			||||
    return true; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Don't recompute in last segment, assume destination is reached
 | 
				
			||||
  if (!segment.nextRouteSegment().isValid()) { | 
				
			||||
    return false; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Compute closest distance to all line segments in the current path
 | 
				
			||||
  float min_d = REROUTE_DISTANCE + 1; | 
				
			||||
  auto path = segment.path(); | 
				
			||||
  auto cur = to_QGeoCoordinate(*last_position); | 
				
			||||
  for (size_t i = 0; i < path.size() - 1; i++) { | 
				
			||||
    auto a = path[i]; | 
				
			||||
    auto b = path[i+1]; | 
				
			||||
    if (a.distanceTo(b) < 1.0) { | 
				
			||||
      continue; | 
				
			||||
    } | 
				
			||||
    min_d = std::min(min_d, minimum_distance(a, b, cur)); | 
				
			||||
  } | 
				
			||||
  return min_d > REROUTE_DISTANCE; | 
				
			||||
 | 
				
			||||
  // TODO: Check for going wrong way in segment
 | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void RouteEngine::recomputeRoute() { | 
				
			||||
  auto new_destination = coordinate_from_param("NavDestination"); | 
				
			||||
  if (!new_destination) { | 
				
			||||
    clearRoute(); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  bool should_recompute = shouldRecompute(); | 
				
			||||
  if (*new_destination != nav_destination) { | 
				
			||||
    qWarning() << "Got new destination from NavDestination param" << *new_destination; | 
				
			||||
    should_recompute = true; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  if (!gps_ok && segment.isValid()) return; // Don't recompute when gps drifts in tunnels
 | 
				
			||||
 | 
				
			||||
  if (recompute_countdown == 0 && should_recompute) { | 
				
			||||
    recompute_countdown = std::pow(2, recompute_backoff); | 
				
			||||
    recompute_backoff = std::min(7, recompute_backoff + 1); | 
				
			||||
    calculateRoute(*new_destination); | 
				
			||||
  } else { | 
				
			||||
    recompute_countdown = std::max(0, recompute_countdown - 1); | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void RouteEngine::calculateRoute(QMapbox::Coordinate destination) { | 
				
			||||
  qWarning() << "Calculating route" << *last_position << "->" << destination; | 
				
			||||
 | 
				
			||||
  nav_destination = destination; | 
				
			||||
  QGeoRouteRequest request(to_QGeoCoordinate(*last_position), to_QGeoCoordinate(destination)); | 
				
			||||
  request.setFeatureWeight(QGeoRouteRequest::TrafficFeature, QGeoRouteRequest::AvoidFeatureWeight); | 
				
			||||
 | 
				
			||||
  if (last_bearing) { | 
				
			||||
    QVariantMap params; | 
				
			||||
    int bearing = ((int)(*last_bearing) + 360) % 360; | 
				
			||||
    params["bearing"] = bearing; | 
				
			||||
    request.setWaypointsMetadata({params}); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  routing_manager->calculateRoute(request); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void RouteEngine::routeCalculated(QGeoRouteReply *reply) { | 
				
			||||
  MessageBuilder msg; | 
				
			||||
  cereal::Event::Builder evt = msg.initEvent(); | 
				
			||||
  cereal::NavRoute::Builder nav_route = evt.initNavRoute(); | 
				
			||||
 | 
				
			||||
  if (reply->error() == QGeoRouteReply::NoError) { | 
				
			||||
    if (reply->routes().size() != 0) { | 
				
			||||
      qWarning() << "Got route response"; | 
				
			||||
 | 
				
			||||
      route = reply->routes().at(0); | 
				
			||||
      segment = route.firstRouteSegment(); | 
				
			||||
 | 
				
			||||
      auto path = route.path(); | 
				
			||||
      auto coordinates = nav_route.initCoordinates(path.size()); | 
				
			||||
 | 
				
			||||
      size_t i = 0; | 
				
			||||
      for (auto const &c : route.path()) { | 
				
			||||
        coordinates[i].setLatitude(c.latitude()); | 
				
			||||
        coordinates[i].setLongitude(c.longitude()); | 
				
			||||
        i++; | 
				
			||||
      } | 
				
			||||
 | 
				
			||||
    } else { | 
				
			||||
      qWarning() << "Got empty route response"; | 
				
			||||
    } | 
				
			||||
  } else { | 
				
			||||
    qWarning() << "Got error in route reply" << reply->errorString(); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  pm->send("navRoute", msg); | 
				
			||||
 | 
				
			||||
  reply->deleteLater(); | 
				
			||||
} | 
				
			||||
@ -0,0 +1,50 @@ | 
				
			||||
#pragma once | 
				
			||||
 | 
				
			||||
#include <QThread> | 
				
			||||
#include <QGeoCoordinate> | 
				
			||||
#include <QGeoManeuver> | 
				
			||||
#include <QGeoRouteRequest> | 
				
			||||
#include <QGeoRouteSegment> | 
				
			||||
#include <QGeoRoutingManager> | 
				
			||||
#include <QGeoServiceProvider> | 
				
			||||
#include <QTimer> | 
				
			||||
#include <QMapboxGL> | 
				
			||||
 | 
				
			||||
#include "cereal/messaging/messaging.h" | 
				
			||||
 | 
				
			||||
class RouteEngine : public QObject { | 
				
			||||
  Q_OBJECT | 
				
			||||
 | 
				
			||||
public: | 
				
			||||
  RouteEngine(); | 
				
			||||
 | 
				
			||||
  SubMaster *sm; | 
				
			||||
  PubMaster *pm; | 
				
			||||
 | 
				
			||||
  QTimer* timer; | 
				
			||||
 | 
				
			||||
  // Route
 | 
				
			||||
  bool gps_ok = false; | 
				
			||||
  QGeoServiceProvider *geoservice_provider; | 
				
			||||
  QGeoRoutingManager *routing_manager; | 
				
			||||
  QGeoRoute route; | 
				
			||||
  QGeoRouteSegment segment; | 
				
			||||
  QMapbox::Coordinate nav_destination; | 
				
			||||
 | 
				
			||||
  // Position
 | 
				
			||||
  std::optional<QMapbox::Coordinate> last_position; | 
				
			||||
  std::optional<float> last_bearing; | 
				
			||||
  bool localizer_valid = false; | 
				
			||||
 | 
				
			||||
  // Route recompute
 | 
				
			||||
  int recompute_backoff = 0; | 
				
			||||
  int recompute_countdown = 0; | 
				
			||||
  void calculateRoute(QMapbox::Coordinate destination); | 
				
			||||
  void clearRoute(); | 
				
			||||
  bool shouldRecompute(); | 
				
			||||
 | 
				
			||||
private slots: | 
				
			||||
  void timerUpdate(); | 
				
			||||
  void routeCalculated(QGeoRouteReply *reply); | 
				
			||||
  void recomputeRoute(); | 
				
			||||
}; | 
				
			||||
					Loading…
					
					
				
		Reference in new issue