Compare commits

...

33 Commits

Author SHA1 Message Date
James Vecellio-Grant 9c3a73b4cf Merge branch 'navigationd-service' into nav-desires 2025-11-16 15:42:20 -08:00
James Vecellio-Grant 8adbd56acd Merge branch 'navigationd-service' into nav-desires 2025-11-16 15:25:42 -08:00
James Vecellio-Grant d106c192f2 Merge branch 'navigationd-service' into nav-desires 2025-11-10 18:49:22 -08:00
discountchubbs 3af0d6e87f more 2025-11-10 18:44:26 -08:00
discountchubbs 584269fced Revert "more"
This reverts commit b69da9e5ea.
2025-11-10 18:43:50 -08:00
James Vecellio-Grant 1dc5741e75 Merge branch 'navigationd-service' into nav-desires 2025-11-10 18:41:36 -08:00
James Vecellio-Grant 48dc9dbb69 Merge branch 'navigationd-service' into nav-desires 2025-11-10 12:41:03 -08:00
discountchubbs 799e819e58 more desire changes 2025-11-10 11:57:55 -08:00
James Vecellio-Grant aaac1c79d0 Merge branch 'navigationd-service' into nav-desires 2025-11-10 11:37:15 -08:00
James Vecellio-Grant d7fa10a827 Merge branch 'navigationd-service' into nav-desires 2025-11-10 08:58:01 -08:00
James Vecellio-Grant ba176a6581 Merge branch 'navigationd-service' into nav-desires 2025-11-07 07:45:21 -08:00
discountchubbs 864c811ef6 small clean 2025-11-05 19:05:29 -08:00
James Vecellio-Grant 906e9d7a80 Merge branch 'navigationd-service' into nav-desires 2025-11-05 18:51:41 -08:00
James Vecellio-Grant b6dd2d14db Merge branch 'navigationd-service' into nav-desires 2025-11-01 08:02:18 -07:00
discountchubbs d17e80ad94 Merge remote-tracking branch 'origin/navigationd-service' into nav-desires 2025-10-29 19:41:29 -07:00
discountchubbs 18cd3633e5 Merge remote-tracking branch 'origin/navigationd-service' into nav-desires 2025-10-29 19:37:41 -07:00
discountchubbs 5f5e3668eb Add steering pressed and torque to desire for non blindspot cars, and a note! 2025-10-28 06:43:00 -07:00
discountchubbs 8c07958f6f Merge remote-tracking branch 'origin/navigationd-service' into nav-desires 2025-10-28 06:28:33 -07:00
discountchubbs 29f15dc8ed Merge remote-tracking branch 'origin/navigationd-service' into nav-desires 2025-10-26 11:07:16 -07:00
discountchubbs 2a4b348497 Merge remote-tracking branch 'origin/navigationd-service' into nav-desires 2025-10-26 08:44:38 -07:00
discountchubbs ff4cc96a81 Merge remote-tracking branch 'origin/navigationd-service' into nav-desires 2025-10-23 19:21:04 -07:00
discountchubbs 9fbef36c6b desire handling 2025-10-23 19:12:34 -07:00
discountchubbs 7b28c2f59a Merge remote-tracking branch 'origin/navigationd-service' into nav-desires 2025-10-23 17:24:16 -07:00
discountchubbs b763f7aac1 Merge remote-tracking branch 'origin/navigationd-service' into nav-desires 2025-10-21 16:27:02 -07:00
James Vecellio bd269defb3 oopsie 2025-10-21 16:19:49 -07:00
James Vecellio-Grant 8423ecedb1 Merge branch 'master' into nav-desires 2025-10-21 15:29:39 -07:00
discountchubbs dd1479ed82 More macOS crap 🤠 2025-10-21 15:25:15 -07:00
discountchubbs f82845ff42 Merge remote-tracking branch 'origin/navigationd-service' into nav-desires 2025-10-21 12:05:13 -07:00
discountchubbs 091bce4a3a Merge remote-tracking branch 'origin/navigationd-service' into nav-desires 2025-10-21 05:54:42 -07:00
discountchubbs f17b0f200c non blocking polling
SOME attribute protection. kids crying, so need to stop for now!
2025-10-19 17:20:13 -07:00
discountchubbs ad9bde8b1f non blocking polling
SOME attribute protection. kids crying, so need to stop for now!
2025-10-19 17:17:37 -07:00
discountchubbs 8cf9f9fe23 params!
maybe these should be protected attributes, but thats a tomorrow problem
2025-10-19 17:03:04 -07:00
discountchubbs 713985d823 feat: navigationd desire loop
Notes: Maybe I should add a param for this, so its not automatic when a route is set.
2025-10-19 09:38:29 -07:00
8 changed files with 169 additions and 3 deletions
+1
View File
@@ -195,6 +195,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"MapboxSettings", {CLEAR_ON_MANAGER_START, JSON}},
{"MapboxRoute", {PERSISTENT, STRING}},
{"MapboxRecompute", {PERSISTENT | BACKUP, BOOL, "0"}},
{"NavDesiresAllowed", {PERSISTENT | BACKUP, BOOL, "0"}},
// Neural Network Lateral Control
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
+6
View File
@@ -3,6 +3,7 @@ from openpilot.common.constants import CV
from openpilot.common.realtime import DT_MDL
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode
from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController
from openpilot.sunnypilot.navd.navigation_desires.navigation_desires import NavigationDesires
LaneChangeState = log.LaneChangeState
LaneChangeDirection = log.LaneChangeDirection
@@ -51,6 +52,7 @@ class DesireHelper:
self.alc = AutoLaneChangeController(self)
self.lane_turn_controller = LaneTurnController(self)
self.lane_turn_direction = TurnDirection.none
self.navigation_desires = NavigationDesires()
@staticmethod
def get_lane_change_direction(CS):
@@ -143,3 +145,7 @@ class DesireHelper:
self.desire = log.Desire.none
self.alc.update_state()
nav_desire = self.navigation_desires.update(carstate, lateral_active)
if nav_desire != log.Desire.none and (self.desire == log.Desire.none or self.desire in (log.Desire.turnLeft, log.Desire.turnRight)):
self.desire = nav_desire
@@ -0,0 +1,42 @@
"""
Copyright (c) 2021-, James Vecellio, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import cereal.messaging as messaging
from cereal import car, log
from openpilot.common.constants import CV
from openpilot.common.params import Params
class NavigationDesires:
def __init__(self):
self.sm = messaging.SubMaster(['navigationd'])
self.desire = log.Desire.none
self._turn_speed_limit = 20 * CV.MPH_TO_MS
self._params = Params()
self.param_counter = -1
self.nav_allowed: bool = False
def update_params(self):
self.param_counter += 1
if self.param_counter % 60 == 0: # every 3 seconds at 20hz
self.nav_allowed = self._params.get("NavDesiresAllowed", return_default=True)
def update(self, CS: car.CarState, lateral_active: bool) -> log.Desire:
self.update_params()
self.sm.update(0)
nav_msg = self.sm['navigationd']
self.desire = log.Desire.none
if self.nav_allowed and nav_msg.valid and lateral_active:
upcoming = nav_msg.upcomingTurn
if upcoming == 'slightLeft' and not CS.rightBlinker and not CS.leftBlindspot and CS.steeringPressed and CS.steeringTorque > 0:
self.desire = log.Desire.keepLeft
elif upcoming == 'slightRight' and not CS.leftBlinker and not CS.rightBlindspot and CS.steeringPressed and CS.steeringTorque < 0:
self.desire = log.Desire.keepRight
elif upcoming == 'left' and not CS.rightBlinker and not CS.leftBlindspot and CS.vEgo < self._turn_speed_limit:
self.desire = log.Desire.turnLeft
elif upcoming == 'right' and not CS.leftBlinker and not CS.rightBlindspot and CS.vEgo < self._turn_speed_limit:
self.desire = log.Desire.turnRight
return self.desire
@@ -0,0 +1,96 @@
"""
Copyright (c) 2021-, James Vecellio, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pytest
import types
from cereal import log
from openpilot.common.params import Params
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
from openpilot.sunnypilot.navd.navigation_desires.navigation_desires import NavigationDesires
def make_car(vEgo=0, leftBlinker=False, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, steeringPressed=False, steeringTorque=0):
return types.SimpleNamespace(
vEgo=vEgo, leftBlinker=leftBlinker, rightBlinker=rightBlinker,
leftBlindspot=leftBlindspot, rightBlindspot=rightBlindspot,
steeringPressed=steeringPressed, steeringTorque=steeringTorque
)
NAVIGATION_PARAMS: list[tuple] = [
('slightLeft', make_car(steeringPressed=True, steeringTorque=1), log.Desire.keepLeft),
('slightRight', make_car(steeringPressed=True, steeringTorque=-1), log.Desire.keepRight),
('slightLeft', make_car(vEgo=9, leftBlindspot=True), log.Desire.none),
('slightRight', make_car(vEgo=9, rightBlindspot=True), log.Desire.none),
('left', make_car(vEgo=5, leftBlinker=True, rightBlinker=False, leftBlindspot=False), log.Desire.turnLeft),
('left', make_car(vEgo=5, leftBlinker=False, rightBlinker=True), log.Desire.none),
('right', make_car(vEgo=6, rightBlinker=True, leftBlindspot=False), log.Desire.turnRight),
('right', make_car(vEgo=6, rightBlinker=True, rightBlindspot=True), log.Desire.none),
('left', make_car(vEgo=9, leftBlinker=True), log.Desire.none),
]
INTEGRATION_PARAMS: list[tuple] = [(carstate, upcoming, log.Desire.none, expected) for upcoming, carstate, expected in NAVIGATION_PARAMS] + [
(make_car(vEgo=6, leftBlinker=True, steeringPressed=True, steeringTorque=1), 'slightLeft', log.Desire.turnLeft, log.Desire.keepLeft),
(make_car(vEgo=5, rightBlinker=True, steeringPressed=True, steeringTorque=-1), 'slightRight', log.Desire.turnRight, log.Desire.keepRight),
(make_car(vEgo=9, leftBlinker=True), 'slightLeft', log.Desire.laneChangeLeft, log.Desire.laneChangeLeft),
(make_car(vEgo=9, rightBlinker=True), 'slightRight', log.Desire.laneChangeRight, log.Desire.laneChangeRight),
(make_car(vEgo=9), 'none', log.Desire.none, log.Desire.none),
]
def make_nav_msg(valid=False, upcoming='none'):
return types.SimpleNamespace(valid=valid, upcomingTurn=upcoming)
def params_setter(allowed: bool):
params = Params()
params.put("NavDesiresAllowed", allowed)
@pytest.fixture
def mock_submaster(mocker):
mock_sm = mocker.patch('cereal.messaging.SubMaster')
mock_sm_instance = mocker.Mock()
mock_sm.return_value = mock_sm_instance
mock_sm_instance.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=False))
params_setter(True)
return mock_sm_instance
@pytest.mark.parametrize("upcoming, carstate, expected", NAVIGATION_PARAMS)
def test_navigation_desires_update(mock_submaster, mocker, upcoming, carstate, expected):
nav_desires = NavigationDesires()
nav_desires.sm.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=True, upcoming=upcoming))
nav_desires.update(carstate, True)
assert nav_desires.desire == expected
@pytest.mark.parametrize("msg_valid,lateral_active", [(False, True), (True, False)])
def test_invalid_or_inactive(mock_submaster, mocker, msg_valid, lateral_active):
nav_desires = NavigationDesires()
nav_desires.sm.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=msg_valid, upcoming='slightLeft'))
nav_desires.update(make_car(), lateral_active)
assert nav_desires.desire == log.Desire.none
def test_update(mock_submaster, mocker):
mock_submaster.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=True, upcoming='left'))
nav_desires = NavigationDesires()
assert nav_desires.update(make_car(leftBlinker=True, steeringPressed=True, steeringTorque=1), True) == log.Desire.turnLeft
params_setter(False)
nav_desires.param_counter = 59
nav_desires.update(make_car(leftBlinker=True), True)
assert nav_desires.desire == log.Desire.none
@pytest.mark.parametrize("carstate, upcoming, current_desire, expected", INTEGRATION_PARAMS)
def test_desire_helper(mock_submaster, mocker, carstate, upcoming, current_desire, expected):
mock_submaster.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=True, upcoming=upcoming))
dh = DesireHelper()
dh.desire = current_desire
if current_desire in (log.Desire.laneChangeLeft, log.Desire.laneChangeRight):
dh.lane_change_state = log.LaneChangeState.laneChangeStarting
dh.lane_change_direction = log.LaneChangeDirection.left if current_desire == log.Desire.laneChangeLeft else log.LaneChangeDirection.right
dh.update(carstate, True, 1.0)
assert dh.desire == expected
@@ -7,11 +7,17 @@ See the LICENSE.md file in the root directory for more details.
from parameterized import parameterized
import cereal.messaging
from openpilot.common.realtime import DT_MDL
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper, LaneChangeState, LaneChangeDirection
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode, \
AUTO_LANE_CHANGE_TIMER, ONE_SECOND_DELAY
class MockSubMaster:
def __init__(self, services):
pass
AUTO_LANE_CHANGE_TIMER_COMBOS = [
(AutoLaneChangeMode.NUDGELESS, AUTO_LANE_CHANGE_TIMER[AutoLaneChangeMode.NUDGELESS]),
(AutoLaneChangeMode.HALF_SECOND, AUTO_LANE_CHANGE_TIMER[AutoLaneChangeMode.HALF_SECOND]),
@@ -23,6 +29,7 @@ AUTO_LANE_CHANGE_TIMER_COMBOS = [
class TestAutoLaneChangeController:
def setup_method(self):
cereal.messaging.SubMaster = MockSubMaster
self.DH = DesireHelper()
self.alc = AutoLaneChangeController(self.DH)
@@ -1,14 +1,29 @@
import pytest
import cereal.messaging
from cereal import log, custom
from openpilot.common.params import Params
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController, LANE_CHANGE_SPEED_MIN
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeMode
TurnDirection = custom.ModelDataV2SP.TurnDirection
class MockSubMaster:
def __init__(self, services): pass
def update(self, timeout): pass
def __getitem__(self, key):
return type('nav_msg', (), {'valid': False})()
@pytest.fixture(autouse=True)
def mock_submaster():
cereal.messaging.SubMaster = MockSubMaster
@pytest.mark.parametrize("left_blinker,right_blinker,v_ego,blindspot_left,blindspot_right,expected", [
(True, False, 5, False, False, TurnDirection.turnLeft),
(False, True, 6, False, False, TurnDirection.turnRight),
@@ -107,7 +122,6 @@ def set_lane_turn_params():
])
def test_desire_helper_integration(carstate, lateral_active, lane_change_prob, expected_desire, set_lane_turn_params):
dh = DesireHelper()
dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE
for _ in range(10):
dh.update(carstate, lateral_active, lane_change_prob)
assert dh.desire == expected_desire # The first four tests were unit tests to test the controller, where this tests the integration in desire helpers
assert dh.desire == expected_desire