mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-09 10:44:32 +08:00
Compare commits
138 Commits
clippy
...
nav-commac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
793359d87d | ||
|
|
e81722517e | ||
|
|
837db1f257 | ||
|
|
e27915bb3d | ||
|
|
bf84976129 | ||
|
|
d3db789e86 | ||
|
|
ae023d8303 | ||
|
|
6b102b4e63 | ||
|
|
71ac12f438 | ||
|
|
035d5fba92 | ||
|
|
729c508a73 | ||
|
|
1796598b52 | ||
|
|
ec031ffeb6 | ||
|
|
9c5ee2e12a | ||
|
|
65386da6d4 | ||
|
|
00799154ef | ||
|
|
d6731c30da | ||
|
|
0b54b86476 | ||
|
|
fd675d29a9 | ||
|
|
d58bbda789 | ||
|
|
50773a62ed | ||
|
|
f5dbdc192e | ||
|
|
f9987b2d7d | ||
|
|
d51af1513b | ||
|
|
2807c949a9 | ||
|
|
0268d4384d | ||
|
|
0e60a56e56 | ||
|
|
c5445d2a8b | ||
|
|
6df1edbbaf | ||
|
|
a098a0805a | ||
|
|
469f1e01ef | ||
|
|
0fc5414f7c | ||
|
|
d798288b2c | ||
|
|
0e02fc2449 | ||
|
|
7d15afe5bc | ||
|
|
b6dd2d14db | ||
|
|
7d4e5bedaf | ||
|
|
1063114408 | ||
|
|
958b4df69f | ||
|
|
f5953c5d8c | ||
|
|
72998034e6 | ||
|
|
cefb344183 | ||
|
|
c0da31abb6 | ||
|
|
bd759a56cf | ||
|
|
befc73c53e | ||
|
|
8dbfc267ac | ||
|
|
d17e80ad94 | ||
|
|
c2b7087723 | ||
|
|
81b37712f1 | ||
|
|
68270a13a3 | ||
|
|
18cd3633e5 | ||
|
|
9c6a4d4a57 | ||
|
|
1a4c48249b | ||
|
|
95d887a417 | ||
|
|
e297b4c03f | ||
|
|
1132377837 | ||
|
|
35f03ae001 | ||
|
|
1c0b54a447 | ||
|
|
8f0cdd514e | ||
|
|
3681caa717 | ||
|
|
7446c43f69 | ||
|
|
5f5e3668eb | ||
|
|
8c07958f6f | ||
|
|
ca1ce9bcc9 | ||
|
|
34a0819bc5 | ||
|
|
c68ea82a5d | ||
|
|
3157054100 | ||
|
|
2486ef1825 | ||
|
|
29f15dc8ed | ||
|
|
31a5a3b3c0 | ||
|
|
d8fa3cfd04 | ||
|
|
2a4b348497 | ||
|
|
3ef3aceb4b | ||
|
|
3d8763b3ce | ||
|
|
b2427a5f20 | ||
|
|
7ddafe62cd | ||
|
|
ff4cc96a81 | ||
|
|
3b1ada64be | ||
|
|
6a08186434 | ||
|
|
cf2b033c79 | ||
|
|
9fbef36c6b | ||
|
|
f5a38aa613 | ||
|
|
25f5058430 | ||
|
|
7b28c2f59a | ||
|
|
99d954de10 | ||
|
|
b28f33481c | ||
|
|
589e33f665 | ||
|
|
39342d7b5e | ||
|
|
fe70650f73 | ||
|
|
e3f9fe892a | ||
|
|
f4373fa244 | ||
|
|
2376802589 | ||
|
|
c3b51d7335 | ||
|
|
d3d8802402 | ||
|
|
d866500c92 | ||
|
|
23879836d9 | ||
|
|
06add21971 | ||
|
|
66fd3d1a01 | ||
|
|
71f7754f51 | ||
|
|
b5591cbd62 | ||
|
|
7430c450c2 | ||
|
|
e54a39cf43 | ||
|
|
3afe0bcdb3 | ||
|
|
b763f7aac1 | ||
|
|
450fcd4d55 | ||
|
|
551b4dea31 | ||
|
|
bd269defb3 | ||
|
|
8998f63a28 | ||
|
|
399ed08926 | ||
|
|
90f02040fe | ||
|
|
8423ecedb1 | ||
|
|
34d1514e11 | ||
|
|
c50d511616 | ||
|
|
dd1479ed82 | ||
|
|
87ec262e39 | ||
|
|
f82845ff42 | ||
|
|
efcc5ccd15 | ||
|
|
6aac50ab56 | ||
|
|
da0920cb60 | ||
|
|
091bce4a3a | ||
|
|
088f6aa407 | ||
|
|
211c8adcce | ||
|
|
fe5366e5b2 | ||
|
|
1ecb0b0f66 | ||
|
|
51e455db79 | ||
|
|
dc6672fa80 | ||
|
|
07b8e7783d | ||
|
|
f17b0f200c | ||
|
|
ad9bde8b1f | ||
|
|
8cf9f9fe23 | ||
|
|
713985d823 | ||
|
|
088f9d0b59 | ||
|
|
53bf5b0d41 | ||
|
|
8c33592628 | ||
|
|
3bbb33f6bd | ||
|
|
5bd9549bd1 | ||
|
|
3481702715 | ||
|
|
c9781ee31d |
3
.github/workflows/selfdrive_tests.yaml
vendored
3
.github/workflows/selfdrive_tests.yaml
vendored
@@ -21,11 +21,12 @@ env:
|
||||
PYTHONWARNINGS: error
|
||||
BASE_IMAGE: sunnypilot-base
|
||||
AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }}
|
||||
MAPBOX_TOKEN_CI: ${{ secrets.MAPBOX_TOKEN_CI }}
|
||||
|
||||
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD: release/ci/docker_build_sp.sh base
|
||||
|
||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -e MAPBOX_TOKEN_CI=$MAPBOX_TOKEN_CI -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||
|
||||
PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical
|
||||
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -109,7 +109,3 @@ Pipfile
|
||||
!.idea/customTargets.xml
|
||||
!.idea/tools/*
|
||||
!.run/*
|
||||
|
||||
### clippy ###
|
||||
clippy_stats.json
|
||||
clippy.log
|
||||
|
||||
@@ -369,6 +369,7 @@ struct CarControlSP @0xa5cd762cd951a455 {
|
||||
leadOne @2 :LeadData;
|
||||
leadTwo @3 :LeadData;
|
||||
intelligentCruiseButtonManagement @4 :IntelligentCruiseButtonManagement;
|
||||
speed @5 :Float32;
|
||||
|
||||
struct Param {
|
||||
key @0 :Text;
|
||||
@@ -454,7 +455,20 @@ struct ModelDataV2SP @0xa1680744031fdb2d {
|
||||
}
|
||||
}
|
||||
|
||||
struct CustomReserved10 @0xcb9fd56c7057593a {
|
||||
struct Navigationd @0xcb9fd56c7057593a {
|
||||
upcomingTurn @0 :Text;
|
||||
currentSpeedLimit @1 :UInt16;
|
||||
bannerInstructions @2 :Text;
|
||||
distanceFromRoute @3 :Float32;
|
||||
allManeuvers @4 :List(Maneuver);
|
||||
valid @5 :Bool;
|
||||
|
||||
struct Maneuver {
|
||||
distance @0 :Float32;
|
||||
type @1 :Text;
|
||||
modifier @2 :Text;
|
||||
instruction @3 :Text;
|
||||
}
|
||||
}
|
||||
|
||||
struct CustomReserved11 @0xc2243c65e0340384 {
|
||||
|
||||
@@ -2632,7 +2632,7 @@ struct Event {
|
||||
carStateSP @114 :Custom.CarStateSP;
|
||||
liveMapDataSP @115 :Custom.LiveMapDataSP;
|
||||
modelDataV2SP @116 :Custom.ModelDataV2SP;
|
||||
customReserved10 @136 :Custom.CustomReserved10;
|
||||
navigationd @136 :Custom.Navigationd;
|
||||
customReserved11 @137 :Custom.CustomReserved11;
|
||||
customReserved12 @138 :Custom.CustomReserved12;
|
||||
customReserved13 @139 :Custom.CustomReserved13;
|
||||
|
||||
@@ -89,6 +89,7 @@ _services: dict[str, tuple] = {
|
||||
"carStateSP": (True, 100., 10),
|
||||
"liveMapDataSP": (True, 1., 1),
|
||||
"modelDataV2SP": (True, 20.),
|
||||
"navigationd": (True, 3.),
|
||||
"liveLocationKalman": (True, 20.),
|
||||
|
||||
# debug
|
||||
|
||||
@@ -187,6 +187,15 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
||||
|
||||
// Navigation params
|
||||
{"AllowNavigation", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"MapboxFavorites", {PERSISTENT | BACKUP, STRING}},
|
||||
{"MapboxToken", {PERSISTENT | BACKUP, STRING}},
|
||||
{"MapboxSettings", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"MapboxRoute", {PERSISTENT, STRING}},
|
||||
{"MapboxRecompute", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"NavDesiresAllowed", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
// Neural Network Lateral Control
|
||||
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
|
||||
Submodule opendbc_repo updated: c32e79f3c6...a26f7827c5
@@ -73,10 +73,6 @@ dependencies = [
|
||||
|
||||
# ui
|
||||
"qrcode",
|
||||
|
||||
# clippy
|
||||
"discord-py",
|
||||
"flask",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
@@ -3,6 +3,7 @@ from openpilot.common.constants import CV
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController
|
||||
from openpilot.sunnypilot.navd.navigation_desires.navigation_desires import NavigationDesires
|
||||
|
||||
LaneChangeState = log.LaneChangeState
|
||||
LaneChangeDirection = log.LaneChangeDirection
|
||||
@@ -51,6 +52,7 @@ class DesireHelper:
|
||||
self.alc = AutoLaneChangeController(self)
|
||||
self.lane_turn_controller = LaneTurnController(self)
|
||||
self.lane_turn_direction = TurnDirection.none
|
||||
self.navigation_desires = NavigationDesires()
|
||||
|
||||
@staticmethod
|
||||
def get_lane_change_direction(CS):
|
||||
@@ -143,3 +145,7 @@ class DesireHelper:
|
||||
self.desire = log.Desire.none
|
||||
|
||||
self.alc.update_state()
|
||||
|
||||
nav_desire = self.navigation_desires.update(carstate, lateral_active)
|
||||
if nav_desire != log.Desire.none and (self.desire == log.Desire.none or self.desire in (log.Desire.turnLeft, log.Desire.turnRight)):
|
||||
self.desire = nav_desire
|
||||
|
||||
@@ -99,7 +99,7 @@ class SelfdriveD(CruiseHelper):
|
||||
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
|
||||
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback',
|
||||
'modelDataV2SP', 'longitudinalPlanSP'] + \
|
||||
self.camera_packets + self.sensor_packets + self.gps_packets,
|
||||
self.camera_packets + self.sensor_packets + self.gps_packets,
|
||||
ignore_alive=ignore, ignore_avg_freq=ignore,
|
||||
ignore_valid=ignore, frequency=int(1/DT_CTRL))
|
||||
|
||||
@@ -293,7 +293,7 @@ class SelfdriveD(CruiseHelper):
|
||||
if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange:
|
||||
direction = self.sm['modelV2'].meta.laneChangeDirection
|
||||
if (CS.leftBlindspot and direction == LaneChangeDirection.left) or \
|
||||
(CS.rightBlindspot and direction == LaneChangeDirection.right):
|
||||
(CS.rightBlindspot and direction == LaneChangeDirection.right):
|
||||
self.events.add(EventName.laneChangeBlocked)
|
||||
else:
|
||||
if direction == LaneChangeDirection.left:
|
||||
@@ -301,7 +301,7 @@ class SelfdriveD(CruiseHelper):
|
||||
else:
|
||||
self.events.add(EventName.preLaneChangeRight)
|
||||
elif self.sm['modelV2'].meta.laneChangeState in (LaneChangeState.laneChangeStarting,
|
||||
LaneChangeState.laneChangeFinishing):
|
||||
LaneChangeState.laneChangeFinishing):
|
||||
self.events.add(EventName.laneChange)
|
||||
|
||||
# Handle lane turn
|
||||
@@ -496,7 +496,7 @@ class SelfdriveD(CruiseHelper):
|
||||
|
||||
# All pandas not in silent mode must have controlsAllowed when openpilot is enabled
|
||||
if self.enabled and any(not ps.controlsAllowed for ps in self.sm['pandaStates']
|
||||
if ps.safetyModel not in IGNORED_SAFETY_MODES):
|
||||
if ps.safetyModel not in IGNORED_SAFETY_MODES):
|
||||
self.mismatch_counter += 1
|
||||
|
||||
return CS
|
||||
|
||||
@@ -118,7 +118,7 @@ void AnnotatedCameraWidget::paintGL() {
|
||||
} else if (v_ego > 15) {
|
||||
wide_cam_requested = false;
|
||||
}
|
||||
// wide_cam_requested = wide_cam_requested && sm["selfdriveState"].getSelfdriveState().getExperimentalMode();
|
||||
wide_cam_requested = wide_cam_requested && sm["selfdriveState"].getSelfdriveState().getExperimentalMode();
|
||||
}
|
||||
CameraWidget::setStreamType(wide_cam_requested ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD);
|
||||
CameraWidget::setFrameId(sm["modelV2"].getModelV2().getFrameId());
|
||||
|
||||
@@ -30,6 +30,7 @@ qt_src = [
|
||||
"sunnypilot/qt/offroad/settings/max_time_offroad.cc",
|
||||
"sunnypilot/qt/offroad/settings/brightness.cc",
|
||||
"sunnypilot/qt/offroad/settings/models_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/navigation_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/osm_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/software_panel.cc",
|
||||
|
||||
190
selfdrive/ui/sunnypilot/qt/offroad/settings/navigation_panel.cc
Normal file
190
selfdrive/ui/sunnypilot/qt/offroad/settings/navigation_panel.cc
Normal file
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/navigation_panel.h"
|
||||
|
||||
NavigationPanel::NavigationPanel(QWidget* parent) : QWidget(parent) {
|
||||
QVBoxLayout* main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(50, 25, 50, 25);
|
||||
|
||||
list = new ListWidget(this, false);
|
||||
scroller = new ScrollViewSP(list, this);
|
||||
main_layout->addWidget(scroller);
|
||||
|
||||
// Mapbox Token
|
||||
mapbox_token = new ButtonControl(tr("Mapbox token"), tr("Edit"), tr("Enter your mapbox public token"));
|
||||
QObject::connect(mapbox_token, &ButtonControl::clicked, [=]() {
|
||||
QString current = QString::fromStdString(params.get("MapboxToken"));
|
||||
QString token = InputDialog::getText(tr("Enter Mapbox Token"), this, "", false, -1, current);
|
||||
if (!token.isEmpty()) {
|
||||
params.put("MapboxToken", token.toStdString());
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
list->addItem(mapbox_token);
|
||||
|
||||
// Mapbox Route
|
||||
mapbox_route = new ButtonControl(tr("Mapbox route"), tr("Edit"), tr(""));
|
||||
QObject::connect(mapbox_route, &ButtonControl::clicked, [=]() {
|
||||
QString current = QString::fromStdString(params.get("MapboxRoute"));
|
||||
QString route = InputDialog::getText(tr("Enter Mapbox Route"), this, "", false, -1, current);
|
||||
if (!route.isEmpty()) {
|
||||
params.put("MapboxRoute", route.toStdString());
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
list->addItem(mapbox_route);
|
||||
|
||||
// Clear Route
|
||||
clear_route = new ButtonControl(tr("Clear current route"), tr("Clear"), tr(""));
|
||||
QObject::connect(clear_route, &ButtonControl::clicked, [=]() {
|
||||
params.remove("MapboxRoute");
|
||||
refresh();
|
||||
});
|
||||
list->addItem(clear_route);
|
||||
|
||||
// Favs selector
|
||||
favorites_selector = new MultiButtonControlSP("Favorites", "Select favorite route", "", {"Home", "Work", "Favs"}, 470);
|
||||
QObject::connect(favorites_selector, &MultiButtonControlSP::buttonClicked, [=](int id) {
|
||||
QString favs_str = QString::fromStdString(params.get("MapboxFavorites"));
|
||||
QJsonDocument doc = QJsonDocument::fromJson(favs_str.toUtf8());
|
||||
QJsonObject obj = doc.object();
|
||||
QString route;
|
||||
if (id == 0) route = obj["home"].toString();
|
||||
else if (id == 1) route = obj["work"].toString();
|
||||
else if (id == 2) {
|
||||
QJsonObject favorites_obj = obj["favorites"].toObject();
|
||||
if (favorites_obj.isEmpty()) {
|
||||
ConfirmationDialog::alert(tr("No custom favorites set"), this);
|
||||
return;
|
||||
}
|
||||
QStringList favNames = favorites_obj.keys();
|
||||
const QString selected = MultiOptionDialog::getSelection(tr("Select Favorite"), favNames, "", this);
|
||||
if (!selected.isEmpty()) {
|
||||
route = favorites_obj[selected].toString();
|
||||
}
|
||||
}
|
||||
if (!route.isEmpty()) {
|
||||
params.put("MapboxRoute", route.toStdString());
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
list->addItem(favorites_selector);
|
||||
|
||||
struct FavInfo {
|
||||
QString key;
|
||||
QString title;
|
||||
QString desc;
|
||||
};
|
||||
std::vector<FavInfo> favs = {
|
||||
{"home", tr("Set Home"), tr("")},
|
||||
{"work", tr("Set Work"), tr("")}
|
||||
};
|
||||
set_buttons.resize(favs.size());
|
||||
for (size_t i = 0; i < favs.size(); ++i) {
|
||||
const auto& fav = favs[i];
|
||||
set_buttons[i] = new ButtonControl(fav.title, tr("Set"), fav.desc);
|
||||
QObject::connect(set_buttons[i], &ButtonControl::clicked, [this, fav]() {
|
||||
QString favs_str = QString::fromStdString(params.get("MapboxFavorites"));
|
||||
QJsonDocument doc = QJsonDocument::fromJson(favs_str.toUtf8());
|
||||
QJsonObject obj = doc.object();
|
||||
QString current = obj[fav.key].toString();
|
||||
QString route = InputDialog::getText(tr("Set %1 Route").arg(fav.title.mid(4)), this, "", false, -1, current); // mid(4) to remove "Set "
|
||||
if (!route.isEmpty()) {
|
||||
obj[fav.key] = route;
|
||||
QJsonDocument new_doc(obj);
|
||||
params.put("MapboxFavorites", new_doc.toJson(QJsonDocument::Compact).toStdString());
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
list->addItem(set_buttons[i]);
|
||||
}
|
||||
|
||||
add_fav = new ButtonControl(tr("Add Favorite"), tr("Add"), tr("Add a new custom favorite"));
|
||||
QObject::connect(add_fav, &ButtonControl::clicked, [=]() {
|
||||
QString name = InputDialog::getText(tr("Favorite Name"), this, "", false, -1, "");
|
||||
if (name.isEmpty()) return;
|
||||
|
||||
QString route = InputDialog::getText(tr("Favorite Route"), this, "", false, -1, "");
|
||||
if (route.isEmpty()) return;
|
||||
|
||||
QString favs_str = QString::fromStdString(params.get("MapboxFavorites"));
|
||||
QJsonDocument doc = QJsonDocument::fromJson(favs_str.toUtf8());
|
||||
QJsonObject obj = doc.object();
|
||||
QJsonObject favorites_obj = obj["favorites"].toObject();
|
||||
favorites_obj[name] = route;
|
||||
obj["favorites"] = favorites_obj;
|
||||
QJsonDocument new_doc(obj);
|
||||
params.put("MapboxFavorites", new_doc.toJson(QJsonDocument::Compact).toStdString());
|
||||
refresh();
|
||||
});
|
||||
list->addItem(add_fav);
|
||||
|
||||
remove_fav = new ButtonControl(tr("Remove Favorite"), tr("Remove"), tr("Remove a custom favorite"));
|
||||
QObject::connect(remove_fav, &ButtonControl::clicked, [=]() {
|
||||
QString favs_str = QString::fromStdString(params.get("MapboxFavorites"));
|
||||
QJsonDocument doc = QJsonDocument::fromJson(favs_str.toUtf8());
|
||||
QJsonObject obj = doc.object();
|
||||
QJsonObject favorites_obj = obj["favorites"].toObject();
|
||||
if (favorites_obj.isEmpty()) {
|
||||
ConfirmationDialog::alert(tr("No custom favorites to remove"), this);
|
||||
return;
|
||||
}
|
||||
QStringList favNames = favorites_obj.keys();
|
||||
const QString selected = MultiOptionDialog::getSelection(tr("Remove Favorite"), favNames, "", this);
|
||||
if (!selected.isEmpty()) {
|
||||
favorites_obj.remove(selected);
|
||||
obj["favorites"] = favorites_obj;
|
||||
QJsonDocument new_doc(obj);
|
||||
params.put("MapboxFavorites", new_doc.toJson(QJsonDocument::Compact).toStdString());
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
list->addItem(remove_fav);
|
||||
|
||||
// Dumb params
|
||||
allow_navigation = new ParamControlSP("AllowNavigation", tr("Allow navigation"), tr("Enable navigation features and start navigationd"), "", this);
|
||||
QObject::connect(allow_navigation, &ParamControlSP::toggleFlipped, this, &NavigationPanel::updateNavigationVisibility);
|
||||
list->addItem(allow_navigation);
|
||||
|
||||
mapbox_recompute = new ParamControlSP("MapboxRecompute", tr("Mapbox recompute"), tr("Enable automatic route recomputation"), "", this);
|
||||
list->addItem(mapbox_recompute);
|
||||
|
||||
nav_allowed = new ParamControlSP("NavDesiresAllowed", tr("Navigation allowed"), tr("Allow navigation to automatically take turns"), "", this);
|
||||
list->addItem(nav_allowed);
|
||||
}
|
||||
|
||||
void NavigationPanel::updateNavigationVisibility(bool state) {
|
||||
mapbox_recompute->setVisible(state);
|
||||
nav_allowed->setVisible(state);
|
||||
favorites_selector->setVisible(state);
|
||||
for (auto btn : set_buttons) {
|
||||
btn->setVisible(state);
|
||||
}
|
||||
add_fav->setVisible(state);
|
||||
remove_fav->setVisible(state);
|
||||
}
|
||||
|
||||
void NavigationPanel::showEvent(QShowEvent *event) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
void NavigationPanel::refresh() {
|
||||
allow_navigation->refresh();
|
||||
|
||||
bool nav_enabled = allow_navigation->isToggled();
|
||||
updateNavigationVisibility(nav_enabled);
|
||||
|
||||
QString token = QString::fromStdString(params.get("MapboxToken"));
|
||||
mapbox_token->setValue(token.isEmpty() ? tr("Not set") : token);
|
||||
|
||||
QString route = QString::fromStdString(params.get("MapboxRoute"));
|
||||
mapbox_route->setValue(route.isEmpty() ? tr("Not set") : route);
|
||||
|
||||
mapbox_recompute->refresh();
|
||||
nav_allowed->refresh();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
*
|
||||
* This file is part of sunnypilot and is licensed under the MIT License.
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <vector>
|
||||
|
||||
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
||||
|
||||
class NavigationPanel : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NavigationPanel(QWidget* parent = nullptr);
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void refresh();
|
||||
|
||||
public slots:
|
||||
void updateNavigationVisibility(bool state);
|
||||
|
||||
private:
|
||||
Params params;
|
||||
ListWidget* list;
|
||||
ScrollViewSP* scroller;
|
||||
ParamControlSP* allow_navigation;
|
||||
ButtonControl* mapbox_token;
|
||||
ButtonControl* mapbox_route;
|
||||
ButtonControl* clear_route;
|
||||
ParamControlSP* mapbox_recompute;
|
||||
ParamControlSP* nav_allowed;
|
||||
MultiButtonControlSP* favorites_selector;
|
||||
std::vector<ButtonControl*> set_buttons;
|
||||
ButtonControl* add_fav;
|
||||
ButtonControl* remove_fav;
|
||||
};
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/display_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/navigation_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h"
|
||||
@@ -85,6 +86,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) {
|
||||
PanelInfo(" " + tr("Toggles"), toggles, "../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"),
|
||||
PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../../sunnypilot/selfdrive/assets/offroad/icon_software.png"),
|
||||
PanelInfo(" " + tr("Models"), new ModelsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_models.png"),
|
||||
PanelInfo(" " + tr("Navigation"), new NavigationPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_map.png"),
|
||||
PanelInfo(" " + tr("Steering"), new LateralPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"),
|
||||
PanelInfo(" " + tr("Cruise"), new LongitudinalPanel(this), "../assets/icons/speed_limit.png"),
|
||||
PanelInfo(" " + tr("Visuals"), new VisualsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_visuals.png"),
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* See the LICENSE.md file in the root directory for more details.
|
||||
*/
|
||||
#include <QPainterPath>
|
||||
#include <cmath>
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/onroad/hud.h"
|
||||
|
||||
@@ -154,6 +155,73 @@ void HudRendererSP::updateState(const UIState &s) {
|
||||
|
||||
allow_e2e_alerts = sm["selfdriveState"].getSelfdriveState().getAlertSize() == cereal::SelfdriveState::AlertSize::NONE &&
|
||||
sm.rcv_frame("driverStateV2") > s.scene.started_frame && !reversing;
|
||||
|
||||
// Navigationd
|
||||
if (sm.updated("navigationd")) {
|
||||
auto nav = sm["navigationd"].getNavigationd();
|
||||
navigationValid = nav.getValid();
|
||||
if (navigationValid && nav.getAllManeuvers().size() > 0) {
|
||||
int currManeuverIdx = nav.getAllManeuvers().size() > 1 ? 1 : 0;
|
||||
auto maneuver = nav.getAllManeuvers()[currManeuverIdx];
|
||||
navigationModifier = QString::fromStdString(maneuver.getModifier());
|
||||
navigationManeuverType = QString::fromStdString(maneuver.getType());
|
||||
|
||||
float dist = maneuver.getDistance();
|
||||
if (is_metric) {
|
||||
if (dist < 1000) {
|
||||
if (dist < 500) {
|
||||
navigationDistance = QString::number(std::round(dist / 25.0) * 25) + " m";
|
||||
}
|
||||
else {
|
||||
navigationDistance = QString::number(std::round(dist / 50.0) * 50) + " m";
|
||||
}
|
||||
} else {
|
||||
float dist_km = dist / 1000;
|
||||
if (dist_km >= 10.0) {
|
||||
navigationDistance = QString::number(std::floor(dist_km)) + " km";
|
||||
} else {
|
||||
navigationDistance = QString::number(dist_km, 'f', 1) + " km";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
float dist_ft = dist * 3.28084f;
|
||||
if (dist_ft < 1000) {
|
||||
if (dist_ft <= 100){
|
||||
navigationDistance = QString::number((std::round(dist_ft / 10.0) * 10)) + " ft";
|
||||
}
|
||||
else {
|
||||
navigationDistance = QString::number((std::round(dist_ft / 50.0) * 50)) + " ft";
|
||||
}
|
||||
} else {
|
||||
float dist_mi = dist_ft / 5280;
|
||||
if (dist_mi >= 10.0) {
|
||||
navigationDistance = QString::number(std::floor(dist_mi)) + " mi";
|
||||
} else {
|
||||
navigationDistance = QString::number(dist_mi, 'f', 1) + " mi";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString instruction = QString::fromStdString(maneuver.getInstruction());
|
||||
QStringList parts = instruction.split(" onto ");
|
||||
if (parts.size() > 1) {
|
||||
navigationStreet = parts[1].trimmed();
|
||||
} else {
|
||||
navigationStreet = instruction;
|
||||
}
|
||||
navigationStreet = navigationStreet.replace(".", "");
|
||||
|
||||
// Get next maneuver if available
|
||||
if (nav.getAllManeuvers().size() > 2) {
|
||||
auto nextManeuver = nav.getAllManeuvers()[2];
|
||||
navigationNextModifier = QString::fromStdString(nextManeuver.getModifier());
|
||||
navigationNextManeuverType = QString::fromStdString(nextManeuver.getType());
|
||||
navigationHasNext = true;
|
||||
} else {
|
||||
navigationHasNext = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
|
||||
@@ -287,6 +355,8 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
|
||||
}
|
||||
}
|
||||
|
||||
drawNavigationHUD(p, surface_rect);
|
||||
|
||||
p.restore();
|
||||
}
|
||||
|
||||
@@ -306,9 +376,14 @@ bool HudRendererSP::pulseElement(int frame) {
|
||||
}
|
||||
|
||||
void HudRendererSP::drawSmartCruiseControlOnroadIcon(QPainter &p, const QRect &surface_rect, int x_offset, int y_offset, std::string name) {
|
||||
int x = surface_rect.center().x();
|
||||
int base_x = surface_rect.center().x();
|
||||
int y = surface_rect.height() / 4;
|
||||
|
||||
if (navigationValid) {
|
||||
base_x = 618;
|
||||
y = 420;
|
||||
}
|
||||
|
||||
QString text = QString::fromStdString(name);
|
||||
QFont font = InterFont(36, QFont::Bold);
|
||||
p.setFont(font);
|
||||
@@ -319,7 +394,7 @@ void HudRendererSP::drawSmartCruiseControlOnroadIcon(QPainter &p, const QRect &s
|
||||
int box_width = 160;
|
||||
int box_height = fm.height() + padding_v * 2;
|
||||
|
||||
QRectF bg_rect(x - (box_width / 2) + x_offset,
|
||||
QRectF bg_rect(base_x - (box_width / 2) + x_offset,
|
||||
y - (box_height / 2) + y_offset,
|
||||
box_width, box_height);
|
||||
|
||||
@@ -684,15 +759,16 @@ void HudRendererSP::drawSpeedLimitPreActiveArrow(QPainter &p, QRect &sign_rect)
|
||||
}
|
||||
|
||||
void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) {
|
||||
// Draw outer box + border to contain set speed
|
||||
const QSize default_size = {172, 204};
|
||||
QSize set_speed_size = is_metric ? QSize(200, 204) : default_size;
|
||||
QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size);
|
||||
|
||||
// Draw set speed box
|
||||
p.setPen(QPen(QColor(255, 255, 255, 75), 6));
|
||||
p.setBrush(QColor(0, 0, 0, 166));
|
||||
p.drawRoundedRect(set_speed_rect, 32, 32);
|
||||
// ICBM counter logic
|
||||
if (!pcmCruiseSpeed && carControlEnabled) {
|
||||
if (std::nearbyint(set_speed) != std::nearbyint(speedCluster)) {
|
||||
icbm_active_counter = 3 * UI_FREQ;
|
||||
} else if (icbm_active_counter > 0) {
|
||||
icbm_active_counter--;
|
||||
}
|
||||
} else {
|
||||
icbm_active_counter = 0;
|
||||
}
|
||||
|
||||
// Colors based on status
|
||||
QColor max_color = QColor(0xa6, 0xa6, 0xa6, 0xff);
|
||||
@@ -713,29 +789,62 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw "MAX" or carState.cruiseState.speedCluster (when ICBM is active) text
|
||||
if (!pcmCruiseSpeed && carControlEnabled) {
|
||||
if (std::nearbyint(set_speed) != std::nearbyint(speedCluster)) {
|
||||
icbm_active_counter = 3 * UI_FREQ;
|
||||
} else if (icbm_active_counter > 0) {
|
||||
icbm_active_counter--;
|
||||
}
|
||||
} else {
|
||||
icbm_active_counter = 0;
|
||||
}
|
||||
int max_str_size = (icbm_active_counter != 0) ? 60 : 40;
|
||||
int max_str_y = (icbm_active_counter != 0) ? 15 : 27;
|
||||
QString max_str = (icbm_active_counter != 0) ? QString::number(std::nearbyint(speedCluster)) : tr("MAX");
|
||||
|
||||
p.setFont(InterFont(max_str_size, QFont::DemiBold));
|
||||
p.setPen(max_color);
|
||||
p.drawText(set_speed_rect.adjusted(0, max_str_y, 0, 0), Qt::AlignTop | Qt::AlignHCenter, max_str);
|
||||
|
||||
// Draw set speed
|
||||
QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(set_speed)) : "–";
|
||||
p.setFont(InterFont(90, QFont::Bold));
|
||||
p.setPen(set_speed_color);
|
||||
p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr);
|
||||
|
||||
if (!navigationValid) {
|
||||
// Original positions when navigation is not valid
|
||||
const QSize default_size = {172, 204};
|
||||
QSize set_speed_size = is_metric ? QSize(200, 204) : default_size;
|
||||
QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size);
|
||||
|
||||
// Draw set speed box
|
||||
p.setPen(QPen(QColor(255, 255, 255, 75), 6));
|
||||
p.setBrush(QColor(0, 0, 0, 166));
|
||||
p.drawRoundedRect(set_speed_rect, 32, 32);
|
||||
|
||||
// Draw "MAX" or carState.cruiseState.speedCluster (when ICBM is active) text
|
||||
int max_str_size = (icbm_active_counter != 0) ? 60 : 40;
|
||||
int max_str_y = (icbm_active_counter != 0) ? 15 : 27;
|
||||
|
||||
p.setFont(InterFont(max_str_size, QFont::DemiBold));
|
||||
p.setPen(max_color);
|
||||
p.drawText(set_speed_rect.adjusted(0, max_str_y, 0, 0), Qt::AlignTop | Qt::AlignHCenter, max_str);
|
||||
|
||||
// Draw set speed
|
||||
p.setFont(InterFont(90, QFont::Bold));
|
||||
p.setPen(set_speed_color);
|
||||
p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr);
|
||||
} else {
|
||||
// Modified positions when navigation is valid
|
||||
const int container_width = 200;
|
||||
const int container_height = 320;
|
||||
const int container_x = 40;
|
||||
const int container_y = 45;
|
||||
|
||||
QRect speed_container(container_x, container_y, container_width, container_height);
|
||||
|
||||
// Draw outer rounded rectangle container
|
||||
p.setPen(QPen(QColor(255, 255, 255, 75), 6));
|
||||
p.setBrush(QColor(0, 0, 0, 166));
|
||||
p.drawRoundedRect(speed_container, 24, 24);
|
||||
|
||||
int divider_y = container_y + 190;
|
||||
p.setPen(QPen(QColor(255, 255, 255, 50), 2));
|
||||
p.drawLine(container_x + 20, divider_y, container_x + container_width - 20, divider_y);
|
||||
|
||||
// max label
|
||||
QRect max_label_rect(container_x, container_y + 200, container_width, 35);
|
||||
p.setFont(InterFont(32, QFont::Normal));
|
||||
p.setPen(max_color);
|
||||
p.drawText(max_label_rect, Qt::AlignCenter, max_str);
|
||||
|
||||
// Set speed value
|
||||
QRect set_speed_rect(container_x, container_y + 240, container_width, 70);
|
||||
p.setFont(InterFont(68, QFont::Bold));
|
||||
p.setPen(set_speed_color);
|
||||
p.drawText(set_speed_rect, Qt::AlignCenter, setSpeedStr);
|
||||
}
|
||||
}
|
||||
|
||||
void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const QString &alert_alt_text) {
|
||||
@@ -794,12 +903,40 @@ void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const Q
|
||||
|
||||
void HudRendererSP::drawCurrentSpeedSP(QPainter &p, const QRect &surface_rect) {
|
||||
QString speedStr = QString::number(std::nearbyint(speed));
|
||||
QString unit = is_metric ? tr("km/h") : tr("mph");
|
||||
|
||||
p.setFont(InterFont(176, QFont::Bold));
|
||||
HudRenderer::drawText(p, surface_rect.center().x(), 210, speedStr);
|
||||
int speed_x = surface_rect.center().x();
|
||||
int speed_y = 210;
|
||||
int unit_y = 290;
|
||||
QFont speed_font = InterFont(176, QFont::Bold);
|
||||
QFont unit_font = InterFont(66);
|
||||
|
||||
p.setFont(InterFont(66));
|
||||
HudRenderer::drawText(p, surface_rect.center().x(), 290, is_metric ? tr("km/h") : tr("mph"), 200);
|
||||
if (navigationValid) {
|
||||
speed_y = 75;
|
||||
unit_y = 175;
|
||||
speed_font = InterFont(100, QFont::Bold);
|
||||
unit_font = InterFont(35, QFont::Normal);
|
||||
}
|
||||
|
||||
// Draw speed
|
||||
p.setFont(speed_font);
|
||||
if (!navigationValid) {
|
||||
HudRenderer::drawText(p, speed_x, speed_y, speedStr);
|
||||
} else {
|
||||
QRect current_speed_rect(40, speed_y, 200, 100);
|
||||
p.setPen(Qt::white);
|
||||
p.drawText(current_speed_rect, Qt::AlignCenter, speedStr);
|
||||
}
|
||||
|
||||
// Draw unit
|
||||
p.setFont(unit_font);
|
||||
if (!navigationValid) {
|
||||
HudRenderer::drawText(p, speed_x, unit_y, unit, 200);
|
||||
} else {
|
||||
QRect unit_rect(40, unit_y, 200, 40);
|
||||
p.setPen(QColor(180, 180, 180, 255));
|
||||
p.drawText(unit_rect, Qt::AlignCenter, unit);
|
||||
}
|
||||
}
|
||||
|
||||
void HudRendererSP::drawBlinker(QPainter &p, const QRect &surface_rect) {
|
||||
@@ -827,7 +964,7 @@ void HudRendererSP::drawBlinker(QPainter &p, const QRect &surface_rect) {
|
||||
const int circleRadius = 60;
|
||||
const int arrowLength = 60;
|
||||
const int x_gap = 160;
|
||||
const int y_offset = 272;
|
||||
const int y_offset = navigationValid ? 352 : 300;
|
||||
|
||||
const int centerX = surface_rect.center().x();
|
||||
|
||||
@@ -885,3 +1022,186 @@ void HudRendererSP::drawBlinker(QPainter &p, const QRect &surface_rect) {
|
||||
|
||||
p.restore();
|
||||
}
|
||||
|
||||
QString HudRendererSP::getNavigationIconName(const QString &type, const QString &mod) {
|
||||
static QMap<QString, QString> icon_map;
|
||||
if (icon_map.isEmpty()) {
|
||||
icon_map["turn|uturn"] = "direction_uturn.png";
|
||||
icon_map["turn|sharp right"] = "direction_turn_sharp_right.png";
|
||||
icon_map["turn|right"] = "direction_turn_right.png";
|
||||
icon_map["turn|slight right"] = "direction_turn_slight_right.png";
|
||||
icon_map["turn|straight"] = "direction_turn_straight.png";
|
||||
icon_map["turn|slight left"] = "direction_turn_slight_left.png";
|
||||
icon_map["turn|left"] = "direction_turn_left.png";
|
||||
icon_map["turn|sharp left"] = "direction_turn_sharp_left.png";
|
||||
|
||||
icon_map["arrive|right"] = "direction_arrive_right.png";
|
||||
icon_map["arrive|straight"] = "direction_arrive_straight.png";
|
||||
icon_map["arrive|left"] = "direction_arrive_left.png";
|
||||
icon_map["arrive|"] = "direction_arrive.png";
|
||||
|
||||
icon_map["merge|slight right"] = "direction_merge_slight_right.png";
|
||||
icon_map["merge|right"] = "direction_merge_right.png";
|
||||
icon_map["merge|straight"] = "direction_merge_straight.png";
|
||||
icon_map["merge|slight left"] = "direction_merge_slight_left.png";
|
||||
icon_map["merge|left"] = "direction_merge_left.png";
|
||||
|
||||
icon_map["on ramp|sharp right"] = "direction_on_ramp_sharp_right.png";
|
||||
icon_map["on ramp|right"] = "direction_on_ramp_right.png";
|
||||
icon_map["on ramp|slight right"] = "direction_on_ramp_slight_right.png";
|
||||
icon_map["on ramp|straight"] = "direction_on_ramp_straight.png";
|
||||
icon_map["on ramp|slight left"] = "direction_on_ramp_slight_left.png";
|
||||
icon_map["on ramp|left"] = "direction_on_ramp_left.png";
|
||||
icon_map["on ramp|sharp left"] = "direction_on_ramp_sharp_left.png";
|
||||
|
||||
icon_map["off ramp|slight right"] = "direction_off_ramp_slight_right.png";
|
||||
icon_map["off ramp|right"] = "direction_off_ramp_right.png";
|
||||
icon_map["off ramp|slight left"] = "direction_off_ramp_slight_left.png";
|
||||
icon_map["off ramp|left"] = "direction_off_ramp_left.png";
|
||||
|
||||
icon_map["roundabout|sharp right"] = "direction_roundabout_sharp_right.png";
|
||||
icon_map["roundabout|right"] = "direction_roundabout_right.png";
|
||||
icon_map["roundabout|slight right"] = "direction_roundabout_slight_right.png";
|
||||
icon_map["roundabout|straight"] = "direction_roundabout_straight.png";
|
||||
icon_map["roundabout|slight left"] = "direction_roundabout_slight_left.png";
|
||||
icon_map["roundabout|left"] = "direction_roundabout_left.png";
|
||||
icon_map["roundabout|sharp left"] = "direction_roundabout_sharp_left.png";
|
||||
icon_map["roundabout|"] = "direction_roundabout.png";
|
||||
}
|
||||
|
||||
QString normalized_type = type;
|
||||
if (normalized_type == "rotary") {
|
||||
normalized_type = "roundabout";
|
||||
} else if (normalized_type == "new name") {
|
||||
normalized_type = "turn";
|
||||
} else if (normalized_type == "continue") {
|
||||
normalized_type = "turn";
|
||||
}
|
||||
|
||||
QString icon_name;
|
||||
QStringList keys = {normalized_type + "|" + mod, normalized_type + "|", "turn|" + mod};
|
||||
for (const QString &key : keys) {
|
||||
icon_name = icon_map.value(key);
|
||||
if (!icon_name.isEmpty()) break;
|
||||
}
|
||||
if (icon_name.isEmpty()) {
|
||||
icon_name = "direction_turn_straight.png";
|
||||
}
|
||||
return icon_name;
|
||||
}
|
||||
|
||||
void HudRendererSP::drawNavigationHUD(QPainter &p, const QRect &surface_rect) {
|
||||
if (!navigationValid) return;
|
||||
p.save();
|
||||
|
||||
const int container_width = 1080;
|
||||
const int container_height = 225;
|
||||
int container_x = (surface_rect.width() - container_width) / 2;
|
||||
const int container_y = 62;
|
||||
const int border_radius = 42;
|
||||
|
||||
|
||||
if (speedLimitAssistState == cereal::LongitudinalPlanSP::SpeedLimit::AssistState::PRE_ACTIVE) {
|
||||
container_x += 190;
|
||||
}
|
||||
|
||||
QRect container_rect(container_x, container_y, container_width, container_height);
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(QColor(0, 0, 0, 180));
|
||||
p.drawRoundedRect(container_rect, border_radius, border_radius);
|
||||
|
||||
// Navigation icon
|
||||
const int icon_size = 150;
|
||||
const int icon_padding = 30;
|
||||
const int icon_x = container_x + icon_padding;
|
||||
const int icon_y = container_y;
|
||||
|
||||
QString icon_name = getNavigationIconName(navigationManeuverType, navigationModifier);
|
||||
QPixmap nav_icon = loadPixmap("../../sunnypilot/selfdrive/assets/navigation/" + icon_name, {icon_size, icon_size});
|
||||
|
||||
if (!nav_icon.isNull()) {
|
||||
p.drawPixmap(icon_x, icon_y, nav_icon);
|
||||
}
|
||||
|
||||
// Distance
|
||||
p.setFont(InterFont(48, QFont::Bold));
|
||||
p.setPen(Qt::white);
|
||||
QRect distance_rect(icon_x - 25, icon_y + icon_size, icon_size + 50, 38);
|
||||
p.drawText(distance_rect, Qt::AlignCenter, navigationDistance);
|
||||
|
||||
const int then_section_width = 180;
|
||||
const int text_x = icon_x + icon_size + 53;
|
||||
const int text_area_width = container_width - (text_x - container_x) - icon_padding - then_section_width;
|
||||
|
||||
// Street name
|
||||
p.setFont(InterFont(75, QFont::Bold));
|
||||
p.setPen(Qt::white);
|
||||
QFontMetrics fm(p.font());
|
||||
|
||||
QString street_line1, street_line2;
|
||||
QStringList words = navigationStreet.split(' ');
|
||||
QString currentLine;
|
||||
|
||||
for (int i = 0; i < words.size(); ++i) {
|
||||
QString testLine = currentLine.isEmpty() ? words[i] : currentLine + " " + words[i];
|
||||
if (fm.horizontalAdvance(testLine) <= text_area_width) {
|
||||
currentLine = testLine;
|
||||
} else {
|
||||
if (street_line1.isEmpty()) {
|
||||
street_line1 = currentLine;
|
||||
currentLine = words[i];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (street_line1.isEmpty()) {
|
||||
street_line1 = currentLine;
|
||||
} else if (!currentLine.isEmpty()) {
|
||||
street_line2 = currentLine;
|
||||
if (words.size() > words.indexOf(currentLine.split(' ').last()) + 1) {
|
||||
street_line2 = fm.elidedText(street_line2, Qt::ElideRight, text_area_width);
|
||||
}
|
||||
}
|
||||
|
||||
if (street_line2.isEmpty()) {
|
||||
QRect street_rect(text_x, container_y + (container_height - fm.height()) / 2, text_area_width, fm.height());
|
||||
p.drawText(street_rect, Qt::AlignLeft | Qt::AlignVCenter, street_line1);
|
||||
} else {
|
||||
QRect street_rect1(text_x, container_y + 23, text_area_width, fm.height());
|
||||
p.drawText(street_rect1, Qt::AlignLeft | Qt::AlignVCenter, street_line1);
|
||||
|
||||
QRect street_rect2(text_x, container_y + 23 + fm.height(), text_area_width, fm.height());
|
||||
p.drawText(street_rect2, Qt::AlignLeft | Qt::AlignVCenter, street_line2);
|
||||
}
|
||||
|
||||
// Next Maneuver
|
||||
if (navigationHasNext) {
|
||||
const int divider_x = container_x + container_width - then_section_width - 8;
|
||||
p.setPen(QPen(QColor(255, 255, 255, 50), 2));
|
||||
p.drawLine(divider_x, container_y + 23, divider_x, container_y + container_height - 23);
|
||||
|
||||
const int then_x = divider_x + 15;
|
||||
const int then_icon_size = 105;
|
||||
|
||||
QRect then_label_rect(then_x, container_y + 30, then_section_width - 23, 38);
|
||||
p.setFont(InterFont(53, QFont::Medium));
|
||||
p.setPen(Qt::white);
|
||||
p.drawText(then_label_rect, Qt::AlignCenter, tr("Then"));
|
||||
|
||||
// Next maneuver icon
|
||||
const int then_icon_x = then_x + (then_section_width - 23 - then_icon_size) / 2;
|
||||
const int then_icon_y = container_y + 75;
|
||||
|
||||
QString next_icon_name = getNavigationIconName(navigationNextManeuverType, navigationNextModifier);
|
||||
QPixmap next_nav_icon = loadPixmap("../../sunnypilot/selfdrive/assets/navigation/" + next_icon_name, {then_icon_size, then_icon_size});
|
||||
|
||||
if (!next_nav_icon.isNull()) {
|
||||
p.drawPixmap(then_icon_x, then_icon_y, next_nav_icon);
|
||||
}
|
||||
}
|
||||
|
||||
p.restore();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ private:
|
||||
void drawE2eAlert(QPainter &p, const QRect &surface_rect, const QString &alert_alt_text = "");
|
||||
void drawCurrentSpeedSP(QPainter &p, const QRect &surface_rect);
|
||||
void drawBlinker(QPainter &p, const QRect &surface_rect);
|
||||
void drawNavigationHUD(QPainter &p, const QRect &surface_rect);
|
||||
QString getNavigationIconName(const QString &type, const QString &mod);
|
||||
|
||||
bool lead_status;
|
||||
float lead_d_rel;
|
||||
@@ -120,4 +122,13 @@ private:
|
||||
float speedCluster = 0;
|
||||
int icbm_active_counter = 0;
|
||||
bool pcmCruiseSpeed = true;
|
||||
|
||||
bool navigationValid;
|
||||
QString navigationStreet;
|
||||
QString navigationDistance;
|
||||
QString navigationModifier;
|
||||
QString navigationManeuverType;
|
||||
QString navigationNextModifier;
|
||||
QString navigationNextManeuverType;
|
||||
bool navigationHasNext;
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ UIStateSP::UIStateSP(QObject *parent) : UIState(parent) {
|
||||
"wideRoadCameraState", "managerState", "selfdriveState", "longitudinalPlan",
|
||||
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP",
|
||||
"carControl", "gpsLocationExternal", "gpsLocation", "liveTorqueParameters",
|
||||
"carStateSP", "liveParameters", "liveMapDataSP", "carParamsSP"
|
||||
"carStateSP", "liveParameters", "liveMapDataSP", "carParamsSP", "navigationd"
|
||||
});
|
||||
|
||||
// update timer
|
||||
|
||||
6
sunnypilot/navd/README.md
Normal file
6
sunnypilot/navd/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Navigation
|
||||
|
||||
Navigation daemon with Mapbox integration for semi-offline navigation. This module handles route planning, geocoding, and turn-by-turn instructions to support autonomous driving features.
|
||||
|
||||
- `navigation_helpers/`: Mapbox API integration and navigation instructions processing.
|
||||
- `navigationd`: Navigation service which uses mapbox integration to generate a route and keep it up to date. This service runs at three hz, using keep time to ensure the while loop only updates three times a second rather than every time sm updates, which in this case would be twenty hz (LLK).
|
||||
0
sunnypilot/navd/__init__.py
Normal file
0
sunnypilot/navd/__init__.py
Normal file
16
sunnypilot/navd/constants.py
Normal file
16
sunnypilot/navd/constants.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
|
||||
|
||||
class NAV_CV:
|
||||
""" These distances are expected in meters format and convert to desired format """
|
||||
SHORT_DISTANCE_METERS = 200.0
|
||||
QUARTER_MILE = 402.336
|
||||
POINT_ONE_MILE = 160.9344
|
||||
METERS_TO_KILO = 1000 # divide n by this
|
||||
METERS_TO_MILE = 1609.344 # divide n by this
|
||||
METERS_TO_FEET = 3.280839895 # multiply n by this
|
||||
@@ -72,6 +72,15 @@ class Coordinate:
|
||||
return x * EARTH_MEAN_RADIUS
|
||||
|
||||
|
||||
def bearing_between_two_points(point_one: Coordinate, point_two: Coordinate) -> float:
|
||||
dlon = math.radians(point_two.longitude - point_one.longitude)
|
||||
bearing_radians = math.atan2(math.sin(dlon)* math.cos(point_two.latitude), math.cos(point_one.latitude) * math.sin(point_two.latitude) -
|
||||
math.sin(point_one.latitude) * math.cos(point_two.latitude) * math.cos(dlon))
|
||||
bearing_degrees = math.degrees(bearing_radians)
|
||||
bearing_normalized = (bearing_degrees + 360) % 360
|
||||
return bearing_normalized
|
||||
|
||||
|
||||
def minimum_distance(a: Coordinate, b: Coordinate, p: Coordinate):
|
||||
if a.distance_to(b) < 0.01:
|
||||
return a.distance_to(p)
|
||||
@@ -126,6 +135,8 @@ def string_to_direction(direction: str) -> str:
|
||||
if d in direction:
|
||||
if 'slight' in direction and d in MODIFIABLE_DIRECTIONS:
|
||||
return 'slight' + d.capitalize()
|
||||
elif 'sharp' in direction and d in MODIFIABLE_DIRECTIONS:
|
||||
return 'sharp' + d.capitalize()
|
||||
return d
|
||||
return 'none'
|
||||
|
||||
|
||||
0
sunnypilot/navd/navigation_desires/__init__.py
Normal file
0
sunnypilot/navd/navigation_desires/__init__.py
Normal file
42
sunnypilot/navd/navigation_desires/navigation_desires.py
Normal file
42
sunnypilot/navd/navigation_desires/navigation_desires.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
import cereal.messaging as messaging
|
||||
from cereal import car, log
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.params import Params
|
||||
|
||||
|
||||
class NavigationDesires:
|
||||
def __init__(self):
|
||||
self.sm = messaging.SubMaster(['navigationd'])
|
||||
self.desire = log.Desire.none
|
||||
self._turn_speed_limit = 20 * CV.MPH_TO_MS
|
||||
self._params = Params()
|
||||
self.param_counter = -1
|
||||
self.nav_allowed: bool = False
|
||||
|
||||
def update_params(self):
|
||||
self.param_counter += 1
|
||||
if self.param_counter % 60 == 0: # every 3 seconds at 20hz
|
||||
self.nav_allowed = self._params.get("NavDesiresAllowed", return_default=True)
|
||||
|
||||
def update(self, CS: car.CarState, lateral_active: bool) -> log.Desire:
|
||||
self.update_params()
|
||||
self.sm.update(0)
|
||||
nav_msg = self.sm['navigationd']
|
||||
self.desire = log.Desire.none
|
||||
if self.nav_allowed and nav_msg.valid and lateral_active:
|
||||
upcoming = nav_msg.upcomingTurn
|
||||
if upcoming == 'slightLeft' and not CS.rightBlinker and not CS.leftBlindspot and CS.steeringPressed and CS.steeringTorque > 0:
|
||||
self.desire = log.Desire.keepLeft
|
||||
elif upcoming == 'slightRight' and not CS.leftBlinker and not CS.rightBlindspot and CS.steeringPressed and CS.steeringTorque < 0:
|
||||
self.desire = log.Desire.keepRight
|
||||
elif upcoming == 'left' and not CS.rightBlinker and not CS.leftBlindspot and CS.vEgo < self._turn_speed_limit:
|
||||
self.desire = log.Desire.turnLeft
|
||||
elif upcoming == 'right' and not CS.leftBlinker and not CS.rightBlindspot and CS.vEgo < self._turn_speed_limit:
|
||||
self.desire = log.Desire.turnRight
|
||||
return self.desire
|
||||
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
import pytest
|
||||
import types
|
||||
|
||||
from cereal import log
|
||||
from openpilot.common.params import Params
|
||||
|
||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
||||
from openpilot.sunnypilot.navd.navigation_desires.navigation_desires import NavigationDesires
|
||||
|
||||
|
||||
def make_car(vEgo=0, leftBlinker=False, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, steeringPressed=False, steeringTorque=0):
|
||||
return types.SimpleNamespace(
|
||||
vEgo=vEgo, leftBlinker=leftBlinker, rightBlinker=rightBlinker,
|
||||
leftBlindspot=leftBlindspot, rightBlindspot=rightBlindspot,
|
||||
steeringPressed=steeringPressed, steeringTorque=steeringTorque
|
||||
)
|
||||
|
||||
NAVIGATION_PARAMS: list[tuple] = [
|
||||
('slightLeft', make_car(steeringPressed=True, steeringTorque=1), log.Desire.keepLeft),
|
||||
('slightRight', make_car(steeringPressed=True, steeringTorque=-1), log.Desire.keepRight),
|
||||
('slightLeft', make_car(vEgo=9, leftBlindspot=True), log.Desire.none),
|
||||
('slightRight', make_car(vEgo=9, rightBlindspot=True), log.Desire.none),
|
||||
('left', make_car(vEgo=5, leftBlinker=True, rightBlinker=False, leftBlindspot=False), log.Desire.turnLeft),
|
||||
('left', make_car(vEgo=5, leftBlinker=False, rightBlinker=True), log.Desire.none),
|
||||
('right', make_car(vEgo=6, rightBlinker=True, leftBlindspot=False), log.Desire.turnRight),
|
||||
('right', make_car(vEgo=6, rightBlinker=True, rightBlindspot=True), log.Desire.none),
|
||||
('left', make_car(vEgo=9, leftBlinker=True), log.Desire.none),
|
||||
]
|
||||
|
||||
INTEGRATION_PARAMS: list[tuple] = [(carstate, upcoming, log.Desire.none, expected) for upcoming, carstate, expected in NAVIGATION_PARAMS] + [
|
||||
(make_car(vEgo=6, leftBlinker=True, steeringPressed=True, steeringTorque=1), 'slightLeft', log.Desire.turnLeft, log.Desire.keepLeft),
|
||||
(make_car(vEgo=5, rightBlinker=True, steeringPressed=True, steeringTorque=-1), 'slightRight', log.Desire.turnRight, log.Desire.keepRight),
|
||||
(make_car(vEgo=9, leftBlinker=True), 'slightLeft', log.Desire.laneChangeLeft, log.Desire.laneChangeLeft),
|
||||
(make_car(vEgo=9, rightBlinker=True), 'slightRight', log.Desire.laneChangeRight, log.Desire.laneChangeRight),
|
||||
(make_car(vEgo=9), 'none', log.Desire.none, log.Desire.none),
|
||||
]
|
||||
|
||||
def make_nav_msg(valid=False, upcoming='none'):
|
||||
return types.SimpleNamespace(valid=valid, upcomingTurn=upcoming)
|
||||
|
||||
def params_setter(allowed: bool):
|
||||
params = Params()
|
||||
params.put("NavDesiresAllowed", allowed)
|
||||
|
||||
@pytest.fixture
|
||||
def mock_submaster(mocker):
|
||||
mock_sm = mocker.patch('cereal.messaging.SubMaster')
|
||||
mock_sm_instance = mocker.Mock()
|
||||
mock_sm.return_value = mock_sm_instance
|
||||
mock_sm_instance.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=False))
|
||||
params_setter(True)
|
||||
return mock_sm_instance
|
||||
|
||||
@pytest.mark.parametrize("upcoming, carstate, expected", NAVIGATION_PARAMS)
|
||||
def test_navigation_desires_update(mock_submaster, mocker, upcoming, carstate, expected):
|
||||
nav_desires = NavigationDesires()
|
||||
nav_desires.sm.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=True, upcoming=upcoming))
|
||||
nav_desires.update(carstate, True)
|
||||
assert nav_desires.desire == expected
|
||||
|
||||
@pytest.mark.parametrize("msg_valid,lateral_active", [(False, True), (True, False)])
|
||||
def test_invalid_or_inactive(mock_submaster, mocker, msg_valid, lateral_active):
|
||||
nav_desires = NavigationDesires()
|
||||
nav_desires.sm.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=msg_valid, upcoming='slightLeft'))
|
||||
nav_desires.update(make_car(), lateral_active)
|
||||
assert nav_desires.desire == log.Desire.none
|
||||
|
||||
def test_update(mock_submaster, mocker):
|
||||
mock_submaster.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=True, upcoming='left'))
|
||||
nav_desires = NavigationDesires()
|
||||
|
||||
assert nav_desires.update(make_car(leftBlinker=True, steeringPressed=True, steeringTorque=1), True) == log.Desire.turnLeft
|
||||
|
||||
params_setter(False)
|
||||
nav_desires.param_counter = 59
|
||||
nav_desires.update(make_car(leftBlinker=True), True)
|
||||
assert nav_desires.desire == log.Desire.none
|
||||
|
||||
@pytest.mark.parametrize("carstate, upcoming, current_desire, expected", INTEGRATION_PARAMS)
|
||||
def test_desire_helper(mock_submaster, mocker, carstate, upcoming, current_desire, expected):
|
||||
mock_submaster.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=True, upcoming=upcoming))
|
||||
dh = DesireHelper()
|
||||
dh.desire = current_desire
|
||||
|
||||
if current_desire in (log.Desire.laneChangeLeft, log.Desire.laneChangeRight):
|
||||
dh.lane_change_state = log.LaneChangeState.laneChangeStarting
|
||||
dh.lane_change_direction = log.LaneChangeDirection.left if current_desire == log.Desire.laneChangeLeft else log.LaneChangeDirection.right
|
||||
|
||||
dh.update(carstate, True, 1.0)
|
||||
assert dh.desire == expected
|
||||
0
sunnypilot/navd/navigation_helpers/__init__.py
Normal file
0
sunnypilot/navd/navigation_helpers/__init__.py
Normal file
113
sunnypilot/navd/navigation_helpers/mapbox_integration.py
Normal file
113
sunnypilot/navd/navigation_helpers/mapbox_integration.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
import requests
|
||||
from urllib.parse import quote
|
||||
|
||||
from openpilot.common.params import Params
|
||||
|
||||
|
||||
class MapboxIntegration:
|
||||
def __init__(self):
|
||||
self.params = Params()
|
||||
|
||||
def get_public_token(self) -> str:
|
||||
token: str = self.params.get('MapboxToken', return_default=True)
|
||||
return token
|
||||
|
||||
def set_destination(self, postvars, current_lon, current_lat, bearing=None) -> tuple[dict, bool]:
|
||||
if 'latitude' in postvars and 'longitude' in postvars:
|
||||
self.nav_confirmed(postvars, current_lon, current_lat, bearing)
|
||||
return postvars, True
|
||||
|
||||
addr = postvars['place_name']
|
||||
if not addr:
|
||||
return postvars, False
|
||||
|
||||
token = self.get_public_token()
|
||||
url = f'https://api.mapbox.com/geocoding/v5/mapbox.places/{quote(addr)}.json?access_token={token}&limit=1&proximity={current_lon},{current_lat}'
|
||||
try:
|
||||
response = requests.get(url, timeout=5)
|
||||
if response.status_code == 200:
|
||||
features = response.json()['features']
|
||||
if features:
|
||||
longitude, latitude = features[0]['geometry']['coordinates']
|
||||
postvars.update({'latitude': latitude, 'longitude': longitude, 'name': addr})
|
||||
self.nav_confirmed(postvars, current_lon, current_lat, bearing)
|
||||
return postvars, True
|
||||
except requests.RequestException:
|
||||
pass # Broad exception to handle network errors like no internet without crashing navd process.
|
||||
return postvars, False
|
||||
|
||||
def nav_confirmed(self, postvars, start_lon, start_lat, bearing=None) -> None:
|
||||
if not postvars:
|
||||
return
|
||||
|
||||
latitude = float(postvars['latitude'])
|
||||
longitude = float(postvars['longitude'])
|
||||
|
||||
data: dict = {'navData': {'current': {'latitude': latitude, 'longitude': longitude}, 'route': {}}}
|
||||
|
||||
token = self.get_public_token()
|
||||
route_data = self.generate_route(start_lon, start_lat, longitude, latitude, token, bearing)
|
||||
if route_data:
|
||||
data['navData']['route'] = route_data
|
||||
self.params.put('MapboxSettings', data)
|
||||
|
||||
@staticmethod
|
||||
def generate_route(start_lon, start_lat, end_lon, end_lat, token, bearing=None) -> dict | None:
|
||||
if not token:
|
||||
return None
|
||||
|
||||
params = {
|
||||
'access_token': token,
|
||||
'geometries': 'geojson',
|
||||
'steps': 'true',
|
||||
'overview': 'full',
|
||||
'annotations': 'maxspeed',
|
||||
'alternatives': 'false',
|
||||
'banner_instructions': 'true',
|
||||
}
|
||||
if bearing is not None:
|
||||
params['bearings'] = f'{int((bearing + 360) % 360):.0f},90;'
|
||||
|
||||
try:
|
||||
response = requests.get(f'https://api.mapbox.com/directions/v5/mapbox/driving/{start_lon},{start_lat};{end_lon},{end_lat}', params=params, timeout=5)
|
||||
data = response.json() if response.status_code == 200 else {}
|
||||
except requests.RequestException:
|
||||
return None
|
||||
|
||||
routes = data['routes'] if data else None
|
||||
legs = routes[0]['legs'] if routes else None
|
||||
|
||||
if data.get('code') != 'Ok' or not routes or not legs:
|
||||
return None
|
||||
|
||||
route = routes[0]
|
||||
leg = legs[0]
|
||||
|
||||
steps = [
|
||||
{
|
||||
'maneuver': step['maneuver']['type'],
|
||||
'instruction': step['maneuver']['instruction'],
|
||||
'distance': step['distance'],
|
||||
'duration': step['duration'],
|
||||
'location': {'longitude': step['maneuver']['location'][0], 'latitude': step['maneuver']['location'][1]},
|
||||
'modifier': step['maneuver'].get('modifier', 'none'),
|
||||
'bannerInstructions': step['bannerInstructions'],
|
||||
}
|
||||
for step in leg['steps']
|
||||
]
|
||||
|
||||
maxspeed = [{'speed': item['speed'], 'unit': item['unit']} for item in leg['annotation']['maxspeed'] if 'speed' in item]
|
||||
|
||||
return {
|
||||
'steps': steps,
|
||||
'totalDistance': route['distance'],
|
||||
'totalDuration': route['duration'],
|
||||
'geometry': [{'longitude': coord[0], 'latitude': coord[1]} for coord in route['geometry']['coordinates']],
|
||||
'maxspeed': maxspeed,
|
||||
}
|
||||
153
sunnypilot/navd/navigation_helpers/nav_instructions.py
Normal file
153
sunnypilot/navd/navigation_helpers/nav_instructions.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
from numpy import interp
|
||||
|
||||
from openpilot.common.params import Params
|
||||
|
||||
from openpilot.sunnypilot.navd.helpers import Coordinate, bearing_between_two_points, string_to_direction, distance_along_geometry
|
||||
|
||||
|
||||
class NavigationInstructions:
|
||||
def __init__(self):
|
||||
self.coord = Coordinate(0, 0)
|
||||
self.params = Params()
|
||||
|
||||
self._cached_route = None
|
||||
self._route_loaded = False
|
||||
self._no_route = False
|
||||
|
||||
self.closest_idx: float = 0
|
||||
self.min_distance: float = 0
|
||||
|
||||
def get_route_progress(self, current_lat, current_lon) -> dict | None:
|
||||
route = self.get_current_route()
|
||||
if not route or not route['geometry'] or not route['steps']:
|
||||
return None
|
||||
|
||||
self.coord.latitude = current_lat
|
||||
self.coord.longitude = current_lon
|
||||
|
||||
# Find the closest point on the route relative to self
|
||||
self.closest_idx, self.min_distance = min(((idx, self.coord.distance_to(coord)) for idx, coord in enumerate(route['geometry'])), key=lambda x: x[1])
|
||||
closest_cumulative = distance_along_geometry(route['geometry'], self.coord)
|
||||
|
||||
# Find the current step index, which is the HIGHEST idx where the step location cumulative less/equal closest cumulative
|
||||
current_step_idx = max((idx for idx, step in enumerate(route['steps']) if step['cumulative_distance'] <= closest_cumulative), default=-1)
|
||||
current_step = route['steps'][current_step_idx if current_step_idx >= 0 else 0]
|
||||
|
||||
# The next turn is the next step relative to our cumulative index
|
||||
next_turn_idx = current_step_idx + 1
|
||||
next_turn = route['steps'][next_turn_idx] if 0 <= next_turn_idx < len(route['steps']) else None
|
||||
|
||||
current_maxspeed = current_step['maxspeed']
|
||||
|
||||
distance_to_end_of_step = max(0, current_step['distance'] - (closest_cumulative - current_step['cumulative_distance']))
|
||||
|
||||
all_maneuvers: list = []
|
||||
max_maneuvers = 3
|
||||
for idx in range(current_step_idx, min(current_step_idx + max_maneuvers, len(route['steps']))):
|
||||
step = route['steps'][idx]
|
||||
if idx == current_step_idx:
|
||||
distance = distance_to_end_of_step
|
||||
else:
|
||||
distance = step['cumulative_distance'] - closest_cumulative
|
||||
all_maneuvers.append({'distance': distance, 'type': step['maneuver'], 'modifier': step['modifier'], 'instruction': step['instruction']})
|
||||
|
||||
return {
|
||||
'distance_from_route': self.min_distance,
|
||||
'current_step': current_step,
|
||||
'next_turn': next_turn,
|
||||
'current_maxspeed': current_maxspeed,
|
||||
'all_maneuvers': all_maneuvers,
|
||||
'current_step_idx': current_step_idx,
|
||||
'distance_to_end_of_step': distance_to_end_of_step,
|
||||
}
|
||||
|
||||
def get_current_route(self):
|
||||
if self._route_loaded and self._cached_route is not None:
|
||||
return self._cached_route
|
||||
if self._no_route:
|
||||
return None
|
||||
|
||||
param_value = self.params.get('MapboxSettings')
|
||||
route = param_value['navData']['route'] if param_value else None
|
||||
if not route:
|
||||
self._no_route = True
|
||||
return None
|
||||
|
||||
geometry = [Coordinate(coord['latitude'], coord['longitude']) for coord in route['geometry']]
|
||||
cumulative_distances = [0.0]
|
||||
cumulative_distances.extend(cumulative_distances[-1] + geometry[step - 1].distance_to(geometry[step]) for step in range(1, len(geometry)))
|
||||
maxspeed = [(speed['speed'], speed['unit']) for speed in route['maxspeed']]
|
||||
steps = []
|
||||
for step in route['steps']:
|
||||
location = Coordinate(step['location']['latitude'], step['location']['longitude'])
|
||||
closest_idx = min(range(len(geometry)), key=lambda i: location.distance_to(geometry[i]))
|
||||
steps.append({
|
||||
'bannerInstructions': step['bannerInstructions'],
|
||||
'distance': step['distance'],
|
||||
'duration': step['duration'],
|
||||
'maneuver': step['maneuver'],
|
||||
'location': location,
|
||||
'cumulative_distance': cumulative_distances[closest_idx],
|
||||
'maxspeed': maxspeed[min(closest_idx, len(maxspeed) - 1)] if len(maxspeed) > 0 else (0, 'kmh'),
|
||||
'modifier': string_to_direction(step['modifier']),
|
||||
'instruction': step['instruction'],
|
||||
})
|
||||
self._cached_route = {
|
||||
'bearings': [bearing_between_two_points(geometry[i], geometry[i+2]) for i in range(len(geometry)-2)],
|
||||
'steps': steps,
|
||||
'total_distance': route['totalDistance'],
|
||||
'total_duration': route['totalDuration'],
|
||||
'geometry': geometry,
|
||||
'cumulative_distances': cumulative_distances,
|
||||
'maxspeed': maxspeed,
|
||||
}
|
||||
self._route_loaded = True
|
||||
return self._cached_route
|
||||
|
||||
def clear_route_cache(self):
|
||||
self._cached_route = None
|
||||
self._route_loaded = False
|
||||
self._no_route = False
|
||||
|
||||
def route_bearing_misalign(self, route, bearing, v_ego) -> bool:
|
||||
route_bearing_misalign:bool = False
|
||||
|
||||
if v_ego < 5.0:
|
||||
route_bearing_misalign = False
|
||||
elif self.closest_idx > 0 and self.closest_idx < len(route['geometry']) -1:
|
||||
route_bearing = route['bearings'][self.closest_idx -1]
|
||||
current_bearing_normalized = (bearing + 360) % 360
|
||||
bearing_difference = abs(current_bearing_normalized - route_bearing)
|
||||
|
||||
if min(bearing_difference, 360 - bearing_difference) > 110:
|
||||
route_bearing_misalign = True # flag for recompute/cancellation
|
||||
return route_bearing_misalign
|
||||
|
||||
def get_upcoming_turn_from_progress(self, progress, current_lat, current_lon, v_ego: float) -> str:
|
||||
if progress and progress['next_turn']:
|
||||
speed_breakpoints: list = [0, 5, 10, 15, 20, 25, 30, 35, 40]
|
||||
distance_breakpoints: list = [20, 25, 30, 45, 60, 75, 90, 105, 120]
|
||||
distance_interp = interp(v_ego, speed_breakpoints, distance_breakpoints)
|
||||
|
||||
self.coord.latitude = current_lat
|
||||
self.coord.longitude = current_lon
|
||||
distance = self.coord.distance_to(progress['next_turn']['location'])
|
||||
|
||||
if distance <= distance_interp:
|
||||
modifier = progress['next_turn']['modifier']
|
||||
return str(modifier)
|
||||
return 'none'
|
||||
|
||||
@staticmethod
|
||||
def arrived_at_destination(progress, v_ego) -> bool:
|
||||
if v_ego < 1.0:
|
||||
maneuvers = progress['all_maneuvers'][0]
|
||||
if maneuvers['type'] == 'arrive' or maneuvers['instruction'].startswith('Your destination'):
|
||||
return True
|
||||
return False
|
||||
98
sunnypilot/navd/navigation_helpers/tests/test_mapbox.py
Normal file
98
sunnypilot/navd/navigation_helpers/tests/test_mapbox.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
import os
|
||||
|
||||
from openpilot.common.constants import CV
|
||||
|
||||
from openpilot.sunnypilot.navd.navigation_helpers.mapbox_integration import MapboxIntegration
|
||||
from openpilot.sunnypilot.navd.navigation_helpers.nav_instructions import NavigationInstructions
|
||||
|
||||
|
||||
class TestMapbox:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.mapbox = MapboxIntegration()
|
||||
cls.nav = NavigationInstructions()
|
||||
|
||||
token = os.environ.get('MAPBOX_TOKEN_CI')
|
||||
if token:
|
||||
cls.mapbox.params.put('MapboxToken', token)
|
||||
|
||||
# route setup
|
||||
cls.current_lon, cls.current_lat = -119.17557, 34.23305
|
||||
cls.mapbox.params.put('MapboxRoute', '740 E Ventura Blvd. Camarillo, CA')
|
||||
cls.postvars = {"place_name": cls.mapbox.params.get('MapboxRoute')}
|
||||
cls.postvars, cls.valid_addr = cls.mapbox.set_destination(cls.postvars, cls.current_lon, cls.current_lat)
|
||||
cls.route = cls.nav.get_current_route()
|
||||
cls.progress = cls.nav.get_route_progress(cls.current_lat, cls.current_lon)
|
||||
|
||||
def test_set_destination(self):
|
||||
assert self.valid_addr
|
||||
settings = self.mapbox.params.get('MapboxSettings')
|
||||
assert settings is not None
|
||||
dest_lat = settings['navData']['current']['latitude']
|
||||
dest_lon = settings['navData']['current']['longitude']
|
||||
assert dest_lat == self.postvars["latitude"] and dest_lon == self.postvars["longitude"]
|
||||
|
||||
def test_get_route(self):
|
||||
assert self.route is not None
|
||||
assert 'steps' in self.route
|
||||
assert 'geometry' in self.route
|
||||
assert 'maxspeed' in self.route
|
||||
assert 'total_distance' in self.route
|
||||
assert 'total_duration' in self.route
|
||||
assert len(self.route['steps']) > 0
|
||||
assert len(self.route['geometry']) > 0
|
||||
assert len(self.route['maxspeed']) > 0
|
||||
|
||||
if self.route and 'steps' in self.route:
|
||||
for step in self.route['steps']:
|
||||
assert 'modifier' in step
|
||||
|
||||
def test_upcoming_turn_detection(self):
|
||||
upcoming = self.nav.get_upcoming_turn_from_progress(self.progress, self.current_lat, self.current_lon, v_ego=40.0)
|
||||
assert isinstance(upcoming, str)
|
||||
assert upcoming == 'none'
|
||||
|
||||
if self.route['steps']:
|
||||
turn_lat = self.route['steps'][1]['location'].latitude
|
||||
turn_lon = self.route['steps'][1]['location'].longitude
|
||||
close_lat = turn_lat - 0.000175 # slightly before the turn
|
||||
if self.progress and self.progress.get('next_turn'):
|
||||
expected_turn = self.progress['next_turn']['modifier']
|
||||
upcoming_close = self.nav.get_upcoming_turn_from_progress(self.progress, close_lat, turn_lon, v_ego=0.0)
|
||||
if expected_turn:
|
||||
assert upcoming_close == expected_turn == 'right', "Should be a right turn upcoming"
|
||||
|
||||
def test_route_progress_tracking(self):
|
||||
assert self.progress is not None
|
||||
assert 'distance_from_route' in self.progress
|
||||
assert 'next_turn' in self.progress
|
||||
assert 'current_maxspeed' in self.progress
|
||||
assert 'all_maneuvers' in self.progress
|
||||
assert 'distance_to_end_of_step' in self.progress
|
||||
assert self.progress['distance_from_route'] >= 0
|
||||
assert isinstance(self.progress['all_maneuvers'], list)
|
||||
|
||||
def test_speed_limit_handling(self):
|
||||
speed_limit_metric = self.progress['current_maxspeed'][0]
|
||||
speed_limit_imperial = (round(speed_limit_metric * CV.KPH_TO_MPH))
|
||||
assert isinstance(speed_limit_metric, int)
|
||||
assert isinstance(speed_limit_imperial, int)
|
||||
|
||||
def test_arrival_detection(self):
|
||||
is_arrived = self.nav.arrived_at_destination(self.progress, 2.0)
|
||||
assert isinstance(is_arrived, bool)
|
||||
assert not is_arrived
|
||||
|
||||
def test_bearing_misalign(self):
|
||||
lat = self.route['steps'][1]['location'].latitude
|
||||
lon = self.route['steps'][1]['location'].longitude
|
||||
self.nav.get_route_progress(lat, lon)
|
||||
route_bearing_misaligned = self.nav.route_bearing_misalign(self.route, 45, 5.0)
|
||||
# based on math: closest index: 7, normalized bearing: 45 route bearing: 180.5486953778888, expected differential: 135.54869538
|
||||
assert route_bearing_misaligned
|
||||
168
sunnypilot/navd/navigationd.py
Executable file
168
sunnypilot/navd/navigationd.py
Executable file
@@ -0,0 +1,168 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
from math import degrees
|
||||
from numpy import interp
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from cereal import custom
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import Ratekeeper
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
|
||||
from openpilot.sunnypilot.navd.constants import NAV_CV
|
||||
from openpilot.sunnypilot.navd.helpers import Coordinate, parse_banner_instructions
|
||||
from openpilot.sunnypilot.navd.navigation_helpers.mapbox_integration import MapboxIntegration
|
||||
from openpilot.sunnypilot.navd.navigation_helpers.nav_instructions import NavigationInstructions
|
||||
|
||||
|
||||
class Navigationd:
|
||||
def __init__(self):
|
||||
self.params = Params()
|
||||
self.mapbox = MapboxIntegration()
|
||||
self.nav_instructions = NavigationInstructions()
|
||||
|
||||
self.sm = messaging.SubMaster(['carControlSP', 'liveLocationKalman'])
|
||||
self.pm = messaging.PubMaster(['navigationd'])
|
||||
self.rk = Ratekeeper(3) # 3 Hz
|
||||
|
||||
self.route = None
|
||||
self.destination: str | None = None
|
||||
self.new_destination: str = ''
|
||||
|
||||
self.allow_navigation: bool = False
|
||||
self.recompute_allowed: bool = False
|
||||
self.allow_recompute: bool = False
|
||||
self.reroute_counter: int = 0
|
||||
self.cancel_route_counter: int = 0
|
||||
|
||||
self.frame: int = -1
|
||||
self.last_position: Coordinate | None = None
|
||||
self.last_bearing: float | None = None
|
||||
self.valid: bool = False
|
||||
|
||||
def _update_params(self):
|
||||
if self.last_position is not None:
|
||||
self.frame += 1
|
||||
if self.frame % 15 == 0:
|
||||
self.allow_navigation = self.params.get('AllowNavigation', return_default=True)
|
||||
self.new_destination = self.params.get('MapboxRoute')
|
||||
self.recompute_allowed = self.params.get('MapboxRecompute', return_default=True)
|
||||
|
||||
self.allow_recompute: bool = (self.new_destination != self.destination and self.new_destination != '') or (
|
||||
self.recompute_allowed and self.reroute_counter > 9 and self.route)
|
||||
|
||||
if self.allow_recompute:
|
||||
postvars = {'place_name': self.new_destination}
|
||||
postvars, valid_addr = self.mapbox.set_destination(postvars, self.last_position.longitude, self.last_position.latitude, self.last_bearing)
|
||||
|
||||
if valid_addr:
|
||||
self.destination = self.new_destination
|
||||
self.nav_instructions.clear_route_cache()
|
||||
self.route = self.nav_instructions.get_current_route()
|
||||
self.cancel_route_counter = 0
|
||||
self.reroute_counter = 0
|
||||
|
||||
if self.cancel_route_counter == 30:
|
||||
self.cancel_route_counter = 0
|
||||
self.params.put_nonblocking("MapboxRoute", "")
|
||||
self.nav_instructions.clear_route_cache()
|
||||
self.route = None
|
||||
|
||||
self.valid = self.route is not None
|
||||
|
||||
def _update_navigation(self) -> tuple[str, dict | None, dict]:
|
||||
banner_instructions: str = ''
|
||||
nav_data: dict = {}
|
||||
if self.allow_navigation and self.route and self.last_position is not None:
|
||||
if progress := self.nav_instructions.get_route_progress(self.last_position.latitude, self.last_position.longitude):
|
||||
v_ego = float(max(self.sm['carControlSP'].speed, 0.0))
|
||||
nav_data['upcoming_turn'] = self.nav_instructions.get_upcoming_turn_from_progress(progress, self.last_position.latitude,
|
||||
self.last_position.longitude, v_ego)
|
||||
speed_limit, _ = progress['current_maxspeed']
|
||||
nav_data['current_speed_limit'] = speed_limit
|
||||
arrived = self.nav_instructions.arrived_at_destination(progress, v_ego)
|
||||
|
||||
if progress['current_step']:
|
||||
parsed = parse_banner_instructions(progress['current_step']['bannerInstructions'], progress['distance_to_end_of_step'])
|
||||
if parsed:
|
||||
banner_instructions = parsed['maneuverPrimaryText']
|
||||
|
||||
nav_data['distance_from_route'] = progress['distance_from_route']
|
||||
speed_breakpoints: list = [0.0, 5.0, 10.0, 20.0, 40.0]
|
||||
distance_list: list = [100.0, 125.0, 150.0, 200.0, 250.0]
|
||||
large_distance: bool = progress['distance_from_route'] > float(interp(v_ego, speed_breakpoints, distance_list))
|
||||
|
||||
route_bearing_misalign: bool = self.nav_instructions.route_bearing_misalign(self.route, self.last_bearing, v_ego)
|
||||
|
||||
if large_distance and not arrived:
|
||||
self.cancel_route_counter = self.cancel_route_counter + 1 if progress['distance_from_route'] > NAV_CV.QUARTER_MILE else 0
|
||||
if self.recompute_allowed:
|
||||
self.reroute_counter += 1
|
||||
elif arrived:
|
||||
self.cancel_route_counter += 1
|
||||
self.recompute_allowed = False
|
||||
elif route_bearing_misalign:
|
||||
self.cancel_route_counter += 1
|
||||
if self.recompute_allowed:
|
||||
self.reroute_counter += 1
|
||||
else:
|
||||
self.cancel_route_counter = 0
|
||||
self.reroute_counter = 0
|
||||
|
||||
# Don't recompute in last segment to prevent reroute loops
|
||||
if progress['current_step_idx'] == len(self.route['steps']) - 1:
|
||||
self.recompute_allowed = False
|
||||
self.allow_navigation = False
|
||||
else:
|
||||
banner_instructions = ''
|
||||
progress = None
|
||||
nav_data = {}
|
||||
|
||||
return banner_instructions, progress, nav_data
|
||||
|
||||
def _build_navigation_message(self, banner_instructions: str, progress: dict | None, nav_data: dict, valid: bool):
|
||||
msg = messaging.new_message('navigationd')
|
||||
msg.valid = valid
|
||||
msg.navigationd.upcomingTurn = nav_data.get('upcoming_turn', 'none')
|
||||
msg.navigationd.currentSpeedLimit = nav_data.get('current_speed_limit', 0)
|
||||
msg.navigationd.bannerInstructions = banner_instructions
|
||||
msg.navigationd.distanceFromRoute = nav_data.get('distance_from_route', 0.0)
|
||||
msg.navigationd.valid = self.valid
|
||||
|
||||
all_maneuvers = (
|
||||
[custom.Navigationd.Maneuver.new_message(distance=m['distance'], type=m['type'], modifier=m['modifier'],
|
||||
instruction=m['instruction']) for m in progress['all_maneuvers']]
|
||||
if progress
|
||||
else []
|
||||
)
|
||||
msg.navigationd.allManeuvers = all_maneuvers
|
||||
return msg
|
||||
|
||||
def run(self):
|
||||
cloudlog.warning('navigationd init')
|
||||
|
||||
while True:
|
||||
self.sm.update(0)
|
||||
location = self.sm['liveLocationKalman']
|
||||
localizer_valid = location.positionGeodetic.valid if location else False
|
||||
|
||||
if localizer_valid:
|
||||
self.last_bearing = degrees(location.calibratedOrientationNED.value[2])
|
||||
self.last_position = Coordinate(location.positionGeodetic.value[0], location.positionGeodetic.value[1])
|
||||
|
||||
self._update_params()
|
||||
banner_instructions, progress, nav_data = self._update_navigation()
|
||||
|
||||
msg = self._build_navigation_message(banner_instructions, progress, nav_data, valid=localizer_valid)
|
||||
|
||||
self.pm.send('navigationd', msg)
|
||||
self.rk.keep_time()
|
||||
|
||||
|
||||
def main():
|
||||
nav = Navigationd()
|
||||
nav.run()
|
||||
0
sunnypilot/navd/tests/__init__.py
Normal file
0
sunnypilot/navd/tests/__init__.py
Normal file
67
sunnypilot/navd/tests/test_navigationd.py
Normal file
67
sunnypilot/navd/tests/test_navigationd.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
import platform
|
||||
import pytest
|
||||
|
||||
import cereal.messaging as messaging
|
||||
|
||||
from openpilot.sunnypilot.navd.navigationd import Navigationd
|
||||
from openpilot.sunnypilot.navd.helpers import Coordinate
|
||||
|
||||
|
||||
class TestNavigationd:
|
||||
is_darwin = platform.system() == "Darwin"
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_method(self, mocker):
|
||||
if self.is_darwin:
|
||||
mocker.patch('cereal.messaging.SubMaster')
|
||||
mocker.patch('cereal.messaging.PubMaster')
|
||||
|
||||
def test_update_params(self):
|
||||
nav = Navigationd()
|
||||
nav.last_position = None
|
||||
nav._update_params()
|
||||
assert nav.frame == -1
|
||||
nav.last_position = Coordinate(latitude=37.0, longitude=128.0)
|
||||
nav._update_params()
|
||||
assert nav.frame == 0 # frame only updates when last position is set
|
||||
|
||||
def test_update_navigation_no_position(self):
|
||||
nav = Navigationd()
|
||||
nav.last_position = None
|
||||
banner, progress, nav_data = nav._update_navigation()
|
||||
assert banner == ''
|
||||
assert progress is None
|
||||
assert nav_data == {}
|
||||
|
||||
def test_update_navigation(self):
|
||||
nav = Navigationd()
|
||||
nav.last_position = Coordinate(latitude=37.0, longitude=128.0)
|
||||
nav.route = {'580 Winchester dr, oxnard, CA': True}
|
||||
banner, progress, nav_data = nav._update_navigation()
|
||||
assert isinstance(banner, str)
|
||||
assert not progress # no route was actually set
|
||||
assert isinstance(nav_data, dict)
|
||||
|
||||
def test_build_navigation_message(self):
|
||||
if self.is_darwin:
|
||||
nav = Navigationd()
|
||||
msg = nav._build_navigation_message('', None, {}, True)
|
||||
assert msg.navigationd.bannerInstructions == ''
|
||||
assert msg.navigationd.valid is False
|
||||
else:
|
||||
sm = messaging.SubMaster(['navigationd'])
|
||||
nav = Navigationd()
|
||||
msg = nav._build_navigation_message('', None, {}, True)
|
||||
|
||||
nav.pm.send('navigationd', msg)
|
||||
sm.update()
|
||||
received_msg = sm['navigationd']
|
||||
|
||||
assert received_msg.bannerInstructions == msg.navigationd.bannerInstructions
|
||||
assert received_msg.valid == msg.navigationd.valid
|
||||
BIN
sunnypilot/selfdrive/assets/navigation/direction_arrive.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_arrive.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_arrive_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_arrive_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_arrive_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_arrive_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_arrive_straight.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_arrive_straight.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_close.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_close.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue_slight_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue_slight_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue_slight_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue_slight_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue_straight.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue_straight.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue_uturn.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_continue_uturn.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_depart.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_depart.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_depart_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_depart_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_depart_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_depart_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_depart_straight.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_depart_straight.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_end_of_road_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_end_of_road_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_end_of_road_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_end_of_road_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_flag.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_flag.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_fork.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_fork.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_fork_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_fork_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_fork_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_fork_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_fork_slight_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_fork_slight_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_fork_slight_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_fork_slight_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_fork_straight.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_fork_straight.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid_slight_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid_slight_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid_slight_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid_slight_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid_straight.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid_straight.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid_uturn.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_invalid_uturn.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_merge_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_merge_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_merge_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_merge_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_merge_slight_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_merge_slight_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_merge_slight_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_merge_slight_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_merge_straight.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_merge_straight.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_sharp_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_sharp_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_sharp_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_sharp_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_slight_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_slight_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_slight_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_slight_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_straight.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_new_name_straight.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_notificaiton_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_notificaiton_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_notificaiton_sharp_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_notificaiton_sharp_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_notification_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_notification_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_notification_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_notification_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_notification_sharp_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_notification_sharp_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_notification_slight_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_notification_slight_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_notification_slight_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_notification_slight_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_notification_straight.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_notification_straight.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_off_ramp_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_off_ramp_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_off_ramp_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_off_ramp_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_off_ramp_slight_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_off_ramp_slight_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_off_ramp_slight_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_off_ramp_slight_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_sharp_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_sharp_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_sharp_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_sharp_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_slight_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_slight_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_slight_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_slight_right.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_straight.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_on_ramp_straight.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_rotary.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_rotary.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_rotary_left.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_rotary_left.png
LFS
Normal file
Binary file not shown.
BIN
sunnypilot/selfdrive/assets/navigation/direction_rotary_right.png
LFS
Normal file
BIN
sunnypilot/selfdrive/assets/navigation/direction_rotary_right.png
LFS
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user