Compare commits

...

51 Commits

Author SHA1 Message Date
Kumar f275604cb2 Update accel_controller.py 2025-11-16 16:11:47 -07:00
Kumar 495bacc576 Update dynamic_follow.py 2025-11-16 16:10:22 -07:00
Kumar 45a9e4c14c Update follow distance profiles and breakpoints 2025-11-16 07:12:10 -07:00
Kumar 67e23a408c Adjust eco acceleration and breakpoint values 2025-11-16 07:11:27 -07:00
rav4kumar a31c5cf227 ref 2025-11-15 06:06:48 -07:00
rav4kumar 5164951ffa ref 2025-11-14 07:05:04 -07:00
rav4kumar db845cdaab ff 2025-11-13 07:04:20 -07:00
rav4kumar 006052c2f3 origin/update-tinygrad 2025-11-12 22:16:06 -07:00
Kumar 32c48b5ffc Update dynamic_follow.py 2025-11-12 18:29:06 -07:00
Kumar 85503e6054 Update accel_controller.py 2025-11-12 18:28:04 -07:00
rav4kumar 2a4bb20f9d ref 2025-11-11 21:19:31 -07:00
rav4kumar 295dac06f1 bai bai llk 2025-11-11 20:53:12 -07:00
rav4kumar eb11de561b a7 2025-11-11 20:51:19 -07:00
rav4kumar 1dd06cc174 a6 2025-11-10 21:54:45 -07:00
rav4kumar 21601660c5 mapdout 2025-11-10 11:25:16 -07:00
rav4kumar 923318b6bb a5 2025-11-10 11:06:21 -07:00
rav4kumar 6e5fb2b9e9 consistency 2025-11-10 10:09:18 -07:00
rav4kumar d413310cbb what if 2025-11-09 19:42:28 -07:00
rav4kumar 86751559cd mapd 2025-11-09 16:13:09 -07:00
rav4kumar 5093b0d1b3 origin/nav-commacon' 2025-11-08 21:09:36 -07:00
rav4kumar e0fd11c184 off 2025-11-08 21:02:02 -07:00
rav4kumar 204eaebf55 what if 2025-11-08 21:02:02 -07:00
Kumar 9fcb4c9ca8 Update accel_controller.py 2025-11-08 21:02:01 -07:00
Kumar da561e8285 Update dynamic_follow.py 2025-11-08 21:02:01 -07:00
rav4kumar 691eb4ee9f what if 2025-11-08 21:02:01 -07:00
rav4kumar c69a340599 just try 2025-11-08 21:02:01 -07:00
rav4kumar 5ef2f5d1fb tune 2025-11-08 21:02:01 -07:00
rav4kumar 3ced0d6115 df exponential smoothing 2025-11-08 21:02:01 -07:00
rav4kumar cdcc2392a8 exponential smoothing 2025-11-08 21:02:01 -07:00
Kumar 22aff8d2eb Update accel_controller.py 2025-11-08 21:02:01 -07:00
Kumar 2c8cb003f9 Update accel_controller.py 2025-11-08 21:02:01 -07:00
Kumar 3ae81d10a3 Update dynamic_follow.py 2025-11-08 21:02:01 -07:00
Kumar c90289c949 Update accel_controller.py 2025-11-08 21:02:01 -07:00
rav4kumar b32c64130c tune 2025-11-08 21:02:01 -07:00
Kumar 9cafeefaf1 Update long_mpc.py 2025-11-08 21:02:01 -07:00
Kumar 2484851edd Update longitudinal_planner.py 2025-11-08 21:02:01 -07:00
rav4kumar 870d5c3a74 tt 2025-11-08 21:02:01 -07:00
Kumar 8e841cac89 Update accel_controller.py 2025-11-08 21:02:01 -07:00
Kumar 51081e8464 Update custom.capnp 2025-11-08 21:02:01 -07:00
rav4kumar 5f8875b16e conflic 2025-11-08 21:02:01 -07:00
rav4kumar c47223ec59 ff 2025-11-08 21:02:01 -07:00
rav4kumar 3e84bbc9aa ref 2025-11-08 21:02:01 -07:00
rav4kumar 8587e08908 merge conflict 2025-11-08 21:02:01 -07:00
rav4kumar 4dadfbad1f Concept by Aleksei Voronov: At SunnyPilot, we turn concepts into reality. 2025-11-08 21:02:01 -07:00
rav4kumar 4a0ba000d3 rebase fix 2025-11-08 21:02:00 -07:00
rav4kumar 1d7165786a point the submodule 2025-11-08 21:02:00 -07:00
rav4kumar c750a631ef misc. toggles. 2025-11-08 21:02:00 -07:00
rav4kumar 0f6006c87b feat/relc 2025-11-08 21:02:00 -07:00
rav4kumar 19eca5e035 drive mode btn support 2025-11-08 21:02:00 -07:00
rav4kumar 5010d752f6 rip vibecontroller in to two halfs 2025-11-08 21:01:59 -07:00
rav4kumar 07efcb4abb abh, bsm 2025-11-08 21:01:57 -07:00
159 changed files with 3208 additions and 194 deletions
@@ -74,7 +74,7 @@ jobs:
env:
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
run: |
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
cd gitlab_docs
git checkout main
git sparse-checkout set --no-cone models/
@@ -191,7 +191,7 @@ jobs:
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
run: |
echo "Cloning GitLab"
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
cd gitlab_docs
echo "checkout models/${RECOMPILED_DIR}"
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}
@@ -109,7 +109,7 @@ jobs:
GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts'
run: |
echo "Cloning GitLab"
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs
git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs
cd gitlab_docs
echo "checkout models/${RECOMPILED_DIR}"
git sparse-checkout set --no-cone models/${RECOMPILED_DIR}
+2 -1
View File
@@ -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
@@ -156,6 +156,8 @@ jobs:
with:
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
path: ${{ github.workspace }}/selfdrive/modeld/models
- run: |
rm -f ${{ github.workspace }}/selfdrive/modeld/models/{dmonitoring_model,big_driving_policy,big_driving_vision}.onnx
- name: Build Model
run: |
+1
View File
@@ -4,6 +4,7 @@
[submodule "opendbc"]
path = opendbc_repo
url = https://github.com/sunnypilot/opendbc.git
branch = tn
[submodule "msgq"]
path = msgq_repo
url = https://github.com/sunnypilot/msgq.git
+99 -5
View File
@@ -192,6 +192,7 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
aTarget @5 :Float32;
events @6 :List(OnroadEventSP.Event);
e2eAlerts @7 :E2eAlerts;
accelPersonality @8 :AccelerationPersonality;
struct DynamicExperimentalControl {
state @0 :DynamicExperimentalControlState;
@@ -203,7 +204,11 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
blended @1;
}
}
enum AccelerationPersonality {
sport @0;
normal @1;
eco @2;
}
struct SmartCruiseControl {
vision @0 :Vision;
map @1 :Map;
@@ -340,6 +345,7 @@ struct OnroadEventSP @0xda96579883444c35 {
speedLimitChanged @21;
speedLimitPending @22;
e2eChime @23;
laneChangeRoadEdge @24;
}
}
@@ -446,6 +452,8 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
struct ModelDataV2SP @0xa1680744031fdb2d {
laneTurnDirection @0 :TurnDirection;
leftLaneChangeEdgeBlock @1 :Bool;
rightLaneChangeEdgeBlock @2 :Bool;
enum TurnDirection {
none @0;
@@ -454,7 +462,20 @@ struct ModelDataV2SP @0xa1680744031fdb2d {
}
}
struct CustomReserved10 @0xcb9fd56c7057593a {
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 CustomReserved11 @0xc2243c65e0340384 {
@@ -475,11 +496,84 @@ struct CustomReserved15 @0xbd443b539493bc68 {
struct CustomReserved16 @0xfc6241ed8877b611 {
}
struct CustomReserved17 @0xa30662f84033036c {
enum MapdExtendedOutType {
paths @0;
settings @1;
}
struct CustomReserved18 @0xc86a3d38d13eb3ef {
struct MapdExtendedOut @0xa30662f84033036c {
type @0 :MapdExtendedOutType;
json @1 :Text;
}
struct CustomReserved19 @0xa4f1eb3323f5f582 {
enum MapdInputType {
download @0;
setTargetLateralAccel @1;
setSpeedLimitOffset @2;
setSpeedLimitControl @3;
setCurveSpeedControl @4;
setVisionCurveSpeedControl @5;
setLogLevel @6;
setVisionCurveTargetLatA @7;
setVisionCurveMinTargetV @8;
reloadSettings @9;
saveSettings @10;
setEnableSpeed @11;
setVisionCurveUseEnableSpeed @12;
setCurveUseEnableSpeed @13;
setSpeedLimitUseEnableSpeed @14;
setHoldLastSeenSpeedLimit @15;
setCurveTargetJerk @16;
setCurveTargetAccel @17;
setCurveTargetOffset @18;
setDefaultLaneWidth @19;
setCurveTargetLatA @20;
loadDefaultSettings @21;
loadRecommendedSettings @22;
setSlowDownForNextSpeedLimit @23;
setSpeedUpForNextSpeedLimit @24;
setHoldSpeedLimitWhileChangingSetSpeed @25;
}
enum SpeedLimitOffsetType {
static @0;
percent @1;
}
struct MapdIn @0xc86a3d38d13eb3ef {
type @0 :MapdInputType;
float @1 :Float32;
str @2 :Text;
bool @3 :Bool;
}
enum RoadContext {
freeway @0;
city @1;
unknown @2;
}
struct MapdOut @0xa4f1eb3323f5f582 {
wayName @0 :Text;
wayRef @1 :Text;
roadName @2 :Text;
speedLimit @3 :Float32;
nextSpeedLimit @4 :Float32;
nextSpeedLimitDistance @5 :Float32;
hazard @6 :Text;
nextHazard @7 :Text;
nextHazardDistance @8 :Float32;
advisorySpeed @9 :Float32;
nextAdvisorySpeed @10 :Float32;
nextAdvisorySpeedDistance @11 :Float32;
oneWay @12 :Bool;
lanes @13 :UInt8;
tileLoaded @14 :Bool;
speedLimitOffset @15 :Float32;
suggestedSpeed @16 :Float32;
estimatedRoadWidth @17 :Float32;
roadContext @18 :RoadContext;
distanceFromWayCenter @19 :Float32;
visionCurveSpeed @20 :Float32;
curveSpeed @21 :Float32;
}
+4 -4
View File
@@ -2632,16 +2632,16 @@ 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;
customReserved14 @140 :Custom.CustomReserved14;
customReserved15 @141 :Custom.CustomReserved15;
customReserved16 @142 :Custom.CustomReserved16;
customReserved17 @143 :Custom.CustomReserved17;
customReserved18 @144 :Custom.CustomReserved18;
customReserved19 @145 :Custom.CustomReserved19;
mapdExtendedOut @143 :Custom.MapdExtendedOut;
mapdIn @144 :Custom.MapdIn;
mapdOut @145 :Custom.MapdOut;
# *********** legacy + deprecated ***********
model @9 :Legacy.ModelData; # TODO: rename modelV2 and mark this as deprecated
+2
View File
@@ -89,7 +89,9 @@ _services: dict[str, tuple] = {
"carStateSP": (True, 100., 10),
"liveMapDataSP": (True, 1., 1),
"modelDataV2SP": (True, 20.),
"navigationd": (True, 3.),
"liveLocationKalman": (True, 20.),
"mapdOut": (True, 20., 20),
# debug
"uiDebug": (True, 0., 1),
+22
View File
@@ -130,6 +130,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"Version", {PERSISTENT, STRING}},
// --- sunnypilot params --- //
{"AccelPersonality", {PERSISTENT | BACKUP, INT, std::to_string(static_cast<int>(cereal::LongitudinalPlanSP::AccelerationPersonality::NORMAL))}},
{"AccelPersonalityEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ApiCache_DriveStats", {PERSISTENT, JSON}},
{"AutoLaneChangeBsmDelay", {PERSISTENT | BACKUP, BOOL, "0"}},
{"AutoLaneChangeTimer", {PERSISTENT | BACKUP, INT, "0"}},
@@ -146,6 +148,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
{"DevUIInfo", {PERSISTENT | BACKUP, INT, "0"}},
{"DynamicFollow", {PERSISTENT | BACKUP, BOOL, "0"}},
{"EnableCopyparty", {PERSISTENT | BACKUP, BOOL}},
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
{"GreenLightAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
@@ -168,11 +171,19 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RoadEdgeLaneChangeEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}},
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
// toyota specific params
{"ToyotaAutoHold", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaEnhancedBsm", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaTSS2Long", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaStockLongitudinal", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaDriveMode", {PERSISTENT | BACKUP, BOOL, "0"}},
// MADS params
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
{"MadsMainCruiseAllowed", {PERSISTENT | BACKUP, BOOL, "1"}},
@@ -187,6 +198,14 @@ 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"}},
@@ -220,6 +239,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"LaneTurnDesire", {PERSISTENT | BACKUP, BOOL, "0"}},
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
// mapd v020
{"MapdSettings", {PERSISTENT | BACKUP, JSON}},
// mapd
{"MapAdvisorySpeedLimit", {CLEAR_ON_ONROAD_TRANSITION, FLOAT}},
{"MapdVersion", {PERSISTENT, STRING}},
+7 -1
View File
@@ -10,7 +10,7 @@ from cereal import car, log, custom
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
from openpilot.common.swaglog import cloudlog, ForwardingHandler
from opendbc.safety import ALTERNATIVE_EXPERIENCE
from opendbc.car import DT_CTRL, structs
from opendbc.car.can_definitions import CanData, CanRecvCallable, CanSendCallable
from opendbc.car.carlog import carlog
@@ -123,7 +123,13 @@ class Car:
self.CI, self.CP, self.CP_SP = CI, CI.CP, CI.CP_SP
self.RI = RI
# set alternative experiences from parameters
sp_toyota_auto_brake_hold = self.params.get_bool("ToyotaAutoHold")
self.CP.alternativeExperience = 0
if sp_toyota_auto_brake_hold:
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALLOW_AEB
# mads
set_alternative_experience(self.CP, self.CP_SP, self.params)
set_car_specific_params(self.CP, self.CP_SP, self.params)
+9 -3
View File
@@ -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,12 +52,13 @@ 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):
return LaneChangeDirection.left if CS.leftBlinker else LaneChangeDirection.right
def update(self, carstate, lateral_active, lane_change_prob):
def update(self, carstate, lateral_active, lane_change_prob, left_edge_detected, right_edge_detected):
self.alc.update_params()
self.lane_turn_controller.update_params()
v_ego = carstate.vEgo
@@ -88,8 +90,8 @@ class DesireHelper:
((carstate.steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
(carstate.steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))
blindspot_detected = ((carstate.leftBlindspot and self.lane_change_direction == LaneChangeDirection.left) or
(carstate.rightBlindspot and self.lane_change_direction == LaneChangeDirection.right))
blindspot_detected = (((carstate.leftBlindspot or left_edge_detected) and self.lane_change_direction == LaneChangeDirection.left) or
((carstate.rightBlindspot or right_edge_detected) and self.lane_change_direction == LaneChangeDirection.right))
self.alc.update_lane_change(blindspot_detected, carstate.brakePressed)
@@ -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
@@ -10,6 +10,8 @@ from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.modeld.constants import index_function
from openpilot.selfdrive.controls.radard import _LEAD_ACCEL_TAU
from openpilot.sunnypilot.selfdrive.controls.lib.accel_personality.accel_controller import AccelPersonalityController
from openpilot.sunnypilot.selfdrive.controls.lib.dynamic_personality.dynamic_follow import FollowDistanceController
if __name__ == '__main__': # generating code
from openpilot.third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver
else:
@@ -228,6 +230,8 @@ class LongitudinalMpc:
self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
self.reset()
self.source = SOURCES[2]
self.accel_controller = AccelPersonalityController()
self.dynamic_follow = FollowDistanceController()
def reset(self):
# self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
@@ -328,10 +332,27 @@ class LongitudinalMpc:
return lead_xv
def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalPersonality.standard):
t_follow = get_T_FOLLOW(personality)
v_ego = self.x0[1]
if self.dynamic_follow.is_enabled():
t_follow = self.dynamic_follow.get_follow_distance_multiplier(v_ego)
#print(f"DEBUG: dynamic_follow enabled, t_follow={t_follow:.3f}, v_ego={v_ego:.2f}, v_cruise={v_cruise:.2f}")
else:
t_follow = get_T_FOLLOW(personality)
#print(f"DEBUG: dynamic_follow disabled, using personality t_follow={t_follow:.3f}, personality={personality}")
self.status = radarstate.leadOne.status or radarstate.leadTwo.status
# Get acceleration limits
if self.accel_controller.is_enabled():
min_accel = self.accel_controller.get_min_accel(v_ego)
#print(f"DEBUG: accel_enabled=True, min_accel={min_accel:.3f}")
else:
min_accel = CRUISE_MIN_ACCEL
#print(f"DEBUG: accel_enabled=False, using stock min_accel={min_accel}")
a_cruise_min = min_accel
lead_xv_0 = self.process_lead(radarstate.leadOne)
lead_xv_1 = self.process_lead(radarstate.leadTwo)
@@ -350,7 +371,7 @@ class LongitudinalMpc:
# Fake an obstacle for cruise, this ensures smooth acceleration to set speed
# when the leads are no factor.
v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05)
v_lower = v_ego + (T_IDXS * a_cruise_min * 1.05)
# TODO does this make sense when max_a is negative?
v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05)
v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),
+11 -1
View File
@@ -124,7 +124,13 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
prev_accel_constraint = not (reset_state or sm['carState'].standstill)
if mode == 'acc':
accel_clip = [ACCEL_MIN, get_max_accel(v_ego)]
if self.accel_controller.is_enabled():
max_accel = self.accel_controller.get_max_accel(v_ego)
#print(f"Vibe personality active - max accel: {max_accel:.3f}")
accel_clip = [ACCEL_MIN, max_accel]
else:
accel_clip = [ACCEL_MIN, get_max_accel(v_ego)]
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP)
else:
@@ -149,6 +155,10 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
# Get new v_cruise and a_desired from Smart Cruise Control and Speed Limit Assist
v_cruise, self.a_desired = LongitudinalPlannerSP.update_targets(self, sm, self.v_desired_filter.x, self.a_desired, v_cruise)
if sm.valid['mapdOut']:
if sm['mapdOut'].suggestedSpeed > 0 and v_cruise > sm['mapdOut'].suggestedSpeed:
v_cruise = sm['mapdOut'].suggestedSpeed
if force_slow_decel:
v_cruise = 0.0
+2 -2
View File
@@ -27,12 +27,12 @@ def main():
longitudinal_planner = LongitudinalPlanner(CP, CP_SP)
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState',
'liveMapDataSP', 'carStateSP', gps_location_service],
'liveMapDataSP', 'carStateSP', 'mapdOut', gps_location_service],
poll='carState')
while True:
sm.update()
longitudinal_planner.sla.update_car_state(sm['carState'])
#longitudinal_planner.sla.update_car_state(sm['carState'])
if sm.updated['modelV2']:
longitudinal_planner.update(sm)
longitudinal_planner.publish(sm, pm)
Executable
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -51,8 +51,8 @@ def tg_compile(flags, model_name):
for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']:
flags = {
'larch64': 'DEV=QCOM',
'Darwin': 'DEV=CPU IMAGE=0',
}.get(arch, 'DEV=LLVM IMAGE=0')
'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")} IMAGE=0', # tinygrad calls brew which needs a $HOME in the env
}.get(arch, 'DEV=CPU CPU_LLVM=1 IMAGE=0')
tg_compile(flags, model_name)
# Compile BIG model if USB GPU is available
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
import os
from openpilot.system.hardware import TICI
os.environ['DEV'] = 'QCOM' if TICI else 'LLVM'
os.environ['DEV'] = 'QCOM' if TICI else 'CPU'
from tinygrad.tensor import Tensor
from tinygrad.dtype import dtypes
import math
+7 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
import os
from openpilot.system.hardware import TICI
os.environ['DEV'] = 'QCOM' if TICI else 'LLVM'
os.environ['DEV'] = 'QCOM' if TICI else 'CPU'
USBGPU = "USBGPU" in os.environ
if USBGPU:
os.environ['DEV'] = 'AMD'
@@ -33,7 +33,7 @@ from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
PROCESS_NAME = "selfdrive.modeld.modeld"
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
@@ -298,6 +298,7 @@ def main(demo=False):
prev_action = log.ModelDataV2.Action()
DH = DesireHelper()
RELC = RoadEdgeLaneChangeController(params.get_bool("RoadEdgeLaneChangeEnabled"))
while True:
# Keep receiving frames until we are at least 1 frame ahead of previous extra frame
@@ -395,7 +396,10 @@ def main(demo=False):
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
r_lane_change_prob = desire_state[log.Desire.laneChangeRight]
lane_change_prob = l_lane_change_prob + r_lane_change_prob
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
RELC.update(modelv2_send.modelV2.roadEdgeStds, modelv2_send.modelV2.laneLineProbs)
mdv2sp_send.modelDataV2SP.leftLaneChangeEdgeBlock = RELC.left_edge_detected
mdv2sp_send.modelDataV2SP.rightLaneChangeEdgeBlock = RELC.right_edge_detected
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob, RELC.left_edge_detected, RELC.right_edge_detected)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
+14 -8
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']
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP'] + ['navigationd']
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'] + \
self.camera_packets + self.sensor_packets + self.gps_packets,
'modelDataV2SP', 'longitudinalPlanSP', 'navigationd'] + \
self.camera_packets + self.sensor_packets + self.gps_packets,
ignore_alive=ignore, ignore_avg_freq=ignore,
ignore_valid=ignore, frequency=int(1/DT_CTRL))
@@ -230,8 +230,8 @@ class SelfdriveD(CruiseHelper):
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
self.events.add(EventName.pedalPressed)
# Create events for temperature, disk space, and memory
@@ -292,16 +292,22 @@ class SelfdriveD(CruiseHelper):
# Handle lane change
if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange:
direction = self.sm['modelV2'].meta.laneChangeDirection
mdv2sp = self.sm['modelDataV2SP']
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)
elif mdv2sp.leftLaneChangeEdgeBlock or mdv2sp.rightLaneChangeEdgeBlock:
self.events_sp.add(custom.OnroadEventSP.EventName.laneChangeRoadEdge)
else:
if direction == LaneChangeDirection.left:
self.events.add(EventName.preLaneChangeLeft)
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 +502,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
+55 -1
View File
@@ -33,6 +33,42 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"../assets/icons/experimental_white.svg",
false,
},
{
"ToyotaDriveMode",
tr("Enable drive mode btn link"),
tr("Links cars drive mode btn with accel personalities based on personality (i.e., relaxed, standard, sport)"),
"../assets/offroad/icon_blank.png",
false,
},
{
"ToyotaAutoHold",
tr("Toyota: Auto Brake Hold FOR TSS2 HYBRID CARS"),
tr("As you may auto brake hold currently supported by openpilot, this feature will allow sunnypilot to automatically hold the vehicle at a stop when the lead car is stopped. (TSS2 Hybird only)"),
"../assets/offroad/icon_blank.png",
false,
},
{
"ToyotaEnhancedBsm",
tr("Toyota: Prius TSS2 BSM and some tssp"),
tr("Add support for BSM."),
"../assets/offroad/icon_blank.png",
false,
},
{
"ToyotaTSS2Long",
tr("Toyota: custom tune"),
tr("idk something gas and brake"),
"../assets/offroad/icon_blank.png",
false,
},
{
"ToyotaStockLongitudinal",
tr("Toyota: Stock Toyota Longitudinal"),
tr("This feature will allow sunnypilot to use the stock Toyota longitudinal control instead of the sunnypilot longitudinal control. "
""),
"../assets/offroad/icon_blank.png",
false,
},
{
"DisengageOnAccelerator",
tr("Disengage on Accelerator Pedal"),
@@ -85,7 +121,15 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"your steering wheel distance button."),
"../assets/icons/speed_limit.png",
longi_button_texts);
// accel controller
std::vector<QString> accel_personality_texts{tr("Sport"), tr("Normal"), tr("Eco")};
accel_personality_setting = new ButtonParamControlSP("AccelPersonality", tr("Acceleration Personality"),
tr("Normal is recommended. In sport mode, sunnypilot will provide aggressive acceleration for a dynamic driving experience. "
"In eco mode, sunnypilot will apply smoother and more relaxed acceleration. On supported cars, you can cycle through these "
"acceleration personality within Onroad Settings on the driving screen."),
"",
accel_personality_texts);
accel_personality_setting->showDescription();
// set up uiState update for personality setting
QObject::connect(uiState(), &UIState::uiUpdate, this, &TogglesPanel::updateState);
@@ -113,6 +157,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
// insert longitudinal personality after NDOG toggle
if (param == "DisengageOnAccelerator") {
addItem(long_personality_setting);
addItem(accel_personality_setting);
}
}
@@ -133,6 +178,13 @@ void TogglesPanel::updateState(const UIState &s) {
}
uiState()->scene.personality = personality;
}
if (sm.updated("longitudinalPlanSP")) {
auto accel_personality = sm["longitudinalPlanSP"].getLongitudinalPlanSP().getAccelPersonality();
if (accel_personality != s.scene.accel_personality && s.scene.started && isVisible()) {
accel_personality_setting->setCheckedButton(static_cast<int>(accel_personality));
}
uiState()->scene.accel_personality = accel_personality;
}
}
void TogglesPanel::expandToggleDescription(const QString &param) {
@@ -179,10 +231,12 @@ void TogglesPanel::updateToggles() {
experimental_mode_toggle->setEnabled(true);
experimental_mode_toggle->setDescription(e2e_description);
long_personality_setting->setEnabled(true);
accel_personality_setting->setEnabled(true);
} else {
// no long for now
experimental_mode_toggle->setEnabled(false);
long_personality_setting->setEnabled(false);
accel_personality_setting->setEnabled(true);
params.remove("ExperimentalMode");
const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.");
+1
View File
@@ -88,6 +88,7 @@ protected:
Params params;
std::map<std::string, ParamControl*> toggles;
ButtonParamControl *long_personality_setting;
ButtonParamControl *accel_personality_setting;
virtual void updateToggles();
};
+194 -2
View File
@@ -22,7 +22,7 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
update_model(model, lead_one);
drawLaneLines(painter);
drawPath(painter, model, surface_rect.height());
drawPath(painter, model, surface_rect.height(), surface_rect.width());
if (longitudinal_control && sm.alive("radarState")) {
update_leads(radar_state, model.getPosition());
@@ -92,7 +92,7 @@ void ModelRenderer::drawLaneLines(QPainter &painter) {
}
}
void ModelRenderer::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height) {
void ModelRenderer::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height, int width) {
QLinearGradient bg(0, height, 0, 0);
if (experimental_mode) {
// The first half of track_vertices are the points for the right side of the path
@@ -127,6 +127,9 @@ void ModelRenderer::drawPath(QPainter &painter, const cereal::ModelDataV2::Reade
painter.setBrush(bg);
painter.drawPolygon(track_vertices);
//LongFuel(painter,height, width);
//LateralFuel(painter, height, width);
}
void ModelRenderer::updatePathGradient(QLinearGradient &bg) {
@@ -173,6 +176,195 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float
(1 - t) * start.alphaF() + t * end.alphaF());
}
void ModelRenderer::drawGaugeBackground(QPainter &painter, qreal centerX, qreal centerY) {
const qreal backgroundSize = GAUGE_SIZE * BACKGROUND_SIZE_MULTIPLIER;
// Draw circular background
painter.setPen(Qt::NoPen);
painter.setBrush(BACKGROUND_COLOR);
painter.drawEllipse(QPointF(centerX, centerY), backgroundSize / 2, backgroundSize / 2);
// Draw border
QPen borderPen(BORDER_COLOR);
borderPen.setWidth(BORDER_PEN_WIDTH);
painter.setPen(borderPen);
painter.drawEllipse(QPointF(centerX, centerY), backgroundSize / 2 + 1, backgroundSize / 2 + 1);
// Draw background semicircle
QPen semicirclePen(GAUGE_BACKGROUND_COLOR);
semicirclePen.setWidth(GAUGE_PEN_WIDTH);
semicirclePen.setCapStyle(Qt::RoundCap);
painter.setPen(semicirclePen);
painter.drawArc(QRectF(centerX - GAUGE_SIZE / 2, centerY - GAUGE_SIZE / 2,
GAUGE_SIZE, GAUGE_SIZE), 0, SEMICIRCLE_SPAN);
}
QColor ModelRenderer::getIndicatorColor(float absoluteValue, float lowThreshold, float highThreshold) {
if (absoluteValue < lowThreshold) {
return LOW_INDICATOR_COLOR;
} else if (absoluteValue < highThreshold) {
return MODERATE_INDICATOR_COLOR;
} else {
return HIGH_INDICATOR_COLOR;
}
}
int ModelRenderer::calculateSpanAngle(float absoluteValue, float maxValue) {
const int spanAngle = static_cast<int>(QUARTER_CIRCLE_SPAN * (absoluteValue / maxValue));
return std::clamp(spanAngle, 0, QUARTER_CIRCLE_SPAN);
}
void ModelRenderer::drawGaugeArc(QPainter &painter, qreal centerX, qreal centerY,
float value, bool isPositive, const QString &label) {
const float absoluteValue = std::abs(value);
if (absoluteValue <= MIN_THRESHOLD) {
return; // Skip drawing if value is too small
}
// Set up the arc rectangle
const QRectF arcRect(centerX - GAUGE_SIZE / 2, centerY - GAUGE_SIZE / 2,
GAUGE_SIZE, GAUGE_SIZE);
// Configure pen for the indicator arc
QPen indicatorPen;
indicatorPen.setWidth(GAUGE_PEN_WIDTH);
indicatorPen.setCapStyle(Qt::RoundCap);
painter.setPen(indicatorPen);
// Draw the arc based on direction
const int spanAngle = calculateSpanAngle(absoluteValue, 1.0f); // Adjust max value as needed
if (isPositive) {
painter.drawArc(arcRect, STARTING_ANGLE, spanAngle);
} else {
painter.drawArc(arcRect, STARTING_ANGLE, -spanAngle);
}
// Draw center label
painter.setPen(Qt::white);
QFont font = painter.font();
font.setPixelSize(20);
font.setBold(true);
painter.setFont(font);
painter.drawText(QRectF(centerX - 50, centerY + 10, 100, 20), Qt::AlignCenter, label);
}
void ModelRenderer::LongFuel(QPainter &painter, int height, int width) {
const qreal rectWidth = static_cast<qreal>(width);
const qreal rectHeight = static_cast<qreal>(height);
UIState *s = uiState();
if (!s || !s->sm) {
return; // Safety check
}
// Get current acceleration
const float currentAcceleration = (*s->sm)["carControl"].getCarControl().getActuators().getAccel();
const float absoluteAcceleration = std::abs(currentAcceleration);
// Calculate gauge position
const qreal centerX = rectWidth / 17;
const qreal centerY = rectHeight / 2 + 120;
// Draw gauge background
drawGaugeBackground(painter, centerX, centerY);
// Skip drawing arc if acceleration is too small
if (absoluteAcceleration <= MIN_THRESHOLD) {
drawGaugeArc(painter, centerX, centerY, 0.0f, true, "LONG");
return;
}
// Determine indicator color based on acceleration magnitude
const QColor indicatorColor = getIndicatorColor(absoluteAcceleration, 0.3f, 0.6f);
// Calculate span angle (scale for better visibility)
const int spanAngle = static_cast<int>(QUARTER_CIRCLE_SPAN * absoluteAcceleration);
const int clampedSpanAngle = std::clamp(spanAngle, 0, QUARTER_CIRCLE_SPAN);
// Draw the acceleration arc
QPen indicatorPen(indicatorColor);
indicatorPen.setWidth(GAUGE_PEN_WIDTH);
indicatorPen.setCapStyle(Qt::RoundCap);
painter.setPen(indicatorPen);
const QRectF arcRect(centerX - GAUGE_SIZE / 2, centerY - GAUGE_SIZE / 2,
GAUGE_SIZE, GAUGE_SIZE);
// Draw arc based on acceleration direction
if (currentAcceleration > 0) {
painter.drawArc(arcRect, STARTING_ANGLE, -clampedSpanAngle); // Left side for positive
} else {
painter.drawArc(arcRect, STARTING_ANGLE, clampedSpanAngle); // Right side for negative
}
// Draw center label
painter.setPen(Qt::white);
QFont font = painter.font();
font.setPixelSize(20);
font.setBold(true);
painter.setFont(font);
painter.drawText(QRectF(centerX - 50, centerY + 10, 100, 20), Qt::AlignCenter, "LONG");
}
void ModelRenderer::LateralFuel(QPainter &painter, int height, int width) {
const qreal rectWidth = static_cast<qreal>(width);
const qreal rectHeight = static_cast<qreal>(height);
UIState *s = uiState();
if (!s || !s->sm) {
return; // Safety check
}
// Get current steering angle
const float currentLateral = (*s->sm)["carState"].getCarState().getSteeringAngleDeg();
const float absoluteLateral = std::abs(currentLateral);
// Calculate gauge position
const qreal centerX = rectWidth / 17;
const qreal centerY = rectHeight / 2 - 120;
// Draw gauge background
drawGaugeBackground(painter, centerX, centerY);
// Skip drawing arc if lateral force is too small
if (absoluteLateral <= 0.1f) {
drawGaugeArc(painter, centerX, centerY, 0.0f, true, "LAT");
return;
}
// Determine indicator color based on lateral force magnitude
const QColor indicatorColor = getIndicatorColor(absoluteLateral, 5.0f, 15.0f);
// Calculate span angle (normalized to max expected steering angle)
const float maxSteeringAngle = 15.0f; // Adjust based on your vehicle's characteristics
const int spanAngle = static_cast<int>(QUARTER_CIRCLE_SPAN * (absoluteLateral / maxSteeringAngle));
const int clampedSpanAngle = std::clamp(spanAngle, 0, QUARTER_CIRCLE_SPAN);
// Draw the lateral arc
QPen indicatorPen(indicatorColor);
indicatorPen.setWidth(GAUGE_PEN_WIDTH);
indicatorPen.setCapStyle(Qt::RoundCap);
painter.setPen(indicatorPen);
const QRectF arcRect(centerX - GAUGE_SIZE / 2, centerY - GAUGE_SIZE / 2,
GAUGE_SIZE, GAUGE_SIZE);
// Draw arc based on steering direction
if (currentLateral < 0) {
painter.drawArc(arcRect, STARTING_ANGLE, -clampedSpanAngle); // Left turn
} else {
painter.drawArc(arcRect, STARTING_ANGLE, clampedSpanAngle); // Right turn
}
// Draw center label
painter.setPen(Qt::white);
QFont font = painter.font();
font.setPixelSize(20);
font.setBold(true);
painter.setFont(font);
painter.drawText(QRectF(centerX - 50, centerY + 10, 100, 20), Qt::AlignCenter, "LAT");
}
void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &vd, const QRect &surface_rect) {
const float speedBuff = 10.;
+30 -1
View File
@@ -29,6 +29,8 @@ public:
ModelRenderer() {}
void setTransform(const Eigen::Matrix3f &transform) { car_space_transform = transform; }
void draw(QPainter &painter, const QRect &surface_rect);
void LongFuel(QPainter &p, int height, int width);
void LateralFuel(QPainter &p, int height, int width);
protected:
bool mapToScreen(float in_x, float in_y, float in_z, QPointF *out);
@@ -38,7 +40,16 @@ protected:
void update_leads(const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line);
virtual void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead);
void drawLaneLines(QPainter &painter);
void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height);
void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height, int width);
// Gauge helper methods
void drawGaugeBackground(QPainter &painter, qreal centerX, qreal centerY);
void drawGaugeArc(QPainter &painter, qreal centerX, qreal centerY,
float value, bool isPositive, const QString &label);
QColor getIndicatorColor(float absoluteValue, float lowThreshold, float highThreshold);
int calculateSpanAngle(float absoluteValue, float maxValue);
void updatePathGradient(QLinearGradient &bg);
QColor blendColors(const QColor &start, const QColor &end, float t);
@@ -55,4 +66,22 @@ protected:
QPointF lead_vertices[2] = {};
Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero();
QRectF clip_region;
// Gauge configuration constants
static constexpr qreal GAUGE_SIZE = 140.0;
static constexpr qreal BACKGROUND_SIZE_MULTIPLIER = 1.4;
static constexpr qreal GAUGE_PEN_WIDTH = 30.0;
static constexpr qreal BORDER_PEN_WIDTH = 2.0;
static constexpr int SEMICIRCLE_SPAN = 180 * 16;
static constexpr int QUARTER_CIRCLE_SPAN = 90 * 16;
static constexpr int STARTING_ANGLE = 90 * 16;
static constexpr qreal MIN_THRESHOLD = 0.01;
// Color constants - Note: QColor cannot be constexpr, use inline static const instead
inline static const QColor BACKGROUND_COLOR = QColor(0, 0, 0, 80);
inline static const QColor BORDER_COLOR = QColor(0, 0, 0, 100);
inline static const QColor GAUGE_BACKGROUND_COLOR = QColor(50, 50, 50);
inline static const QColor LOW_INDICATOR_COLOR = QColor(23, 241, 66, 200);
inline static const QColor MODERATE_INDICATOR_COLOR = QColor(255, 166, 0, 200);
inline static const QColor HIGH_INDICATOR_COLOR = QColor(245, 0, 0, 200);
};
+1
View File
@@ -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",
@@ -33,6 +33,12 @@ LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) {
tr("Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering."),
"../assets/offroad/icon_blank.png",
},
{
"RoadEdgeLaneChangeEnabled",
tr("Block Lane Change: Road Edge Detection"),
tr("Enable this toggle to block lane change when road edge is detected on the stalk actuated side."),
"../assets/offroad/icon_blank.png",
}
};
// Controls: Auto Lane Change Timer
@@ -75,6 +75,22 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
QObject::connect(uiState(), &UIState::offroadTransition, this, &LongitudinalPanel::refresh);
// Acceleration Personality
AccelPersonalityControl = new ParamControlSP("AccelPersonalityEnabled",
tr("Acceleration Personality"),
tr("Controls acceleration behavior: Eco (efficient), Normal (balanced), Sport (responsive). "
"Adjust how aggressively the vehicle accelerates while maintaining smooth operation."),
"../assets/offroad/icon_shell.png");
list->addItem(AccelPersonalityControl);
// Dynamic Personality
DynamicPersonalityControl = new ParamControlSP("DynamicFollow",
tr("Following Distance Personality"),
tr("Controls following distance and braking behavior: Relaxed (longer distance, gentler braking), Standard (balanced), Aggressive (shorter distance, firmer braking). "
"Fine-tune your comfort level in traffic situations."),
"../assets/offroad/icon_shell.png");
list->addItem(DynamicPersonalityControl);
speedLimitSettings = new PushButtonSP(tr("Speed Limit"), 750, this);
connect(speedLimitSettings, &QPushButton::clicked, [&]() {
cruisePanelScroller->setLastScrollPosition();
@@ -164,6 +180,10 @@ void LongitudinalPanel::refresh(bool _offroad) {
dynamicExperimentalControl->refresh();
SmartCruiseControlVision->refresh();
SmartCruiseControlMap->refresh();
AccelPersonalityControl->setEnabled(true);
DynamicPersonalityControl->setEnabled(true);
AccelPersonalityControl->refresh();
DynamicPersonalityControl->refresh();
} else {
has_longitudinal_control = false;
is_pcm_cruise = false;
@@ -36,6 +36,9 @@ private:
ParamControl *SmartCruiseControlMap;
ParamControl *intelligentCruiseButtonManagement = nullptr;
ParamControl *dynamicExperimentalControl = nullptr;
ParamControlSP *AccelPersonalityControl;
ParamControlSP *DynamicPersonalityControl;
SpeedLimitSettings *speedLimitScreen;
PushButtonSP *speedLimitSettings;
};
@@ -0,0 +1,79 @@
/**
* 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();
}
@@ -0,0 +1,38 @@
/**
* 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,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"),
+409 -63
View File
@@ -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"
@@ -43,8 +44,6 @@ void HudRendererSP::updateState(const UIState &s) {
const auto car_params = sm["carParams"].getCarParams();
const auto car_params_sp = sm["carParamsSP"].getCarParamsSP();
const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP();
const auto lmd = sm["liveMapDataSP"].getLiveMapDataSP();
if (sm.updated("carParams")) {
steerControlType = car_params.getSteerControlType();
}
@@ -53,37 +52,80 @@ void HudRendererSP::updateState(const UIState &s) {
pcmCruiseSpeed = car_params_sp.getPcmCruiseSpeed();
}
if (sm.updated("longitudinalPlanSP")) {
speedLimit = lp_sp.getSpeedLimit().getResolver().getSpeedLimit() * speedConv;
speedLimitLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLast() * speedConv;
speedLimitOffset = lp_sp.getSpeedLimit().getResolver().getSpeedLimitOffset() * speedConv;
speedLimitValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitValid();
speedLimitLastValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLastValid();
speedLimitFinalLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitFinalLast() * speedConv;
speedLimitSource = lp_sp.getSpeedLimit().getResolver().getSource();
speedLimitAssistState = lp_sp.getSpeedLimit().getAssist().getState();
speedLimitAssistActive = lp_sp.getSpeedLimit().getAssist().getActive();
smartCruiseControlVisionEnabled = lp_sp.getSmartCruiseControl().getVision().getEnabled();
smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive();
smartCruiseControlMapEnabled = lp_sp.getSmartCruiseControl().getMap().getEnabled();
smartCruiseControlMapActive = lp_sp.getSmartCruiseControl().getMap().getActive();
}
if (sm.alive("mapdOut") && sm.rcv_frame("mapdOut") > 0) {
const auto mapd = sm["mapdOut"].getMapdOut();
// Road name can come from wayName, wayRef, or roadName
wayName = QString::fromStdString(mapd.getWayName());
wayRef = QString::fromStdString(mapd.getWayRef());
QString mapdRoadName = QString::fromStdString(mapd.getRoadName());
if (!mapdRoadName.isEmpty()) {
roadNameStr = mapdRoadName;
} else if (!wayRef.isEmpty() && !wayName.isEmpty()) {
roadNameStr = wayRef + " - " + wayName;
} else if (!wayName.isEmpty()) {
roadNameStr = wayName;
} else if (!wayRef.isEmpty()) {
roadNameStr = wayRef;
} else {
roadNameStr = "";
}
greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert();
leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert();
if (sm.updated("liveMapDataSP")) {
roadNameStr = QString::fromStdString(lmd.getRoadName());
speedLimitAheadValid = lmd.getSpeedLimitAheadValid();
speedLimitAhead = lmd.getSpeedLimitAhead() * speedConv;
speedLimitAheadDistance = lmd.getSpeedLimitAheadDistance();
tileLoaded = mapd.getTileLoaded();
float mapdSpeedLimitRaw = mapd.getSpeedLimit();
float mapdOffsetRaw = mapd.getSpeedLimitOffset();
mapdSpeedLimit = mapdSpeedLimitRaw * speedConv;
speedLimit = mapdSpeedLimit;
speedLimitLast = mapdSpeedLimit;
speedLimitOffset = mapdOffsetRaw * speedConv;
speedLimitValid = tileLoaded && mapdSpeedLimitRaw > 0;
speedLimitLastValid = speedLimitValid;
speedLimitFinalLast = mapdSpeedLimit + speedLimitOffset;
if (tileLoaded) {
speedLimitSource = 1; // MAP
} else {
speedLimitSource = 0; // NONE
}
float nextSpeedLimitRaw = mapd.getNextSpeedLimit();
speedLimitAheadValid = nextSpeedLimitRaw > 0 && tileLoaded;
speedLimitAhead = nextSpeedLimitRaw * speedConv;
speedLimitAheadDistance = mapd.getNextSpeedLimitDistance();
if (speedLimitAheadDistance < speedLimitAheadDistancePrev && speedLimitAheadValidFrame < SPEED_LIMIT_AHEAD_VALID_FRAME_THRESHOLD) {
speedLimitAheadValidFrame++;
} else if (speedLimitAheadDistance > speedLimitAheadDistancePrev && speedLimitAheadValidFrame > 0) {
speedLimitAheadValidFrame--;
}
// SCC data from mapd
suggestedSpeed = mapd.getSuggestedSpeed() * speedConv;
visionCurveSpeed = mapd.getVisionCurveSpeed() * speedConv;
curveSpeed = mapd.getCurveSpeed() * speedConv;
smartCruiseControlVisionEnabled = visionCurveSpeed > 0;
smartCruiseControlVisionActive = visionCurveSpeed > 0 && visionCurveSpeed < speedLimit;
smartCruiseControlMapEnabled = curveSpeed > 0;
smartCruiseControlMapActive = curveSpeed > 0 && curveSpeed < speedLimit;
advisorySpeed = mapd.getAdvisorySpeed() * speedConv;
nextAdvisorySpeed = mapd.getNextAdvisorySpeed() * speedConv;
nextAdvisorySpeedDistance = mapd.getNextAdvisorySpeedDistance();
}
speedLimitAheadDistancePrev = speedLimitAheadDistance;
speedLimitAssistState = 0;
speedLimitAssistActive = false;
static int reverse_delay = 0;
bool reverse_allowed = false;
if (car_state.getGearShifter() != cereal::CarState::GearShifter::REVERSE) {
@@ -107,7 +149,7 @@ void HudRendererSP::updateState(const UIState &s) {
}
if (sm.updated(gps_source)) {
gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not.
gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0;
altitude = gpsLocation.getAltitude();
bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg();
bearingDeg = gpsLocation.getBearingDeg();
@@ -154,6 +196,63 @@ 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) {
@@ -224,7 +323,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
const int sign_height = 204;
QRect sign_rect(sign_x, sign_y, sign_width, sign_height);
if (speedLimitAssistState == cereal::LongitudinalPlanSP::SpeedLimit::AssistState::PRE_ACTIVE) {
if (speedLimitAssistState == 1) {
speedLimitAssistFrame++;
showSpeedLimit = speed_limit_assist_pre_active_pulse;
drawSpeedLimitPreActiveArrow(p, sign_rect);
@@ -237,7 +336,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
drawSpeedLimitSigns(p, sign_rect);
// do not show during SLA's preActive state
if (speedLimitAssistState != cereal::LongitudinalPlanSP::SpeedLimit::AssistState::PRE_ACTIVE) {
if (speedLimitAssistState != 1) {
drawUpcomingSpeedLimit(p);
}
}
@@ -287,6 +386,8 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
}
}
drawNavigationHUD(p, surface_rect);
p.restore();
}
@@ -306,9 +407,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 +425,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);
@@ -570,7 +676,7 @@ void HudRendererSP::drawSpeedLimitSigns(QPainter &p, QRect &sign_rect) {
void HudRendererSP::drawUpcomingSpeedLimit(QPainter &p) {
bool speed_limit_ahead = speedLimitAheadValid && speedLimitAhead > 0 && speedLimitAhead != speedLimit && speedLimitAheadValidFrame > 0 &&
speedLimitSource == cereal::LongitudinalPlanSP::SpeedLimit::Source::MAP;
tileLoaded;
if (!speed_limit_ahead) {
return;
}
@@ -684,15 +790,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 +820,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 +934,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 +995,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 +1053,181 @@ 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();
}
+24 -2
View File
@@ -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;
@@ -83,7 +85,7 @@ private:
bool speedLimitValid;
bool speedLimitLastValid;
float speedLimitFinalLast;
cereal::LongitudinalPlanSP::SpeedLimit::Source speedLimitSource;
int speedLimitSource; // 0=NONE, 1=MAP
bool speedLimitAheadValid;
float speedLimitAhead;
float speedLimitAheadDistance;
@@ -92,7 +94,7 @@ private:
SpeedLimitMode speedLimitMode = SpeedLimitMode::OFF;
bool roadName;
QString roadNameStr;
cereal::LongitudinalPlanSP::SpeedLimit::AssistState speedLimitAssistState;
int speedLimitAssistState; // 0=NONE, 1=PRE_ACTIVE, etc.
bool speedLimitAssistActive;
int speedLimitAssistFrame;
QPixmap plus_arrow_up_img;
@@ -120,4 +122,24 @@ 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;
QString wayName;
QString wayRef;
float mapdSpeedLimit;
float advisorySpeed;
float nextAdvisorySpeed;
float nextAdvisorySpeedDistance;
float suggestedSpeed;
float visionCurveSpeed;
float curveSpeed;
bool tileLoaded;
};
+1 -1
View File
@@ -48,7 +48,7 @@ void ModelRendererSP::draw(QPainter &painter, const QRect &surface_rect) {
if (s->scene.rainbow_mode) {
drawRainbowPath(painter, surface_rect);
} else {
ModelRenderer::drawPath(painter, model, surface_rect.height());
ModelRenderer::drawPath(painter, model, surface_rect.height(), surface_rect.width());
}
if (longitudinal_control && sm.alive("radarState")) {
+2 -1
View File
@@ -29,7 +29,8 @@ 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",
"mapdOut"
});
// update timer
+1
View File
@@ -60,6 +60,7 @@ typedef struct UIScene {
cereal::PandaState::PandaType pandaType;
cereal::LongitudinalPersonality personality;
cereal::LongitudinalPlanSP::AccelerationPersonality accel_personality;
float light_sensor = -1;
bool started, ignition, is_metric, recording_audio;
+6 -1
View File
@@ -28,6 +28,7 @@ from openpilot.sunnypilot.modeld.constants import ModelConstants, Plan
from openpilot.sunnypilot.models.helpers import get_active_bundle, get_model_path, load_metadata, prepare_inputs, load_meta_constants
from openpilot.sunnypilot.modeld.models.commonmodel_pyx import ModelFrame, CLContext
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
PROCESS_NAME = "selfdrive.modeld.modeld_snpe"
@@ -209,6 +210,7 @@ def main(demo=False):
prev_action = log.ModelDataV2.Action()
DH = DesireHelper()
RELC = RoadEdgeLaneChangeController(params.get_bool("RoadEdgeLaneChangeEnabled"))
while True:
# Keep receiving frames until we are at least 1 frame ahead of previous extra frame
@@ -314,7 +316,10 @@ def main(demo=False):
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
r_lane_change_prob = desire_state[log.Desire.laneChangeRight]
lane_change_prob = l_lane_change_prob + r_lane_change_prob
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
RELC.update(modelv2_send.modelV2.roadEdgeStds, modelv2_send.modelV2.laneLineProbs)
mdv2sp_send.modelDataV2SP.leftLaneChangeEdgeBlock = RELC.left_edge_detected
mdv2sp_send.modelDataV2SP.rightLaneChangeEdgeBlock = RELC.right_edge_detected
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob, RELC.left_edge_detected, RELC.right_edge_detected)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
+8 -1
View File
@@ -26,6 +26,7 @@ from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
from openpilot.sunnypilot.models.helpers import get_active_bundle
from openpilot.sunnypilot.models.runners.helpers import get_model_runner
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
PROCESS_NAME = "selfdrive.modeld.modeld_tinygrad"
@@ -239,6 +240,9 @@ def main(demo=False):
prev_action = log.ModelDataV2.Action()
DH = DesireHelper()
RELC = RoadEdgeLaneChangeController(params.get_bool("RoadEdgeLaneChangeEnabled"))
while True:
# Keep receiving frames until we are at least 1 frame ahead of previous extra frame
@@ -340,7 +344,10 @@ def main(demo=False):
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
r_lane_change_prob = desire_state[log.Desire.laneChangeRight]
lane_change_prob = l_lane_change_prob + r_lane_change_prob
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
RELC.update(modelv2_send.modelV2.roadEdgeStds, modelv2_send.modelV2.laneLineProbs)
mdv2sp_send.modelDataV2SP.leftLaneChangeEdgeBlock = RELC.left_edge_detected
mdv2sp_send.modelDataV2SP.rightLaneChangeEdgeBlock = RELC.right_edge_detected
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob, RELC.left_edge_detected, RELC.right_edge_detected)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
+1 -1
View File
@@ -116,7 +116,7 @@ class ModelCache:
class ModelFetcher:
"""Handles fetching and caching of model data from remote source"""
MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v8.json"
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-docs/refs/heads/gh-pages/docs/driving_models_v9.json"
def __init__(self, params: Params):
self.params = params
+2 -2
View File
@@ -19,8 +19,8 @@ from openpilot.system.hardware.hw import Paths
from pathlib import Path
# see the README.md for more details on the model selector versioning
CURRENT_SELECTOR_VERSION = 10
REQUIRED_MIN_SELECTOR_VERSION = 9
CURRENT_SELECTOR_VERSION = 11
REQUIRED_MIN_SELECTOR_VERSION = 11
USE_ONNX = os.getenv('USE_ONNX', PC)
+2 -5
View File
@@ -14,13 +14,10 @@ CUSTOM_MODEL_PATH = Paths.model_root()
# Set QCOM environment variable for TICI devices, potentially enabling hardware acceleration
USBGPU = "USBGPU" in os.environ
if USBGPU:
os.environ['AMD'] = '1'
os.environ['DEV'] = 'AMD'
os.environ['AMD_IFACE'] = 'USB'
elif TICI:
os.environ['QCOM'] = '1'
else:
os.environ['LLVM'] = '1'
os.environ['JIT'] = '2' # TODO: This may cause issues
os.environ['DEV'] = 'QCOM' if TICI else 'CPU'
class ModelData:
+6
View 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).
View File
+16
View 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
+2
View File
@@ -126,6 +126,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,0 +1,44 @@
"""
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
@@ -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(), 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
@@ -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,
}
@@ -0,0 +1,138 @@
"""
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
@@ -0,0 +1,94 @@
"""
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
@@ -0,0 +1,160 @@
"""
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
@@ -0,0 +1,76 @@
"""
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
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ae17f4c1896c88a6d520f1acdca99ab23d4ce1140a500b64bea3185041f8ecd9
size 1246
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:86a2eaf6070d6b93df385945bd7f4a6e9bb78b7cc42eb5b7ef9a9b6c075c80ce
size 1581
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:398f30c89e396bb4bdbac1ba6dfe292f6e3278923e14113e705f4d0591671f32
size 1528
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ae17f4c1896c88a6d520f1acdca99ab23d4ce1140a500b64bea3185041f8ecd9
size 1246
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0e23cac9bec0de20f9632d19618be9b05eedb010c6e23b0ba7a8e3cc8225f64e
size 865
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:008cf037b8713c710eacd1129973119faa2527ab1c19aee5340a16dc0659df8a
size 1063
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:579d6a27e062779d41beb9320abf5797bc8ec81de1f355a4197c3b3ee0766f28
size 1400
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4010c8122af918dbdb50e88629adbf79f7733f022bba263e6f44f6ffae3e7a5
size 1371
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:89041889817454869d98adfb5a16fae33596c038d5484ec734fe3a5cc532c45c
size 1334
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e691dd43947c9430fcd73e6bede94aa5fd8995e9303eba53cc0ac589a7c9f106
size 1347
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:008cf037b8713c710eacd1129973119faa2527ab1c19aee5340a16dc0659df8a
size 1063
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e02ac2720f12f9373ae2d86d91fffb6203e18aba04b493bd6f41d2953a11cbc9
size 1780
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8f83214c1fa6aba33ca3e1fe29db09733a275efe19864866b9edecbb59abb171
size 1234
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:459fe6410e061542876fa7de93efaf901d15937f891e801e1b93f8270e750130
size 1613
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e3f5fb80d62c92876a5e490917f549e2a443dfc683a6787506747bbbeb75d33b
size 1538
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8f83214c1fa6aba33ca3e1fe29db09733a275efe19864866b9edecbb59abb171
size 1234
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b9c86ddd75ea1ed2c49c1bdc2e7f98ed15d12226938073688dda1b0bbdf62308
size 1439
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cc7248e62d11cf36f9a13d408a0954a51ff0eee9b8e03d8910a2eb2cb6a09ffd
size 1456
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8509bd5dbf96e44f73421dc09ca8f7c1dddf2dc21f3b4b15650472b3e06b3470
size 658
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5198a3fdb610001358501ca76ab52fe0979b9254ed85cbbb2ce1fab1cb77929a
size 2015
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:752424da0ad8430c619045455a86c640b576f8bbe6d3d8b5b6a599b1c61ef489
size 1863
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b6590df8da1f30ec61b3bdf729fda1956d340c5abab448ea0062105500cc5da5
size 1841
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b4fbd63acf8f5e7f734b332a96e9ad138ad22ee07000ad425712e71b77ce0728
size 1800
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a7a7f5bcab30ce961ff7e345c460cfbd133e45c66da1fe1f0e7bc7c826b05f30
size 1805
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:676cbe95f9a663816dd77c55dbcf2c346bfae2454be7832914b5527219aba91f
size 2473
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:008cf037b8713c710eacd1129973119faa2527ab1c19aee5340a16dc0659df8a
size 1063
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:579d6a27e062779d41beb9320abf5797bc8ec81de1f355a4197c3b3ee0766f28
size 1400
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4010c8122af918dbdb50e88629adbf79f7733f022bba263e6f44f6ffae3e7a5
size 1371
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:89041889817454869d98adfb5a16fae33596c038d5484ec734fe3a5cc532c45c
size 1334
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e691dd43947c9430fcd73e6bede94aa5fd8995e9303eba53cc0ac589a7c9f106
size 1347
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:008cf037b8713c710eacd1129973119faa2527ab1c19aee5340a16dc0659df8a
size 1063
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e02ac2720f12f9373ae2d86d91fffb6203e18aba04b493bd6f41d2953a11cbc9
size 1780
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:08245891301f814fc0245133e39df3fb14720025cc3e4bc7131f953ead47edc8
size 1535
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8c629c7273fe2613538295aba807edfddbbae86ae77d6f9b0b60b0486e3f7ca9
size 1484
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:72c1ef39486f3dc3f7a0a97777bce16c7c369cc85bf6c82629d6780803804967
size 1765
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8b8bfd8019320127ed0fe03d4a1bddab57d5c2345b6cdeab66558ea9b53c0266
size 1699
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d10bdde82e7896de072de28da46412d5e2f6c902fd5643c6b48a8fc79ed4232f
size 113
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:579d6a27e062779d41beb9320abf5797bc8ec81de1f355a4197c3b3ee0766f28
size 1400
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4010c8122af918dbdb50e88629adbf79f7733f022bba263e6f44f6ffae3e7a5
size 1371
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0457b0597386d0b7d3cc06d961df5fb34211ee575da7fab2f81e424d9954bb42
size 1549
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f6e1556f0b888afb6856f8cf3e16632959190f88dd66c4d9db4832a8269cbc5
size 1552
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:89041889817454869d98adfb5a16fae33596c038d5484ec734fe3a5cc532c45c
size 1334
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e691dd43947c9430fcd73e6bede94aa5fd8995e9303eba53cc0ac589a7c9f106
size 1347

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