diff --git a/openpilot/common/libparams_c.so b/openpilot/common/libparams_c.so index 18fc01036..6a01ffb13 100755 Binary files a/openpilot/common/libparams_c.so and b/openpilot/common/libparams_c.so differ diff --git a/openpilot/common/params_keys.h b/openpilot/common/params_keys.h index dcba232b7..4cbfa7c89 100644 --- a/openpilot/common/params_keys.h +++ b/openpilot/common/params_keys.h @@ -242,6 +242,7 @@ inline static std::unordered_map keys = { // sunnypilot model params {"CameraOffset", {PERSISTENT | BACKUP, FLOAT, "0.0"}}, + {"RoadCameraViewMode", {PERSISTENT | BACKUP, INT, "0"}}, {"LagdToggle", {PERSISTENT | BACKUP, BOOL, "1"}}, {"LagdToggleDelay", {PERSISTENT | BACKUP, FLOAT, "0.2"}}, {"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}}, diff --git a/openpilot/selfdrive/ui/onroad/augmented_road_view.py b/openpilot/selfdrive/ui/onroad/augmented_road_view.py index 39696e6cf..000737e50 100644 --- a/openpilot/selfdrive/ui/onroad/augmented_road_view.py +++ b/openpilot/selfdrive/ui/onroad/augmented_road_view.py @@ -37,8 +37,24 @@ BORDER_COLORS = { WIDE_CAM_MAX_SPEED = 5.0 # m/s (11 mph) ROAD_CAM_MIN_SPEED = 10.0 # m/s (22 mph) INF_POINT = np.array([1000.0, 0.0, 0.0]) -CAMERA_TOGGLE_SIZE = 104 -CAMERA_TOGGLE_MARGIN = 18 +ROAD_CAMERA_VIEW_AUTO = 0 +ROAD_CAMERA_VIEW_WIDE = 1 +ROAD_CAMERA_VIEW_STANDARD = 2 + + +def camera_stream_target(mode: int, current_stream: VisionStreamType, v_ego: float, + openpilot_longitudinal_control: bool, available_streams: list[VisionStreamType]) -> VisionStreamType: + if mode == ROAD_CAMERA_VIEW_WIDE and WIDE_CAM in available_streams: + return WIDE_CAM + if mode == ROAD_CAMERA_VIEW_STANDARD: + return NARROW_ROAD_CAM + if openpilot_longitudinal_control and WIDE_CAM in available_streams: + if v_ego < WIDE_CAM_MAX_SPEED: + return WIDE_CAM + if v_ego > ROAD_CAM_MIN_SPEED: + return NARROW_ROAD_CAM + return current_stream + return NARROW_ROAD_CAM class AugmentedRoadView(CameraView, AugmentedRoadViewSP): @@ -54,10 +70,6 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewSP): self._matrix_cache_key = (0, 0.0, 0.0, stream_type) self._cached_matrix: np.ndarray | None = None self._content_rect = rl.Rectangle() - self._manual_stream_type: VisionStreamType | None = None - self._camera_toggle_rect = rl.Rectangle() - self._camera_toggle_flash_until = 0.0 - self._camera_toggle_icon = gui_app.texture("icons/eye_open.png", 58, 32) self.model_renderer = ModelRenderer() self._hud_renderer = HudRenderer() @@ -110,7 +122,6 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewSP): )) self.alert_renderer.render(self._content_rect) self.driver_state_renderer.render(self._content_rect) - self._draw_camera_toggle(self._content_rect) # Custom UI extension point - add custom overlays here # Use self._content_rect for positioning within camera bounds @@ -121,10 +132,7 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewSP): # Draw colored border based on driving state self._draw_border(rect) - def _handle_mouse_press(self, mouse_pos): - if self._camera_toggle_rect.width and rl.check_collision_point_rec(mouse_pos, self._camera_toggle_rect): - self._toggle_camera_stream() - return + def _handle_mouse_press(self, _): if self._click_callback is not None: self._click_callback() @@ -141,77 +149,20 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewSP): rl.draw_rectangle_rounded_lines_ex(border_rect, border_roundness, 10, UI_BORDER_SIZE, border_color) def _switch_stream_if_needed(self, sm): - if self._manual_stream_type in self.available_streams: - target = self._manual_stream_type - elif sm['carParams'].openpilotLongitudinalControl and WIDE_CAM in self.available_streams: - v_ego = sm['carState'].vEgo - if v_ego < WIDE_CAM_MAX_SPEED: - target = WIDE_CAM - elif v_ego > ROAD_CAM_MIN_SPEED: - target = NARROW_ROAD_CAM - else: - # Hysteresis zone - keep current stream - target = self.stream_type - else: - target = NARROW_ROAD_CAM - + try: + mode = int(ui_state.params.get("RoadCameraViewMode", return_default=True)) + except Exception: + mode = ROAD_CAMERA_VIEW_AUTO + target = camera_stream_target( + mode, + self.stream_type, + sm['carState'].vEgo, + sm['carParams'].openpilotLongitudinalControl, + self.available_streams, + ) if self.stream_type != target: self.switch_stream(target) - def _toggle_camera_stream(self): - if WIDE_CAM not in self.available_streams: - return - current = self._target_stream_type or self._stream_type - target = NARROW_ROAD_CAM if current == WIDE_CAM else WIDE_CAM - self._manual_stream_type = target - self._camera_toggle_flash_until = rl.get_time() + 0.2 - self.switch_stream(target) - - def _draw_camera_toggle(self, rect: rl.Rectangle): - if WIDE_CAM not in self.available_streams: - self._camera_toggle_rect = rl.Rectangle() - return - - self._camera_toggle_rect = rl.Rectangle( - rect.x + rect.width - CAMERA_TOGGLE_SIZE - CAMERA_TOGGLE_MARGIN, - rect.y + CAMERA_TOGGLE_MARGIN, - CAMERA_TOGGLE_SIZE, - CAMERA_TOGGLE_SIZE, - ) - pressed = rl.get_time() < self._camera_toggle_flash_until - current = self._target_stream_type or self._stream_type - is_wide = current == WIDE_CAM - - center = rl.Vector2( - self._camera_toggle_rect.x + self._camera_toggle_rect.width / 2, - self._camera_toggle_rect.y + self._camera_toggle_rect.height / 2, - ) - radius = self._camera_toggle_rect.width / 2 - bg = rl.Color(35, 83, 61, 215) if pressed else rl.Color(4, 10, 14, 150) - border = rl.Color(155, 230, 185, 245) if pressed else rl.Color(255, 255, 255, 70) - rl.draw_circle_v(center, radius, bg) - rl.draw_circle_lines_v(center, radius - 1, border) - - icon_pos = rl.Vector2(center.x - self._camera_toggle_icon.width / 2, - center.y - self._camera_toggle_icon.height / 2) - rl.draw_texture_ex(self._camera_toggle_icon, icon_pos, 0.0, 1.0, rl.Color(255, 255, 255, 238)) - - inset = 17 if is_wide else 25 - left = self._camera_toggle_rect.x + inset - right = self._camera_toggle_rect.x + self._camera_toggle_rect.width - inset - top = self._camera_toggle_rect.y + inset - bottom = self._camera_toggle_rect.y + self._camera_toggle_rect.height - inset - corner = 9 - accent = rl.Color(155, 230, 185, 220) if is_wide else rl.Color(255, 255, 255, 180) - rl.draw_line_ex(rl.Vector2(left, top), rl.Vector2(left + corner, top), 3, accent) - rl.draw_line_ex(rl.Vector2(left, top), rl.Vector2(left, top + corner), 3, accent) - rl.draw_line_ex(rl.Vector2(right, top), rl.Vector2(right - corner, top), 3, accent) - rl.draw_line_ex(rl.Vector2(right, top), rl.Vector2(right, top + corner), 3, accent) - rl.draw_line_ex(rl.Vector2(left, bottom), rl.Vector2(left + corner, bottom), 3, accent) - rl.draw_line_ex(rl.Vector2(left, bottom), rl.Vector2(left, bottom - corner), 3, accent) - rl.draw_line_ex(rl.Vector2(right, bottom), rl.Vector2(right - corner, bottom), 3, accent) - rl.draw_line_ex(rl.Vector2(right, bottom), rl.Vector2(right, bottom - corner), 3, accent) - def _update_calibration(self): # Update device camera if not already set sm = ui_state.sm diff --git a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py index e960b7745..b93d3b25b 100644 --- a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +++ b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py @@ -17,6 +17,8 @@ CHEVRON_INFO_DESCRIPTION = { "disabled": tr_noop("This feature requires sunnypilot longitudinal control to be available.") } +CAMERA_VIEW_DESCRIPTION = tr_noop("Automatic switches between wide and standard views based on vehicle speed. Wide always uses the wide road camera; Standard always uses the narrow road camera.") # noqa: E501 + class VisualsLayout(Widget): def __init__(self): @@ -110,6 +112,13 @@ class VisualsLayout(Widget): ) self._toggles[param] = toggle + self._camera_view = multiple_button_item_sp( + title=lambda: tr("Road Camera View"), + description=lambda: tr(CAMERA_VIEW_DESCRIPTION), + buttons=[lambda: tr("Auto"), lambda: tr("Wide"), lambda: tr("Standard")], + callback=self._on_camera_view_changed, + inline=False, + ) self._chevron_info = multiple_button_item_sp( title=lambda: tr("Display Metrics Below Chevron"), description="", @@ -126,7 +135,7 @@ class VisualsLayout(Widget): inline=False ) - items = list(self._toggles.values()) + [ + items = [self._camera_view] + list(self._toggles.values()) + [ self._chevron_info, self._dev_ui_info, ] @@ -139,6 +148,11 @@ class VisualsLayout(Widget): self._toggles[param].action_item.set_state(self._params.get_bool(param)) self._dev_ui_info.action_item.set_selected_button(ui_state.params.get("DevUIInfo", return_default=True)) + try: + camera_view_mode = ui_state.params.get("RoadCameraViewMode", return_default=True) + except Exception: + camera_view_mode = 0 + self._camera_view.action_item.set_selected_button(camera_view_mode) if ui_state.has_longitudinal_control: self._chevron_info.set_description(tr(CHEVRON_INFO_DESCRIPTION["enabled"])) @@ -149,6 +163,14 @@ class VisualsLayout(Widget): self._chevron_info.action_item.set_enabled(False) ui_state.params.put("ChevronInfo", 0) + def _on_camera_view_changed(self, index: int): + if index not in (0, 1, 2): + return + try: + ui_state.params.put("RoadCameraViewMode", index, block=True) + except Exception: + pass + def _render(self, rect): self._scroller.render(rect) diff --git a/openpilot/selfdrive/ui/tests/test_camera_toggle.py b/openpilot/selfdrive/ui/tests/test_camera_toggle.py deleted file mode 100644 index 77fc42ec0..000000000 --- a/openpilot/selfdrive/ui/tests/test_camera_toggle.py +++ /dev/null @@ -1,56 +0,0 @@ -import unittest -from types import SimpleNamespace -from unittest.mock import Mock - -from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView, NARROW_ROAD_CAM, WIDE_CAM - - -class TestView(AugmentedRoadView): - def __del__(self): - pass - - -class TestOnroadCameraToggle(unittest.TestCase): - def _view(self): - view = object.__new__(TestView) - view.available_streams = [NARROW_ROAD_CAM, WIDE_CAM] - view._stream_type = NARROW_ROAD_CAM - view._target_stream_type = None - view._manual_stream_type = None - view._camera_toggle_flash_until = 0.0 - view.switch_stream = Mock() - return view - - def test_toggle_between_wide_and_narrow(self): - view = self._view() - view._toggle_camera_stream() - self.assertEqual(view._manual_stream_type, WIDE_CAM) - view.switch_stream.assert_called_once_with(WIDE_CAM) - - view._stream_type = WIDE_CAM - view.switch_stream.reset_mock() - view._toggle_camera_stream() - self.assertEqual(view._manual_stream_type, NARROW_ROAD_CAM) - view.switch_stream.assert_called_once_with(NARROW_ROAD_CAM) - - def test_toggle_requires_wide_stream(self): - view = self._view() - view.available_streams = [NARROW_ROAD_CAM] - view._toggle_camera_stream() - self.assertIsNone(view._manual_stream_type) - view.switch_stream.assert_not_called() - - def test_manual_view_overrides_speed_auto_switch(self): - view = self._view() - view._stream_type = NARROW_ROAD_CAM - view._manual_stream_type = WIDE_CAM - sm = { - 'carParams': SimpleNamespace(openpilotLongitudinalControl=False), - 'carState': SimpleNamespace(vEgo=30.0), - } - view._switch_stream_if_needed(sm) - view.switch_stream.assert_called_once_with(WIDE_CAM) - - -if __name__ == '__main__': - unittest.main() diff --git a/openpilot/selfdrive/ui/tests/test_camera_view.py b/openpilot/selfdrive/ui/tests/test_camera_view.py new file mode 100644 index 000000000..535fe27ca --- /dev/null +++ b/openpilot/selfdrive/ui/tests/test_camera_view.py @@ -0,0 +1,41 @@ +import unittest + +from openpilot.selfdrive.ui.onroad.augmented_road_view import ( + NARROW_ROAD_CAM, + ROAD_CAMERA_VIEW_AUTO, + ROAD_CAMERA_VIEW_STANDARD, + ROAD_CAMERA_VIEW_WIDE, + WIDE_CAM, + camera_stream_target, +) + + +class TestRoadCameraViewMode(unittest.TestCase): + def test_auto_switches_by_speed(self): + available = [NARROW_ROAD_CAM, WIDE_CAM] + self.assertEqual(camera_stream_target(ROAD_CAMERA_VIEW_AUTO, NARROW_ROAD_CAM, 2.0, True, available), WIDE_CAM) + self.assertEqual(camera_stream_target(ROAD_CAMERA_VIEW_AUTO, WIDE_CAM, 12.0, True, available), NARROW_ROAD_CAM) + self.assertEqual(camera_stream_target(ROAD_CAMERA_VIEW_AUTO, WIDE_CAM, 7.5, True, available), WIDE_CAM) + self.assertEqual(camera_stream_target(ROAD_CAMERA_VIEW_AUTO, NARROW_ROAD_CAM, 7.5, True, available), NARROW_ROAD_CAM) + + def test_wide_mode_overrides_speed(self): + self.assertEqual( + camera_stream_target(ROAD_CAMERA_VIEW_WIDE, NARROW_ROAD_CAM, 30.0, False, [NARROW_ROAD_CAM, WIDE_CAM]), + WIDE_CAM, + ) + + def test_standard_mode_overrides_speed(self): + self.assertEqual( + camera_stream_target(ROAD_CAMERA_VIEW_STANDARD, WIDE_CAM, 0.0, True, [NARROW_ROAD_CAM, WIDE_CAM]), + NARROW_ROAD_CAM, + ) + + def test_missing_wide_stream_falls_back_to_standard(self): + self.assertEqual( + camera_stream_target(ROAD_CAMERA_VIEW_WIDE, NARROW_ROAD_CAM, 0.0, True, [NARROW_ROAD_CAM]), + NARROW_ROAD_CAM, + ) + + +if __name__ == '__main__': + unittest.main() diff --git a/openpilot/selfdrive/ui/translations/app.pot b/openpilot/selfdrive/ui/translations/app.pot index b97ca5354..b9caabff6 100644 --- a/openpilot/selfdrive/ui/translations/app.pot +++ b/openpilot/selfdrive/ui/translations/app.pot @@ -434,6 +434,7 @@ msgid "Aggressive" msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py msgid "Standard" msgstr "" @@ -1064,6 +1065,10 @@ msgstr "" msgid "Neural Network Lateral Control (NNLC)" msgstr "" +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +msgid "Automatic switches between wide and standard views based on vehicle speed. Wide always uses the wide road camera; Standard always uses the narrow road camera." +msgstr "" + #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py msgid "This feature requires sunnypilot longitudinal control to be available." msgstr "" @@ -1140,6 +1145,10 @@ msgstr "" msgid "Real-time Acceleration Bar" msgstr "" +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +msgid "Road Camera View" +msgstr "" + #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py msgid "Display Metrics Below Chevron" msgstr "" @@ -1152,6 +1161,14 @@ msgstr "" msgid "Display real-time parameters and metrics from various sources." msgstr "" +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +msgid "Auto" +msgstr "" + +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +msgid "Wide" +msgstr "" + #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/cruise_sub_layouts/speed_limit_settings.py #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/lane_change_settings.py #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/hyundai.py diff --git a/openpilot/selfdrive/ui/translations/app_en.po b/openpilot/selfdrive/ui/translations/app_en.po index ae3e34692..a3373e1e9 100644 --- a/openpilot/selfdrive/ui/translations/app_en.po +++ b/openpilot/selfdrive/ui/translations/app_en.po @@ -192,6 +192,10 @@ msgstr "" msgid "Assist: Adjusts the vehicle's cruise speed based on the current road's speed limit when operating the +/- buttons." msgstr "" +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +msgid "Auto" +msgstr "Auto" + #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/display.py msgid "Auto (Dark)" msgstr "" @@ -208,6 +212,10 @@ msgstr "" msgid "Auto Lane Change: Delay with Blind Spot" msgstr "" +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +msgid "Automatic switches between wide and standard views based on vehicle speed. Wide always uses the wide road camera; Standard always uses the narrow road camera." +msgstr "Automatic switches between wide and standard views based on vehicle speed. Wide always uses the wide road camera; Standard always uses the narrow road camera." + #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/volkswagen.py msgid "Avoid EPS Lockout" msgstr "" @@ -1611,6 +1619,10 @@ msgstr "" msgid "Right & Bottom" msgstr "" +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +msgid "Road Camera View" +msgstr "Road Camera View" + #: openpilot/selfdrive/ui/layouts/settings/software.py #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/models.py #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/osm.py @@ -1796,6 +1808,7 @@ msgid "Speedometer: Hide from Onroad Screen" msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py msgid "Standard" msgstr "Standard" @@ -2200,6 +2213,10 @@ msgstr "Wi-Fi" msgid "Wi-Fi Network Metered" msgstr "Wi-Fi Network Metered" +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +msgid "Wide" +msgstr "Wide" + #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/developer.py msgid "Would you like to delete this log?" msgstr "" diff --git a/openpilot/selfdrive/ui/translations/app_zh-CHS.po b/openpilot/selfdrive/ui/translations/app_zh-CHS.po index 7d16f50d6..7cf0cccb6 100644 --- a/openpilot/selfdrive/ui/translations/app_zh-CHS.po +++ b/openpilot/selfdrive/ui/translations/app_zh-CHS.po @@ -192,6 +192,10 @@ msgstr "辅助" msgid "Assist: Adjusts the vehicle's cruise speed based on the current road's speed limit when operating the +/- buttons." msgstr "辅助:操作 +/- 按钮时,根据当前道路限速调整车辆巡航速度。" +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +msgid "Auto" +msgstr "自动" + #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/display.py msgid "Auto (Dark)" msgstr "自动(暗)" @@ -208,6 +212,10 @@ msgstr "转向灯自动变道" msgid "Auto Lane Change: Delay with Blind Spot" msgstr "自动变道:盲区延迟" +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +msgid "Automatic switches between wide and standard views based on vehicle speed. Wide always uses the wide road camera; Standard always uses the narrow road camera." +msgstr "自动模式会根据车速在广角和标准视野之间切换。广角模式固定使用广角相机;标准模式固定使用窄角相机。" + #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/vehicle/brands/volkswagen.py msgid "Avoid EPS Lockout" msgstr "避免EPS锁定" @@ -1613,6 +1621,10 @@ msgstr "右侧" msgid "Right & Bottom" msgstr "右侧和底部" +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +msgid "Road Camera View" +msgstr "道路相机视野" + #: openpilot/selfdrive/ui/layouts/settings/software.py #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/models.py #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/osm.py @@ -1798,6 +1810,7 @@ msgid "Speedometer: Hide from Onroad Screen" msgstr "速度表:从行车屏幕隐藏" #: openpilot/selfdrive/ui/layouts/settings/toggles.py +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py msgid "Standard" msgstr "标准" @@ -2205,6 +2218,10 @@ msgstr "Wi‑Fi" msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 计量网络" +#: openpilot/selfdrive/ui/sunnypilot/layouts/settings/visuals.py +msgid "Wide" +msgstr "广角" + #: openpilot/selfdrive/ui/sunnypilot/layouts/settings/developer.py msgid "Would you like to delete this log?" msgstr "您要删除此日志吗?"