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 20Hzpull/22793/head
parent
b7394c6171
commit
bef686f275
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