mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-10-27 15:23:43 +08:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08bf9b3511 | |||
| c9316cc3d2 | |||
| a06366ce92 | |||
| 861fd118a1 | |||
| 424311491c | |||
| a8795674b8 | |||
| 823b447fff | |||
| c36db99c2d | |||
| 5426b6af86 | |||
| bf0cdd667b | |||
| 9af59caf9c | |||
| 1a75c53ea4 | |||
| 83999585ec | |||
| 138353adb4 | |||
| 40dc4e1591 | |||
| cce4810904 | |||
| 5b58f9c1f5 | |||
| 815ac7732b | |||
| 2321f9d8f5 | |||
| b9020e0003 | |||
| ceb466578a | |||
| e1cfea4924 | |||
| dd8a2bf83a | |||
| ba36a28f2a | |||
| cf2010389a | |||
| 43522c4922 | |||
| 1cb5f24439 | |||
| b6a3f21a52 | |||
| 2ecc26b623 | |||
| 145f75fe06 | |||
| bd0578cbf9 | |||
| 40f24cc0b6 | |||
| 754efac063 | |||
| 132cc156f4 | |||
| 95ad932efb | |||
| ae30f4119f | |||
| d251ae4976 | |||
| 33324e590b | |||
| fea10ffd22 | |||
| f1e11e3f06 | |||
| c562eca8a1 | |||
| d9a690dac3 | |||
| a0eed058d1 | |||
| abc9f47a6f | |||
| b064f6fcaf | |||
| 0c7d7df2ec | |||
| dc86f35957 | |||
| 9685b0aa9b | |||
| f23cc408e3 | |||
| cea00a6c14 | |||
| f2949e1dc2 | |||
| 5f1f34fa7f | |||
| 2c041f9025 | |||
| 74b2d519b9 |
@@ -50,6 +50,12 @@ jobs:
|
||||
timeout-minutes: 3
|
||||
run: TARGET_DIR=$STRIPPED_DIR tools/release/build_stripped.sh
|
||||
- run: ./tools/op.sh setup
|
||||
- name: Restore scons cache
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: /tmp/scons_cache
|
||||
key: scons-${{ runner.os }}-${{ github.job }}-${{ github.sha }}
|
||||
restore-keys: scons-${{ runner.os }}-${{ github.job }}-
|
||||
- name: Build openpilot and run checks
|
||||
timeout-minutes: 30
|
||||
working-directory: ${{ env.STRIPPED_DIR }}
|
||||
@@ -89,6 +95,12 @@ jobs:
|
||||
FILTERED=$(echo "$PATH" | tr ':' '\n' | grep -v '/opt/homebrew' | tr '\n' ':')
|
||||
echo "PATH=${FILTERED}/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" >> $GITHUB_ENV
|
||||
- run: ./tools/op.sh setup
|
||||
- name: Restore scons cache
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: /tmp/scons_cache
|
||||
key: scons-${{ runner.os }}-${{ github.job }}-${{ github.sha }}
|
||||
restore-keys: scons-${{ runner.os }}-${{ github.job }}-
|
||||
- name: Building openpilot
|
||||
run: scons
|
||||
|
||||
@@ -121,6 +133,12 @@ jobs:
|
||||
- name: Install raylib headless dependencies
|
||||
if: ${{ !contains(runner.name, 'nsc') }}
|
||||
run: sudo apt-get install -y --no-install-recommends libgles2 libegl1
|
||||
- name: Restore scons cache
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: /tmp/scons_cache
|
||||
key: scons-${{ runner.os }}-${{ github.job }}-${{ github.sha }}
|
||||
restore-keys: scons-${{ runner.os }}-${{ github.job }}-
|
||||
- name: Build openpilot
|
||||
run: scons
|
||||
- name: Run unit tests
|
||||
@@ -140,6 +158,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- run: ./tools/op.sh setup
|
||||
- name: Restore scons cache
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: /tmp/scons_cache
|
||||
key: scons-${{ runner.os }}-${{ github.job }}-${{ github.sha }}
|
||||
restore-keys: scons-${{ runner.os }}-${{ github.job }}-
|
||||
- name: Build openpilot
|
||||
run: scons
|
||||
- name: Run replay
|
||||
@@ -228,6 +252,12 @@ jobs:
|
||||
- name: Install raylib headless dependencies
|
||||
if: ${{ !contains(runner.name, 'nsc') }}
|
||||
run: sudo apt-get install -y --no-install-recommends libgles2 libegl1
|
||||
- name: Restore scons cache
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: /tmp/scons_cache
|
||||
key: scons-${{ runner.os }}-${{ github.job }}-${{ github.sha }}
|
||||
restore-keys: scons-${{ runner.os }}-${{ github.job }}-
|
||||
- name: Build openpilot
|
||||
run: scons
|
||||
- name: Create UI Report
|
||||
|
||||
+1
-1
Submodule opendbc_repo updated: e3f1487de4...7b9e770e3b
@@ -200,6 +200,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"CustomButtonAction", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
|
||||
// MADS params
|
||||
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||
|
||||
@@ -64,16 +64,19 @@ 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 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))
|
||||
blindspot_detected = ((carstate.leftBlindspot and self.lane_change_direction == LaneChangeDirection.left) or
|
||||
(carstate.rightBlindspot and self.lane_change_direction == LaneChangeDirection.right))
|
||||
|
||||
self.alc.update_lane_change(blindspot_detected, carstate.brakePressed)
|
||||
edge_detected = ((left_edge_detected and self.lane_change_direction == LaneChangeDirection.left) or
|
||||
(right_edge_detected and self.lane_change_direction == LaneChangeDirection.right))
|
||||
|
||||
self.alc.update_lane_change(blindspot_detected, edge_detected, carstate.brakePressed, torque_applied)
|
||||
|
||||
if not one_blinker or below_lane_change_speed:
|
||||
self.lane_change_state = LaneChangeState.off
|
||||
self.lane_change_direction = LaneChangeDirection.none
|
||||
self.lane_change_timer = 0.0
|
||||
elif (torque_applied or self.alc.auto_lane_change_allowed) and not blindspot_detected:
|
||||
elif (torque_applied or self.alc.auto_lane_change_allowed) and not self.alc.lane_change_blocked:
|
||||
self.lane_change_state = LaneChangeState.laneChangeStarting
|
||||
self.lane_change_timer = 0.0
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from enum import IntEnum
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.selfdrive.ui.sunnypilot.custom_button import CustomButtonAction, handle_custom_button
|
||||
from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH
|
||||
from openpilot.selfdrive.ui.layouts.home import HomeLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType
|
||||
@@ -27,6 +28,7 @@ class MainLayout(Widget):
|
||||
super().__init__()
|
||||
|
||||
self._pm = messaging.PubMaster(['bookmarkButton', 'userBookmark'])
|
||||
self._custom_button_sock = messaging.sub_sock('carState')
|
||||
|
||||
self._sidebar = Sidebar()
|
||||
self._current_mode = MainState.HOME
|
||||
@@ -40,6 +42,10 @@ class MainLayout(Widget):
|
||||
MainState.SETTINGS: SettingsLayout(),
|
||||
MainState.ONROAD: AugmentedRoadView(),
|
||||
}
|
||||
self._custom_button_callbacks = {
|
||||
CustomButtonAction.BOOKMARK: self._on_bookmark_clicked,
|
||||
CustomButtonAction.CYCLE_UI: self._cycle_ui,
|
||||
}
|
||||
|
||||
self._sidebar_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self._content_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
@@ -55,6 +61,7 @@ class MainLayout(Widget):
|
||||
gui_app.push_widget(self._onboarding_window)
|
||||
|
||||
def _render(self, _):
|
||||
handle_custom_button(messaging.drain_sock(self._custom_button_sock), ui_state.params, self._custom_button_callbacks)
|
||||
self._handle_onroad_transition()
|
||||
self._render_main_content()
|
||||
|
||||
@@ -113,6 +120,18 @@ class MainLayout(Widget):
|
||||
def _on_settings_clicked(self):
|
||||
self.open_settings(PanelType.DEVICE)
|
||||
|
||||
def _show_onroad(self):
|
||||
self._set_current_layout(MainState.ONROAD)
|
||||
self._sidebar.set_visible(False)
|
||||
|
||||
def _cycle_ui(self):
|
||||
if self._current_mode == MainState.ONROAD and not self._sidebar.is_visible:
|
||||
self._sidebar.set_visible(True)
|
||||
elif self._current_mode == MainState.SETTINGS:
|
||||
self._show_onroad()
|
||||
else:
|
||||
self._on_settings_clicked()
|
||||
|
||||
def _on_bookmark_clicked(self):
|
||||
for service in ('bookmarkButton', 'userBookmark'):
|
||||
msg = messaging.new_message(service, valid=True)
|
||||
|
||||
@@ -4,6 +4,7 @@ from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsLayout
|
||||
from openpilot.selfdrive.ui.mici.layouts.offroad_alerts import MiciOffroadAlerts
|
||||
from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView
|
||||
from openpilot.selfdrive.ui.sunnypilot.custom_button import CustomButtonAction, handle_custom_button
|
||||
from openpilot.selfdrive.ui.ui_state import device, ui_state
|
||||
from openpilot.selfdrive.ui.mici.layouts.onboarding import OnboardingWindow
|
||||
from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout
|
||||
@@ -23,6 +24,7 @@ class MiciMainLayout(Scroller):
|
||||
super().__init__(snap_items=True, spacing=0, pad=0, scroll_indicator=False, edge_shadows=False)
|
||||
|
||||
self._pm = messaging.PubMaster(['bookmarkButton', 'userBookmark'])
|
||||
self._custom_button_sock = messaging.sub_sock('carState')
|
||||
|
||||
self._prev_onroad = False
|
||||
self._prev_standstill = False
|
||||
@@ -35,6 +37,10 @@ class MiciMainLayout(Scroller):
|
||||
self._settings_layout = SettingsLayout()
|
||||
self._car_onroad_layout = AugmentedRoadView(bookmark_callback=self._on_bookmark_clicked)
|
||||
self._body_onroad_layout = BodyLayout()
|
||||
self._custom_button_callbacks = {
|
||||
CustomButtonAction.BOOKMARK: self._on_bookmark_clicked,
|
||||
CustomButtonAction.CYCLE_UI: self._cycle_ui,
|
||||
}
|
||||
|
||||
# Initialize widget rects
|
||||
for widget in (self._home_layout, self._alerts_layout, self._settings_layout,
|
||||
@@ -97,6 +103,8 @@ class MiciMainLayout(Scroller):
|
||||
self._alerts_layout._update_state()
|
||||
|
||||
def _render(self, _):
|
||||
handle_custom_button(messaging.drain_sock(self._custom_button_sock), ui_state.params, self._custom_button_callbacks)
|
||||
|
||||
if not self._setup:
|
||||
if self._alerts_layout.active_alerts() > 0:
|
||||
self._scroller.scroll_to(self._alerts_layout.rect.x)
|
||||
@@ -152,6 +160,23 @@ class MiciMainLayout(Scroller):
|
||||
msg = messaging.new_message(service, valid=True)
|
||||
self._pm.send(service, msg)
|
||||
|
||||
def _show_layout(self, layout: Widget):
|
||||
if gui_app.widget_in_stack(self._onboarding_window):
|
||||
return
|
||||
gui_app.pop_widgets_to(self, lambda: self._scroll_to(layout))
|
||||
|
||||
def _layout_visible(self, layout: Widget) -> bool:
|
||||
return abs(layout.rect.x - self._rect.x) < self._rect.width / 2
|
||||
|
||||
def _cycle_ui(self):
|
||||
if gui_app.widget_in_stack(self._settings_layout):
|
||||
self._show_layout(self._onroad_layout)
|
||||
elif gui_app.get_active_widget() is self and self._layout_visible(self._home_layout):
|
||||
if not gui_app.widget_in_stack(self._onboarding_window):
|
||||
gui_app.push_widget(self._settings_layout)
|
||||
else:
|
||||
self._show_layout(self._home_layout)
|
||||
|
||||
def _on_body_changed(self):
|
||||
self._car_onroad_layout.set_visible(not ui_state.is_body)
|
||||
self._body_onroad_layout.set_visible(bool(ui_state.is_body))
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
from enum import IntEnum
|
||||
|
||||
from opendbc.car.structs import car
|
||||
|
||||
|
||||
class CustomButtonAction(IntEnum):
|
||||
NONE = 0
|
||||
BOOKMARK = 1
|
||||
CYCLE_UI = 3
|
||||
|
||||
|
||||
def handle_custom_button(messages, params, callbacks):
|
||||
for msg in messages:
|
||||
custom_pressed = any(be.type == car.CarState.ButtonEvent.Type.altButton2 and be.pressed
|
||||
for be in msg.carState.buttonEvents)
|
||||
if custom_pressed:
|
||||
action = CustomButtonAction(params.get('CustomButtonAction', return_default=True))
|
||||
if callback := callbacks.get(action):
|
||||
callback()
|
||||
@@ -0,0 +1,21 @@
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock
|
||||
|
||||
from opendbc.car.structs import car
|
||||
|
||||
from openpilot.selfdrive.ui.sunnypilot.custom_button import CustomButtonAction, handle_custom_button
|
||||
|
||||
|
||||
def test_custom_button_actions():
|
||||
params = Mock()
|
||||
press = SimpleNamespace(carState=SimpleNamespace(buttonEvents=[SimpleNamespace(
|
||||
type=car.CarState.ButtonEvent.Type.altButton2,
|
||||
pressed=True,
|
||||
)]))
|
||||
messages = [press, SimpleNamespace(carState=SimpleNamespace(buttonEvents=[])), press]
|
||||
callbacks = {action: Mock() for action in CustomButtonAction if action != CustomButtonAction.NONE}
|
||||
|
||||
for action, callback in callbacks.items():
|
||||
params.get.return_value = action
|
||||
handle_custom_button(messages, params, callbacks)
|
||||
assert callback.call_count == 2
|
||||
@@ -10,6 +10,8 @@ from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
|
||||
|
||||
|
||||
|
||||
class AutoLaneChangeMode:
|
||||
OFF = -1
|
||||
NUDGE = 0 # default
|
||||
@@ -31,6 +33,7 @@ AUTO_LANE_CHANGE_TIMER = {
|
||||
}
|
||||
|
||||
ONE_SECOND_DELAY = -1
|
||||
EDGE_DELAY = -2
|
||||
|
||||
|
||||
class AutoLaneChangeController:
|
||||
@@ -48,6 +51,7 @@ class AutoLaneChangeController:
|
||||
self.prev_brake_pressed = False
|
||||
self.auto_lane_change_allowed = False
|
||||
self.prev_lane_change = False
|
||||
self.lane_change_blocked = False
|
||||
|
||||
self.read_params()
|
||||
|
||||
@@ -68,7 +72,7 @@ class AutoLaneChangeController:
|
||||
self.read_params()
|
||||
self.param_read_counter += 1
|
||||
|
||||
def update_lane_change_timers(self, blindspot_detected: bool) -> None:
|
||||
def update_lane_change_timers(self, blindspot_detected: bool, edge_detected: bool) -> None:
|
||||
self.lane_change_delay = AUTO_LANE_CHANGE_TIMER.get(self.lane_change_set_timer,
|
||||
AUTO_LANE_CHANGE_TIMER[AutoLaneChangeMode.NUDGE])
|
||||
|
||||
@@ -80,6 +84,12 @@ class AutoLaneChangeController:
|
||||
else:
|
||||
self.lane_change_wait_timer = self.lane_change_delay + ONE_SECOND_DELAY
|
||||
|
||||
if edge_detected and self.lane_change_delay > 0:
|
||||
if self.lane_change_delay == AUTO_LANE_CHANGE_TIMER[AutoLaneChangeMode.NUDGELESS]:
|
||||
self.lane_change_wait_timer = min(self.lane_change_wait_timer, EDGE_DELAY)
|
||||
else:
|
||||
self.lane_change_wait_timer = min(self.lane_change_wait_timer, self.lane_change_delay + EDGE_DELAY)
|
||||
|
||||
def update_allowed(self) -> bool:
|
||||
# Auto lane change allowed if:
|
||||
# 1. A valid delay is set (non-zero)
|
||||
@@ -97,14 +107,23 @@ class AutoLaneChangeController:
|
||||
|
||||
return bool(self.lane_change_wait_timer > self.lane_change_delay)
|
||||
|
||||
def update_lane_change(self, blindspot_detected: bool, brake_pressed: bool) -> None:
|
||||
def update_lane_change(self, blindspot_detected: bool, edge_detected: bool, brake_pressed: bool,
|
||||
torque_applied: bool = False) -> None:
|
||||
if brake_pressed and not self.prev_brake_pressed:
|
||||
self.prev_brake_pressed = brake_pressed
|
||||
|
||||
self.update_lane_change_timers(blindspot_detected)
|
||||
self.update_lane_change_timers(blindspot_detected, edge_detected)
|
||||
|
||||
self.auto_lane_change_allowed = self.update_allowed()
|
||||
|
||||
# BSM always blocks. Edge blocks auto mode but nudge can override.
|
||||
if blindspot_detected:
|
||||
self.lane_change_blocked = True
|
||||
elif edge_detected:
|
||||
self.lane_change_blocked = not torque_applied
|
||||
else:
|
||||
self.lane_change_blocked = False
|
||||
|
||||
def update_state(self):
|
||||
if self.DH.lane_change_state == log.LaneChangeState.laneChangeStarting:
|
||||
self.prev_lane_change = True
|
||||
|
||||
@@ -8,7 +8,7 @@ from openpilot.common.parameterized import parameterized
|
||||
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
|
||||
AUTO_LANE_CHANGE_TIMER, ONE_SECOND_DELAY, EDGE_DELAY
|
||||
from openpilot.common.test import OpenpilotTestCase
|
||||
|
||||
AUTO_LANE_CHANGE_TIMER_COMBOS = [
|
||||
@@ -61,7 +61,7 @@ class TestAutoLaneChangeController(OpenpilotTestCase):
|
||||
# Update controller
|
||||
num_updates = int(5.0 / DT_MDL)
|
||||
for _ in range(num_updates): # Run for 5 seconds
|
||||
self.alc.update_lane_change(blindspot_detected=False, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=False)
|
||||
|
||||
# Mode should not allow lane change immediately
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
@@ -74,11 +74,11 @@ class TestAutoLaneChangeController(OpenpilotTestCase):
|
||||
self.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGELESS
|
||||
|
||||
# Update controller once to read params
|
||||
self.alc.update_lane_change(blindspot_detected=False, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=False)
|
||||
|
||||
# Update multiple times to exceed the timer threshold
|
||||
for _ in range(1): # Should exceed 0.1s with multiple DT_MDL updates
|
||||
self.alc.update_lane_change(blindspot_detected=False, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=False)
|
||||
|
||||
# Now lane change should be allowed
|
||||
assert self.alc.lane_change_wait_timer > self.alc.lane_change_delay
|
||||
@@ -91,7 +91,7 @@ class TestAutoLaneChangeController(OpenpilotTestCase):
|
||||
self.alc.lane_change_set_timer = timer_state
|
||||
|
||||
# Update controller once
|
||||
self.alc.update_lane_change(blindspot_detected=False, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=False)
|
||||
|
||||
# The timer should still be below the threshold after one update
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
@@ -99,7 +99,7 @@ class TestAutoLaneChangeController(OpenpilotTestCase):
|
||||
# Update enough times to exceed the threshold (seconds / DT_MDL)
|
||||
num_updates = int(timer_delay / DT_MDL) + 1 # Add one extra updates to ensure we exceed the threshold
|
||||
for _ in range(num_updates):
|
||||
self.alc.update_lane_change(blindspot_detected=False, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=False)
|
||||
|
||||
# Now lane change should be allowed
|
||||
assert self.alc.lane_change_wait_timer > self.alc.lane_change_delay
|
||||
@@ -116,7 +116,7 @@ class TestAutoLaneChangeController(OpenpilotTestCase):
|
||||
|
||||
# Update with brake pressed for 1 second
|
||||
for _ in range(num_updates):
|
||||
self.alc.update_lane_change(blindspot_detected=False, brake_pressed=True)
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=True)
|
||||
|
||||
# Even though it is an auto lane change mode, lane change should be disallowed due to brake pressed prior initiating lane change
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
@@ -126,7 +126,7 @@ class TestAutoLaneChangeController(OpenpilotTestCase):
|
||||
|
||||
# Even releasing brake shouldn't allow auto lane change
|
||||
for _ in range(num_updates):
|
||||
self.alc.update_lane_change(blindspot_detected=False, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=False)
|
||||
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
|
||||
@@ -139,13 +139,13 @@ class TestAutoLaneChangeController(OpenpilotTestCase):
|
||||
self.alc.lane_change_set_timer = timer_state
|
||||
|
||||
# Update with blindspot detected - this should prevent auto lane change
|
||||
self.alc.update_lane_change(blindspot_detected=True, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=True, edge_detected=False, brake_pressed=False)
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
|
||||
# Keep updating with blindspot detected - should still prevent auto lane change
|
||||
num_updates = int(timer_delay / DT_MDL) + 1 # Add one extra updates to ensure we exceed the threshold
|
||||
for _ in range(num_updates):
|
||||
self.alc.update_lane_change(blindspot_detected=True, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=True, edge_detected=False, brake_pressed=False)
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
|
||||
@parameterized.expand(AUTO_LANE_CHANGE_TIMER_COMBOS)
|
||||
@@ -157,13 +157,13 @@ class TestAutoLaneChangeController(OpenpilotTestCase):
|
||||
self.alc.lane_change_set_timer = timer_state
|
||||
|
||||
# First update with blindspot detected to set the negative timer
|
||||
self.alc.update_lane_change(blindspot_detected=True, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=True, edge_detected=False, brake_pressed=False)
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
|
||||
# Now update with blindspot cleared - should start incrementing timer from negative value
|
||||
num_updates = int((timer_delay + abs(ONE_SECOND_DELAY)) / DT_MDL) + 1
|
||||
for _ in range(num_updates):
|
||||
self.alc.update_lane_change(blindspot_detected=False, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=False)
|
||||
|
||||
# After sufficient updates with no blindspot, auto lane change should be allowed
|
||||
assert self.alc.auto_lane_change_allowed
|
||||
@@ -177,7 +177,7 @@ class TestAutoLaneChangeController(OpenpilotTestCase):
|
||||
|
||||
# Update enough times to exceed the threshold (seconds / DT_MDL)
|
||||
for _ in range(num_updates):
|
||||
self.alc.update_lane_change(blindspot_detected=False, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=False)
|
||||
|
||||
# Now lane change should be allowed
|
||||
assert self.alc.lane_change_wait_timer > self.alc.lane_change_delay
|
||||
@@ -193,7 +193,7 @@ class TestAutoLaneChangeController(OpenpilotTestCase):
|
||||
|
||||
# Update enough times to exceed the threshold (seconds / DT_MDL)
|
||||
for _ in range(num_updates):
|
||||
self.alc.update_lane_change(blindspot_detected=False, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=False)
|
||||
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
|
||||
@@ -206,7 +206,86 @@ class TestAutoLaneChangeController(OpenpilotTestCase):
|
||||
# Simulate updates for a long period of time (e.g., 10 seconds)
|
||||
num_updates = int(10.0 / DT_MDL)
|
||||
for _ in range(num_updates):
|
||||
self.alc.update_lane_change(blindspot_detected=False, brake_pressed=False)
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=False)
|
||||
|
||||
# Lane change should never be allowed
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
|
||||
@parameterized.expand(AUTO_LANE_CHANGE_TIMER_COMBOS)
|
||||
def test_edge_detected_delays_auto_lane_change(self, timer_state, timer_delay):
|
||||
"""Test that road edge detection adds delay to auto lane change."""
|
||||
self._reset_states()
|
||||
self.alc.lane_change_bsm_delay = False
|
||||
self.alc.lane_change_set_timer = timer_state
|
||||
|
||||
# Update with edge detected - should prevent auto lane change
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=True, brake_pressed=False)
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
|
||||
# Keep updating with edge detected - timer keeps resetting, should stay disallowed
|
||||
num_updates = int(timer_delay / DT_MDL) + 1
|
||||
for _ in range(num_updates):
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=True, brake_pressed=False)
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
|
||||
@parameterized.expand(AUTO_LANE_CHANGE_TIMER_COMBOS)
|
||||
def test_edge_detected_then_cleared_allows_after_delay(self, timer_state, timer_delay):
|
||||
"""Test that auto lane change is allowed after edge clears plus extra delay."""
|
||||
self._reset_states()
|
||||
self.alc.lane_change_bsm_delay = False
|
||||
self.alc.lane_change_set_timer = timer_state
|
||||
|
||||
# First update with edge detected to set the negative timer
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=True, brake_pressed=False)
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
|
||||
# Now update with edge cleared - should start incrementing timer from negative value
|
||||
num_updates = int((timer_delay + abs(EDGE_DELAY)) / DT_MDL) + 1
|
||||
for _ in range(num_updates):
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=False)
|
||||
|
||||
# After sufficient updates with no edge, auto lane change should be allowed
|
||||
assert self.alc.auto_lane_change_allowed
|
||||
|
||||
@parameterized.expand(AUTO_LANE_CHANGE_TIMER_COMBOS)
|
||||
def test_edge_and_bsm_combined_uses_worst_delay(self, timer_state, timer_delay):
|
||||
"""Test that when both BSM and edge are detected, the worst (longest) delay wins."""
|
||||
self._reset_states()
|
||||
self.alc.lane_change_bsm_delay = True
|
||||
self.alc.lane_change_set_timer = timer_state
|
||||
|
||||
# Update with both blindspot and edge detected
|
||||
self.alc.update_lane_change(blindspot_detected=True, edge_detected=True, brake_pressed=False)
|
||||
assert not self.alc.auto_lane_change_allowed
|
||||
|
||||
# Clear both - need to wait out the worst delay
|
||||
worst_delay = max(abs(ONE_SECOND_DELAY), abs(EDGE_DELAY))
|
||||
num_updates = int((timer_delay + worst_delay) / DT_MDL) + 1
|
||||
for _ in range(num_updates):
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=False, brake_pressed=False)
|
||||
|
||||
assert self.alc.auto_lane_change_allowed
|
||||
|
||||
@parameterized.expand(AUTO_LANE_CHANGE_TIMER_COMBOS)
|
||||
def test_nudge_overrides_edge_in_lane_change_blocked(self, timer_state, timer_delay):
|
||||
"""Test that lane_change_blocked is False when nudge applied with edge-only."""
|
||||
self._reset_states()
|
||||
self.alc.lane_change_set_timer = timer_state
|
||||
|
||||
# Edge detected, no nudge - should be blocked
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=True, brake_pressed=False, torque_applied=False)
|
||||
assert self.alc.lane_change_blocked
|
||||
|
||||
# Edge detected, nudge applied - should NOT be blocked
|
||||
self.alc.update_lane_change(blindspot_detected=False, edge_detected=True, brake_pressed=False, torque_applied=True)
|
||||
assert not self.alc.lane_change_blocked
|
||||
|
||||
@parameterized.expand(AUTO_LANE_CHANGE_TIMER_COMBOS)
|
||||
def test_nudge_does_not_override_bsm_in_lane_change_blocked(self, timer_state, timer_delay):
|
||||
"""Test that lane_change_blocked stays True when BSM detected even with nudge."""
|
||||
self._reset_states()
|
||||
self.alc.lane_change_set_timer = timer_state
|
||||
|
||||
# BSM detected with nudge - should still be blocked
|
||||
self.alc.update_lane_change(blindspot_detected=True, edge_detected=False, brake_pressed=False, torque_applied=True)
|
||||
assert self.alc.lane_change_blocked
|
||||
|
||||
@@ -113,12 +113,56 @@ class TestDesireHelperIntegration(OpenpilotTestCase):
|
||||
left_edge_detected=False, right_edge_detected=False)
|
||||
assert dh.desire == expected_desire
|
||||
|
||||
def test_edge_blocks_lane_change(self, set_lane_turn_params):
|
||||
def test_nudge_overrides_edge_detection(self, set_lane_turn_params):
|
||||
"""Manual steering nudge should override road edge detection and allow lane change."""
|
||||
dh = DesireHelper()
|
||||
dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE
|
||||
carstate = DummyCarState(vEgo=15, leftBlinker=True, steeringPressed=True, steeringTorque=1)
|
||||
for _ in range(10):
|
||||
dh.update(carstate, True, 1.0, left_edge_detected=True, right_edge_detected=False)
|
||||
assert dh.lane_change_state == LaneChangeState.preLaneChange
|
||||
assert dh.lane_change_state == LaneChangeState.laneChangeStarting
|
||||
assert dh.lane_change_direction == LaneChangeDirection.left
|
||||
assert dh.desire == log.Desire.laneChangeLeft
|
||||
|
||||
def test_nudge_does_not_override_bsm(self, set_lane_turn_params):
|
||||
"""Manual steering nudge must NOT override BSM blindspot detection."""
|
||||
dh = DesireHelper()
|
||||
dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE
|
||||
carstate = DummyCarState(vEgo=15, leftBlinker=True, leftBlindspot=True, steeringPressed=True, steeringTorque=1)
|
||||
for _ in range(10):
|
||||
dh.update(carstate, True, 1.0, left_edge_detected=False, right_edge_detected=False)
|
||||
assert dh.lane_change_state == LaneChangeState.preLaneChange
|
||||
assert dh.desire == log.Desire.none
|
||||
|
||||
def test_edge_blocks_auto_lane_change(self, set_lane_turn_params):
|
||||
"""Road edge detection should still block auto (nudgeless) lane changes."""
|
||||
dh = DesireHelper()
|
||||
dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGELESS
|
||||
dh.alc.param_read_counter = 1
|
||||
carstate = DummyCarState(vEgo=15, leftBlinker=True)
|
||||
for _ in range(10):
|
||||
dh.update(carstate, True, 1.0, left_edge_detected=True, right_edge_detected=False)
|
||||
assert dh.lane_change_state == LaneChangeState.preLaneChange
|
||||
assert dh.desire == log.Desire.none
|
||||
|
||||
def test_auto_lane_change_allowed_without_edge(self, set_lane_turn_params):
|
||||
"""Auto lane change should proceed when no edge and no BSM detected."""
|
||||
dh = DesireHelper()
|
||||
dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGELESS
|
||||
dh.alc.param_read_counter = 1
|
||||
carstate = DummyCarState(vEgo=15, leftBlinker=True)
|
||||
for _ in range(10):
|
||||
dh.update(carstate, True, 1.0, left_edge_detected=False, right_edge_detected=False)
|
||||
assert dh.lane_change_state == LaneChangeState.laneChangeStarting
|
||||
assert dh.desire == log.Desire.laneChangeLeft
|
||||
|
||||
def test_right_edge_does_not_block_left_lane_change(self, set_lane_turn_params):
|
||||
"""Edge on opposite side should not affect lane change direction."""
|
||||
dh = DesireHelper()
|
||||
dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGELESS
|
||||
dh.alc.param_read_counter = 1
|
||||
carstate = DummyCarState(vEgo=15, leftBlinker=True)
|
||||
for _ in range(10):
|
||||
dh.update(carstate, True, 1.0, left_edge_detected=False, right_edge_detected=True)
|
||||
assert dh.lane_change_state == LaneChangeState.laneChangeStarting
|
||||
assert dh.desire == log.Desire.laneChangeLeft
|
||||
|
||||
@@ -2161,6 +2161,26 @@
|
||||
"title": "Hyundai / Kia / Genesis Settings",
|
||||
"description": "",
|
||||
"items": [
|
||||
{
|
||||
"key": "CustomButtonAction",
|
||||
"widget": "multiple_button",
|
||||
"title": "Steering Custom Button",
|
||||
"description": "Choose the openpilot action for the steering wheel custom/star button. OEM functionality is unchanged.",
|
||||
"options": [
|
||||
{
|
||||
"value": 0,
|
||||
"label": "None"
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"label": "Bookmark"
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"label": "Cycle UI"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "HyundaiLongitudinalTuning",
|
||||
"widget": "multiple_button",
|
||||
|
||||
@@ -10,6 +10,17 @@ sections:
|
||||
title: Hyundai / Kia / Genesis Settings
|
||||
description: ''
|
||||
items:
|
||||
- key: CustomButtonAction
|
||||
widget: multiple_button
|
||||
title: Steering Custom Button
|
||||
description: Choose the openpilot action for the steering wheel custom/star button. OEM functionality is unchanged.
|
||||
options:
|
||||
- value: 0
|
||||
label: None
|
||||
- value: 1
|
||||
label: Bookmark
|
||||
- value: 3
|
||||
label: Cycle UI
|
||||
- key: HyundaiLongitudinalTuning
|
||||
widget: multiple_button
|
||||
title: Custom Longitudinal Tuning
|
||||
|
||||
Reference in New Issue
Block a user