Compare commits

..

90 Commits

Author SHA1 Message Date
royjr 669f3b7945 fix 2025-11-07 21:51:35 -05:00
royjr 63122e1a33 Merge branch 'master' into visual-style 2025-11-07 21:25:46 -05:00
royjr c9b1afb154 Merge branch 'master' into visual-style 2025-10-10 00:06:55 -04:00
royjr 6e3bd3fbed explicit radius 2025-10-09 23:56:37 -04:00
royjr 42592dd550 match what we currently send 2025-10-09 23:54:34 -04:00
royjr b2f7d72a33 no need 2025-10-09 23:53:07 -04:00
royjr 2e0ce18c84 group 2025-10-09 23:52:51 -04:00
royjr 2a9a4a9263 Merge branch 'master' into visual-style 2025-10-09 23:51:18 -04:00
royjr e63fb10fdb Revert "metric threshold"
This reverts commit b54941928d.
2025-10-08 21:35:23 -04:00
royjr b54941928d metric threshold 2025-10-08 21:35:15 -04:00
royjr b85b8ffacf better VisualStyleOverheadThreshold 2025-10-08 21:28:10 -04:00
royjr 3ff2e9b26a reorder 2025-10-08 21:25:37 -04:00
royjr 4b3ffc722a show visual_radar_tracks_delay_settings on VisualRadarTracks 2025-10-08 21:24:52 -04:00
royjr 5f49066829 more 2025-10-08 21:18:51 -04:00
royjr ab2eb218d5 clean 2025-10-08 21:16:06 -04:00
royjr 6212b174e9 descs 2025-10-08 21:12:08 -04:00
royjr a8a6e5708a Merge branch 'master' into visual-style 2025-10-08 20:47:03 -04:00
royjr 54b060f178 move with 2025-10-08 20:45:17 -04:00
royjr 8266386cd0 better 2025-10-08 20:44:06 -04:00
royjr 8bbe87ee22 simple 2025-10-08 20:40:23 -04:00
royjr d35ac0c145 a bit better 2025-10-08 20:34:26 -04:00
royjr a134ae1e29 combine 2025-10-08 20:25:55 -04:00
royjr 6a69759b9e hide options based on options 2025-10-08 20:24:51 -04:00
royjr b9063e2966 cleanup 2025-10-08 19:55:40 -04:00
royjr 4397a4387a mooooore fps 2025-10-08 19:49:14 -04:00
royjr 32dc384524 not needed for now 2025-10-08 19:47:08 -04:00
royjr 68fa239b97 unused 2025-10-08 19:45:37 -04:00
royjr c8367fbc25 more fps remove 2025-10-08 19:44:45 -04:00
royjr 76fc4514e1 reorder 2025-10-08 19:43:32 -04:00
royjr 073ce2b4df remove VisualFPS 2025-10-08 19:35:37 -04:00
royjr 5d516ba89f Merge branch 'master' into visual-style 2025-10-08 12:26:38 -04:00
royjr e819a0dcb1 Merge branch 'master' into visual-style 2025-10-08 02:14:22 -04:00
royjr ba4b583e6e fix visual_style_overhead_zoom 2025-10-08 01:55:38 -04:00
royjr 5954354356 fix visual_style_overhead 2025-10-08 01:48:06 -04:00
royjr 23ff232333 fix visual_style_overhead_settings 2025-10-08 01:40:10 -04:00
royjr 0bb47fcfa9 fix visual_style_overhead_threshold_settings 2025-10-08 01:29:15 -04:00
royjr 99a5682371 todo 2025-10-08 01:09:08 -04:00
royjr 22ca343050 fix visual_style_overhead_threshold_settings 2025-10-08 01:07:53 -04:00
royjr 0049d20151 VisualStyleZoom fix 2025-10-08 01:05:17 -04:00
royjr b9f8f4e8ac visual style better 2025-10-08 00:55:20 -04:00
royjr 26564dd42f better VisualWideCam 2025-10-08 00:07:31 -04:00
royjr ec440e4568 Merge branch 'master' into visual-style 2025-10-07 22:38:57 -04:00
royjr 69817f887b Merge branch 'master' into visual-style 2025-09-30 15:01:21 -04:00
royjr df21208b7c Merge branch 'master' into visual-style 2025-09-25 02:08:38 -04:00
royjr 660c994c5e visual_style_blend vs visual_style_overhead_blend 2025-09-25 02:08:28 -04:00
royjr 904cc796b0 prevent jitter 2025-09-25 01:44:06 -04:00
royjr 1a9a1e1b8a Merge branch 'master' into visual-style 2025-09-23 23:00:46 -04:00
royjr 2ae7078c0f use ParamWatcher 2025-09-23 14:37:21 -04:00
royjr 0fde830a30 fix params 2025-09-23 10:35:46 -04:00
royjr 7c744a42e5 safe font 2025-09-23 08:18:51 -04:00
royjr 90cc169dec VisualRadarTracksDelay 2025-09-23 08:17:59 -04:00
royjr 36c1e11a9e add FPS toggle 2025-09-23 07:47:16 -04:00
royjr 5e26a99337 better fps 2025-09-23 07:40:59 -04:00
royjr 53683cf90b constant track size 2025-09-23 07:25:00 -04:00
royjr 95dcc23887 better tracks for overhead 2025-09-23 07:21:57 -04:00
royjr cef3163c7c more more more cached params 2025-09-23 06:54:46 -04:00
royjr cbc34dd1f6 more cached params 2025-09-23 06:49:41 -04:00
royjr fbd1f6bad1 cached params 2025-09-23 06:42:14 -04:00
royjr f2ddf9abba debug ui lag 2025-09-23 04:04:52 -04:00
royjr 8a5eaf5ba6 prepare for lag compensation 2025-09-23 03:35:06 -04:00
royjr ddb377ac5b radar lag compensate 2025-09-23 03:24:15 -04:00
royjr 512a01d28c VisualWideCam toggle (untested) 2025-09-23 01:33:50 -04:00
royjr b30e275169 Merge branch 'master' into visual-style 2025-09-23 01:15:18 -04:00
royjr ee9fe5c9ed VisualRadarTracks toggle 2025-09-23 01:10:44 -04:00
royjr e672a352d3 bigger points 2025-09-23 00:24:38 -04:00
royjr 361d107040 basic radar 2025-09-22 23:55:55 -04:00
royjr 36eb047cd3 Merge branch 'master' into visual-style 2025-09-22 00:10:31 -04:00
royjr 218c6172e6 darker fills 2025-09-22 00:03:29 -04:00
royjr 8f35e4fc3c Revert "smooooth"
This reverts commit c965df39d6.
2025-09-21 23:02:48 -04:00
royjr c965df39d6 smooooth 2025-09-21 22:10:35 -04:00
royjr 53ef69f3c3 hide horizon if no data 2025-09-21 21:55:23 -04:00
royjr ea9ca18c8b horizon at end 2025-09-21 21:49:01 -04:00
royjr 225858261e better horizon 2025-09-21 21:34:14 -04:00
royjr 43de43b34a better lines 2025-09-21 21:25:39 -04:00
royjr 96ddbe35a1 dynamically adjust background 2025-09-21 15:37:54 -04:00
royjr 7d547ad533 fix default 2025-09-21 15:30:13 -04:00
royjr 13de58b845 add more speeds 2025-09-21 15:30:06 -04:00
royjr 3d8c563a4b dynamic zoom 2025-09-21 15:19:57 -04:00
royjr bc75199d5a overhead 2025-09-21 14:34:38 -04:00
royjr ba6e18ed91 only for vision 2025-09-21 14:32:30 -04:00
royjr 74dbcd699b add road edges to vision 2025-09-21 14:31:58 -04:00
royjr 35c6af0190 theme 2025-09-21 13:55:56 -04:00
royjr 827de88c8b stump top down 2025-09-21 13:03:37 -04:00
royjr 54ca4b537d VisualStyleBlendThreshold 2025-09-21 12:48:32 -04:00
royjr c61f327076 allow always overhead view 2025-09-20 13:04:07 -04:00
royjr d569913e5a VisualStyleBlend 2025-09-20 12:57:17 -04:00
royjr 418c93be06 animate overhead 2025-09-20 12:16:49 -04:00
royjr 5acc040a89 overhead 2025-09-20 11:24:24 -04:00
royjr e45b17c230 better 2025-09-20 03:00:00 -04:00
royjr b14b6246d7 visual style init 2025-09-20 00:31:07 -04:00
122 changed files with 51 additions and 1549 deletions
+1 -2
View File
@@ -21,12 +21,11 @@ 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 -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
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
PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical
+1 -14
View File
@@ -454,20 +454,7 @@ struct ModelDataV2SP @0xa1680744031fdb2d {
}
}
struct Navigationd @0xcb9fd56c7057593a {
upcomingTurn @0 :Text;
currentSpeedLimit @1 :UInt64;
bannerInstructions @2 :Text;
distanceFromRoute @3 :Float64;
allManeuvers @4 :List(Maneuver);
valid @5 :Bool;
struct Maneuver {
distance @0 :Float64;
type @1 :Text;
modifier @2 :Text;
instruction @3 :Text;
}
struct CustomReserved10 @0xcb9fd56c7057593a {
}
struct CustomReserved11 @0xc2243c65e0340384 {
+1 -1
View File
@@ -2632,7 +2632,7 @@ struct Event {
carStateSP @114 :Custom.CarStateSP;
liveMapDataSP @115 :Custom.LiveMapDataSP;
modelDataV2SP @116 :Custom.ModelDataV2SP;
navigationd @136 :Custom.Navigationd;
customReserved10 @136 :Custom.CustomReserved10;
customReserved11 @137 :Custom.CustomReserved11;
customReserved12 @138 :Custom.CustomReserved12;
customReserved13 @139 :Custom.CustomReserved13;
-1
View File
@@ -89,7 +89,6 @@ _services: dict[str, tuple] = {
"carStateSP": (True, 100., 10),
"liveMapDataSP": (True, 1., 1),
"modelDataV2SP": (True, 20.),
"navigationd": (True, 3.),
"liveLocationKalman": (True, 20.),
# debug
-8
View File
@@ -195,14 +195,6 @@ 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"}},
{"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"}},
-6
View File
@@ -3,7 +3,6 @@ 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
@@ -52,7 +51,6 @@ 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):
@@ -145,7 +143,3 @@ 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
+6 -6
View File
@@ -88,7 +88,7 @@ class SelfdriveD(CruiseHelper):
# TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches
self.car_state_sock = messaging.sub_sock('carState', timeout=20)
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP'] + ['navigationd']
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP']
if SIMULATION:
ignore += ['driverCameraState', 'managerState']
if REPLAY:
@@ -98,8 +98,8 @@ class SelfdriveD(CruiseHelper):
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback',
'modelDataV2SP', 'longitudinalPlanSP', 'navigationd'] + \
self.camera_packets + self.sensor_packets + self.gps_packets,
'modelDataV2SP', 'longitudinalPlanSP'] + \
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
-1
View File
@@ -30,7 +30,6 @@ 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",
@@ -36,7 +36,7 @@ DisplayPanel::DisplayPanel(QWidget *parent) : QWidget(parent) {
interactivityTimeout = new OptionControlSP("InteractivityTimeout", tr("Interactivity Timeout"),
tr("Apply a custom timeout for settings UI."
"\nThis is the time after which settings UI closes automatically if user is not interacting with the screen."),
"", {999999, 1000000}, 1000000, true, nullptr, false);
"", {0, 120}, 10, true, nullptr, false);
connect(interactivityTimeout, &OptionControlSP::updateLabels, [=]() {
refresh();
@@ -1,79 +0,0 @@
/**
* 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 API 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("Enter Mapbox route data"));
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);
// Allow Navigation
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
mapbox_recompute = new ParamControlSP("MapboxRecompute", tr("Mapbox Recompute"), tr("Enable automatic route recomputation"), "", this);
list->addItem(mapbox_recompute);
// Nav Allowed
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);
}
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();
}
@@ -1,38 +0,0 @@
/**
* 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 "selfdrive/ui/qt/offroad/settings.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/input.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
#include "selfdrive/ui/sunnypilot/qt/util.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;
ParamControlSP* mapbox_recompute;
ParamControlSP* nav_allowed;
};
@@ -15,7 +15,6 @@
#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"
@@ -86,7 +85,6 @@ 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"),
+37 -342
View File
@@ -5,7 +5,6 @@
* See the LICENSE.md file in the root directory for more details.
*/
#include <QPainterPath>
#include <cmath>
#include "selfdrive/ui/sunnypilot/qt/onroad/hud.h"
@@ -155,63 +154,6 @@ 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 {
navigationDistance = QString::number(dist / 1000, '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 {
navigationDistance = QString::number(dist_ft / 5280, '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) {
@@ -345,8 +287,6 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
}
}
drawNavigationHUD(p, surface_rect);
p.restore();
}
@@ -366,14 +306,9 @@ bool HudRendererSP::pulseElement(int frame) {
}
void HudRendererSP::drawSmartCruiseControlOnroadIcon(QPainter &p, const QRect &surface_rect, int x_offset, int y_offset, std::string name) {
int base_x = surface_rect.center().x();
int 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);
@@ -384,7 +319,7 @@ void HudRendererSP::drawSmartCruiseControlOnroadIcon(QPainter &p, const QRect &s
int box_width = 160;
int box_height = fm.height() + padding_v * 2;
QRectF bg_rect(base_x - (box_width / 2) + x_offset,
QRectF bg_rect(x - (box_width / 2) + x_offset,
y - (box_height / 2) + y_offset,
box_width, box_height);
@@ -749,16 +684,15 @@ void HudRendererSP::drawSpeedLimitPreActiveArrow(QPainter &p, QRect &sign_rect)
}
void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) {
// 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;
}
// 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);
// Colors based on status
QColor max_color = QColor(0xa6, 0xa6, 0xa6, 0xff);
@@ -779,62 +713,29 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) {
}
}
QString max_str = (icbm_active_counter != 0) ? QString::number(std::nearbyint(speedCluster)) : tr("MAX");
QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(set_speed)) : "";
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);
// 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 {
// 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);
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);
}
void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const QString &alert_alt_text) {
@@ -893,40 +794,12 @@ 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");
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(176, QFont::Bold));
HudRenderer::drawText(p, surface_rect.center().x(), 210, speedStr);
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);
}
p.setFont(InterFont(66));
HudRenderer::drawText(p, surface_rect.center().x(), 290, is_metric ? tr("km/h") : tr("mph"), 200);
}
void HudRendererSP::drawBlinker(QPainter &p, const QRect &surface_rect) {
@@ -954,7 +827,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 = navigationValid ? 352 : 300;
const int y_offset = 272;
const int centerX = surface_rect.center().x();
@@ -1012,181 +885,3 @@ 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;
const int container_x = (surface_rect.width() - container_width) / 2;
const int container_y = 62;
const int border_radius = 42;
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, icon_y + icon_size, icon_size, 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();
}
-11
View File
@@ -39,8 +39,6 @@ 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;
@@ -122,13 +120,4 @@ 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;
};
+1 -1
View File
@@ -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", "navigationd", "liveTracks"
"carStateSP", "liveParameters", "liveMapDataSP", "carParamsSP", "liveTracks"
});
// update timer
-6
View File
@@ -1,6 +0,0 @@
# 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).
View File
-16
View File
@@ -1,16 +0,0 @@
"""
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
-2
View File
@@ -126,8 +126,6 @@ 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'
@@ -1,44 +0,0 @@
"""
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.leftBlindspot or CS.vEgo < self._turn_speed_limit):
self.desire = log.Desire.keepLeft
elif upcoming == 'slightRight' and (not CS.rightBlindspot or CS.vEgo < self._turn_speed_limit):
self.desire = log.Desire.keepRight
elif (upcoming == 'left' and CS.steeringPressed and CS.steeringTorque > 0 and not CS.rightBlinker
and not CS.leftBlindspot and CS.vEgo < self._turn_speed_limit):
self.desire = log.Desire.turnLeft
elif (upcoming == 'right' and CS.steeringPressed and CS.steeringTorque < 0 and not CS.leftBlinker
and not CS.rightBlindspot and CS.vEgo < self._turn_speed_limit):
self.desire = log.Desire.turnRight
return self.desire
@@ -1,96 +0,0 @@
"""
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(), log.Desire.keepLeft),
('slightRight', make_car(), 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, steeringPressed=True, steeringTorque=1), log.Desire.turnLeft),
('left', make_car(vEgo=5, leftBlinker=False, rightBlinker=True), log.Desire.none),
('right', make_car(vEgo=6, rightBlinker=True, leftBlindspot=False, steeringPressed=True, steeringTorque=-1), 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=9, leftBlinker=True, steeringPressed=True, steeringTorque=1), 'slightLeft', log.Desire.turnLeft, log.Desire.keepLeft),
(make_car(vEgo=9, 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
@@ -1,113 +0,0 @@
"""
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,
}
@@ -1,138 +0,0 @@
"""
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 openpilot.common.constants import CV
from openpilot.common.params import Params
from openpilot.sunnypilot.navd.helpers import Coordinate, string_to_direction
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
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
closest_idx, min_distance = min(((idx, self.coord.distance_to(coord)) for idx, coord in enumerate(route['geometry'])), key=lambda x: x[1])
closest_cumulative = route['cumulative_distances'][closest_idx]
# 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': 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 = {
'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 get_upcoming_turn_from_progress(self, progress, current_lat, current_lon) -> str:
if progress and progress['next_turn']:
self.coord.latitude = current_lat
self.coord.longitude = current_lon
distance = self.coord.distance_to(progress['next_turn']['location'])
if distance <= 30.0:
modifier = progress['next_turn']['modifier']
return str(modifier)
return 'none'
@staticmethod
def get_current_speed_limit_from_progress(progress, is_metric: bool) -> int:
if progress and progress['current_maxspeed']:
speed, _ = progress['current_maxspeed']
if is_metric:
return int(speed)
else:
return int(round(speed * CV.KPH_TO_MPH))
return 0
@staticmethod
def arrived_at_destination(progress) -> bool:
if progress['all_maneuvers'][0]['type'] == 'arrive':
return True
elif progress['all_maneuvers'][0]['instruction'].startswith('Your destination'):
return True
return False
@@ -1,94 +0,0 @@
"""
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)
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.00025 # 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)
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.nav.get_current_speed_limit_from_progress(self.progress, True)
speed_limit_imperial = self.nav.get_current_speed_limit_from_progress(self.progress, False)
assert isinstance(speed_limit_metric, int)
assert isinstance(speed_limit_imperial, int)
expected_metric = int(self.progress['current_maxspeed'][0])
expected_imperial = int(round(self.progress['current_maxspeed'][0] * CV.KPH_TO_MPH))
assert speed_limit_metric == expected_metric
assert speed_limit_imperial == expected_imperial
def test_arrival_detection(self):
is_arrived = self.nav.arrived_at_destination(self.progress)
assert isinstance(is_arrived, bool)
assert not is_arrived
-160
View File
@@ -1,160 +0,0 @@
"""
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 math
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(['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.is_metric: bool = False
self.valid: bool = False
def _update_params(self):
if self.last_position is not None:
self.frame += 1
if self.frame % 9 == 0:
self.allow_navigation = self.params.get('AllowNavigation', return_default=True)
self.is_metric = self.params.get('IsMetric', 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)
cloudlog.debug(f'Set new destination to: {self.new_destination}, valid: {valid_addr}')
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.destination = None
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.last_position is not None:
if progress := self.nav_instructions.get_route_progress(self.last_position.latitude, self.last_position.longitude):
nav_data['upcoming_turn'] = self.nav_instructions.get_upcoming_turn_from_progress(progress, self.last_position.latitude, self.last_position.longitude)
nav_data['current_speed_limit'] = self.nav_instructions.get_current_speed_limit_from_progress(progress, self.is_metric)
arrived = self.nav_instructions.arrived_at_destination(progress)
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']
large_distance = progress['distance_from_route'] > 100
if large_distance:
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
else:
self.cancel_route_counter = 0
self.reroute_counter = 0
# Don't recompute in last segment to prevent reroute loops
if self.route:
if progress['current_step_idx'] == len(self.route['steps']) - 1:
self.allow_recompute = False
else:
banner_instructions = ''
progress = None
nav_data = {}
self.valid = False
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()
location = self.sm['liveLocationKalman']
localizer_valid = location.positionGeodetic.valid if location else False
if localizer_valid:
self.last_bearing = math.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()
View File
-76
View File
@@ -1,76 +0,0 @@
"""
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
def test_cancel_route(self):
nav = Navigationd()
nav.last_position = Coordinate(latitude=37.0, longitude=128.0)
nav.route = {'580 Winchester dr, oxnard, CA': True}
nav.cancel_route_counter = 30
nav._update_params()
assert nav.route is None
assert nav.destination is None
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ae17f4c1896c88a6d520f1acdca99ab23d4ce1140a500b64bea3185041f8ecd9
size 1246
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:86a2eaf6070d6b93df385945bd7f4a6e9bb78b7cc42eb5b7ef9a9b6c075c80ce
size 1581
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:398f30c89e396bb4bdbac1ba6dfe292f6e3278923e14113e705f4d0591671f32
size 1528
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ae17f4c1896c88a6d520f1acdca99ab23d4ce1140a500b64bea3185041f8ecd9
size 1246
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0e23cac9bec0de20f9632d19618be9b05eedb010c6e23b0ba7a8e3cc8225f64e
size 865
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:008cf037b8713c710eacd1129973119faa2527ab1c19aee5340a16dc0659df8a
size 1063
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:579d6a27e062779d41beb9320abf5797bc8ec81de1f355a4197c3b3ee0766f28
size 1400
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4010c8122af918dbdb50e88629adbf79f7733f022bba263e6f44f6ffae3e7a5
size 1371
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:89041889817454869d98adfb5a16fae33596c038d5484ec734fe3a5cc532c45c
size 1334
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e691dd43947c9430fcd73e6bede94aa5fd8995e9303eba53cc0ac589a7c9f106
size 1347
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:008cf037b8713c710eacd1129973119faa2527ab1c19aee5340a16dc0659df8a
size 1063
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e02ac2720f12f9373ae2d86d91fffb6203e18aba04b493bd6f41d2953a11cbc9
size 1780
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8f83214c1fa6aba33ca3e1fe29db09733a275efe19864866b9edecbb59abb171
size 1234
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:459fe6410e061542876fa7de93efaf901d15937f891e801e1b93f8270e750130
size 1613
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e3f5fb80d62c92876a5e490917f549e2a443dfc683a6787506747bbbeb75d33b
size 1538
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8f83214c1fa6aba33ca3e1fe29db09733a275efe19864866b9edecbb59abb171
size 1234
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b9c86ddd75ea1ed2c49c1bdc2e7f98ed15d12226938073688dda1b0bbdf62308
size 1439
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cc7248e62d11cf36f9a13d408a0954a51ff0eee9b8e03d8910a2eb2cb6a09ffd
size 1456
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8509bd5dbf96e44f73421dc09ca8f7c1dddf2dc21f3b4b15650472b3e06b3470
size 658
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5198a3fdb610001358501ca76ab52fe0979b9254ed85cbbb2ce1fab1cb77929a
size 2015
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:752424da0ad8430c619045455a86c640b576f8bbe6d3d8b5b6a599b1c61ef489
size 1863
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b6590df8da1f30ec61b3bdf729fda1956d340c5abab448ea0062105500cc5da5
size 1841
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b4fbd63acf8f5e7f734b332a96e9ad138ad22ee07000ad425712e71b77ce0728
size 1800
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a7a7f5bcab30ce961ff7e345c460cfbd133e45c66da1fe1f0e7bc7c826b05f30
size 1805
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:676cbe95f9a663816dd77c55dbcf2c346bfae2454be7832914b5527219aba91f
size 2473
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:008cf037b8713c710eacd1129973119faa2527ab1c19aee5340a16dc0659df8a
size 1063
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:579d6a27e062779d41beb9320abf5797bc8ec81de1f355a4197c3b3ee0766f28
size 1400
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4010c8122af918dbdb50e88629adbf79f7733f022bba263e6f44f6ffae3e7a5
size 1371
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:89041889817454869d98adfb5a16fae33596c038d5484ec734fe3a5cc532c45c
size 1334
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e691dd43947c9430fcd73e6bede94aa5fd8995e9303eba53cc0ac589a7c9f106
size 1347
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:008cf037b8713c710eacd1129973119faa2527ab1c19aee5340a16dc0659df8a
size 1063
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e02ac2720f12f9373ae2d86d91fffb6203e18aba04b493bd6f41d2953a11cbc9
size 1780
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:08245891301f814fc0245133e39df3fb14720025cc3e4bc7131f953ead47edc8
size 1535
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8c629c7273fe2613538295aba807edfddbbae86ae77d6f9b0b60b0486e3f7ca9
size 1484
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:72c1ef39486f3dc3f7a0a97777bce16c7c369cc85bf6c82629d6780803804967
size 1765
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8b8bfd8019320127ed0fe03d4a1bddab57d5c2345b6cdeab66558ea9b53c0266
size 1699
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d10bdde82e7896de072de28da46412d5e2f6c902fd5643c6b48a8fc79ed4232f
size 113
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:579d6a27e062779d41beb9320abf5797bc8ec81de1f355a4197c3b3ee0766f28
size 1400
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4010c8122af918dbdb50e88629adbf79f7733f022bba263e6f44f6ffae3e7a5
size 1371
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0457b0597386d0b7d3cc06d961df5fb34211ee575da7fab2f81e424d9954bb42
size 1549
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f6e1556f0b888afb6856f8cf3e16632959190f88dd66c4d9db4832a8269cbc5
size 1552
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:89041889817454869d98adfb5a16fae33596c038d5484ec734fe3a5cc532c45c
size 1334
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e691dd43947c9430fcd73e6bede94aa5fd8995e9303eba53cc0ac589a7c9f106
size 1347
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:008cf037b8713c710eacd1129973119faa2527ab1c19aee5340a16dc0659df8a
size 1063
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4010c8122af918dbdb50e88629adbf79f7733f022bba263e6f44f6ffae3e7a5
size 1371
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:57dbfdabe88f6abbb8993982eef72803ac504842e9a9d388e7dde1623f69f0d7
size 1551
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:579d6a27e062779d41beb9320abf5797bc8ec81de1f355a4197c3b3ee0766f28
size 1400
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4010c8122af918dbdb50e88629adbf79f7733f022bba263e6f44f6ffae3e7a5
size 1371
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f2016b7bac3accf541f100a2c83871618214e1806e95e4d80867d30d1dfcf03
size 1546
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:89041889817454869d98adfb5a16fae33596c038d5484ec734fe3a5cc532c45c
size 1334
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e691dd43947c9430fcd73e6bede94aa5fd8995e9303eba53cc0ac589a7c9f106
size 1347
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:008cf037b8713c710eacd1129973119faa2527ab1c19aee5340a16dc0659df8a
size 1063
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6736b6c2123cbaf90d2c8e4da79a4089ba42c0e7a220a596e45afd511166da9b
size 1524
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6a636d33568c658b05e460e05a1eac5c4e1cfced204044657a04191027c44c4c
size 1580
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a27d55d704f77bca4beaac30a2c5b5abee304986627d09f971b2a2e0367e8477
size 1478
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e711c8e2184440f4ddead06897c90758470e7475fa7e0cf8b2db567e491c8e57
size 1519
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:579d6a27e062779d41beb9320abf5797bc8ec81de1f355a4197c3b3ee0766f28
size 1400
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4010c8122af918dbdb50e88629adbf79f7733f022bba263e6f44f6ffae3e7a5
size 1371
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0457b0597386d0b7d3cc06d961df5fb34211ee575da7fab2f81e424d9954bb42
size 1549
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:57dbfdabe88f6abbb8993982eef72803ac504842e9a9d388e7dde1623f69f0d7
size 1551
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7b2f19864347794510da5e13ebaed40cc01d45bbd649c3e5097052607af52fa7
size 1334
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0d2704d4d480568deb63598a48b741b024f4e55f5936e3eeb9cdc3daf6618837
size 1346
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:008cf037b8713c710eacd1129973119faa2527ab1c19aee5340a16dc0659df8a
size 1063
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1868ec727cd761aad76fb6cb7705360ed9f70a92aea97f4c3b945b043996d1e2
size 1906
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b2d41ab080452ce24ce27ed833f24790a6e6218dc4a5df4b35d23d6cfc425fbb
size 1543
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:94251b2485f1f8bd0b22eb32fd59459626229c38e4de9aa538ff249b3cc16505
size 1743
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6fbc14fb3720b7452c34c64af8c9b6ce4c953a5e65f3b28ad1ed17b5b2bc47f0
size 1532
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0fe99a15432706ac8bdd5766d3ed4869042b50e0d3c54665b63ba8eb2aaaef20
size 1855
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:45115d7c0d4a6285406777787b19b5f6f9c8231ba773aaaa86ab62b7f94b1c51
size 1699

Some files were not shown because too many files have changed in this diff Show More