mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-08 04:15:51 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e0d89968d | |||
| fec5a97a9e | |||
| 099143ad9d | |||
| 6909aa95ff |
@@ -0,0 +1,44 @@
|
|||||||
|
# AI policy
|
||||||
|
|
||||||
|
## Why this exists
|
||||||
|
|
||||||
|
We use AI tools ourselves, so this isn't an anti-AI stance. The problem is people submitting code, issues, or comments they don't actually understand. AI makes that very easy to do, and it creates real work for reviewers who have to figure out what you meant when you can't explain it yourself.
|
||||||
|
|
||||||
|
If you're not going to put effort into understanding and verifying your submission, we're not going to put effort into reviewing it.
|
||||||
|
|
||||||
|
## The rule
|
||||||
|
|
||||||
|
You are responsible for everything you submit: code, PR descriptions, issues, bug reports, comments.
|
||||||
|
|
||||||
|
1. Understand what you submit. If a reviewer asks why you did something, you answer from your own understanding, not by re-prompting. If you can't do that, don't submit it.
|
||||||
|
|
||||||
|
2. Test your change. AI gets things wrong all the time. Run it, break it, confirm it actually works.
|
||||||
|
|
||||||
|
3. Driving fixes need real evidence. Attach a dongle ID, upload logs, and include segments that show the fix working. A route hash by itself proves nothing.
|
||||||
|
|
||||||
|
4. No AI-generated media (images, diagrams, videos) in issues or PRs.
|
||||||
|
|
||||||
|
## Disclosure
|
||||||
|
|
||||||
|
If AI tools helped you write something, say so. Add an `Assisted-by:` line in your commit message:
|
||||||
|
|
||||||
|
```
|
||||||
|
Assisted-by: GitHub Copilot
|
||||||
|
Assisted-by: Claude
|
||||||
|
```
|
||||||
|
|
||||||
|
Disclosing won't count against your PR. It helps reviewers know where to look. Hiding it and getting caught will.
|
||||||
|
|
||||||
|
## How we review
|
||||||
|
|
||||||
|
Reviewers are looking at whether you understand your own change. Can you explain it? Can you respond to feedback without re-prompting? Does your PR description say why you made the change, not just list what changed?
|
||||||
|
|
||||||
|
Good code from someone who used AI and understands what they wrote is fine. How you got there doesn't matter as long as you can stand behind it.
|
||||||
|
|
||||||
|
## What happens
|
||||||
|
|
||||||
|
Submissions that don't meet this bar get closed. If it keeps happening, you get blocked.
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
Maintainers use AI at their discretion. They've earned that through sustained contribution and they know the codebase.
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
> sunnypilot follows [commaai/openpilot](https://github.com/commaai/openpilot)'s contributing guidelines. The following applies to all contributions here.
|
||||||
|
|
||||||
# How to contribute
|
# How to contribute
|
||||||
|
|
||||||
Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/).
|
Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/).
|
||||||
@@ -35,6 +37,7 @@ All of these are examples of good PRs:
|
|||||||
* **UI design**: we do not have a good review process for this yet
|
* **UI design**: we do not have a good review process for this yet
|
||||||
* **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't.
|
* **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't.
|
||||||
* **Negative expected value**: This is a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged.
|
* **Negative expected value**: This is a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged.
|
||||||
|
* **AI-generated contributions**: see our [AI policy](AI_POLICY.md)
|
||||||
|
|
||||||
### First contribution
|
### First contribution
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule opendbc_repo updated: 4c64e8a95b...063414f63f
@@ -56,6 +56,9 @@ class CarSpecificEvents:
|
|||||||
if self.CP.minEnableSpeed > 0 and CS.vEgo < 0.001:
|
if self.CP.minEnableSpeed > 0 and CS.vEgo < 0.001:
|
||||||
events.add(EventName.manualRestart)
|
events.add(EventName.manualRestart)
|
||||||
|
|
||||||
|
if CS.brakeHoldActive and CS.blockPcmEnable: # set by Nidec Hybrid which cannot resume from brakehold
|
||||||
|
events.add(EventName.belowEngageSpeed)
|
||||||
|
|
||||||
elif self.CP.brand == 'toyota':
|
elif self.CP.brand == 'toyota':
|
||||||
# TODO: when we check for unexpected disengagement, check gear not S1, S2, S3
|
# TODO: when we check for unexpected disengagement, check gear not S1, S2, S3
|
||||||
if self.CP.openpilotLongitudinalControl:
|
if self.CP.openpilotLongitudinalControl:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout
|
|||||||
|
|
||||||
if gui_app.sunnypilot_ui():
|
if gui_app.sunnypilot_ui():
|
||||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.settings import SettingsLayoutSP as SettingsLayout
|
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.settings import SettingsLayoutSP as SettingsLayout
|
||||||
|
from openpilot.selfdrive.ui.sunnypilot.layouts.home import HomeLayoutSP as HomeLayout
|
||||||
|
|
||||||
|
|
||||||
class MainState(IntEnum):
|
class MainState(IntEnum):
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from openpilot.system.ui.lib.application import gui_app
|
|||||||
|
|
||||||
if gui_app.sunnypilot_ui():
|
if gui_app.sunnypilot_ui():
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.settings import SettingsLayoutSP as SettingsLayout
|
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.settings import SettingsLayoutSP as SettingsLayout
|
||||||
|
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.home import MiciHomeLayoutSP as MiciHomeLayout
|
||||||
|
|
||||||
ONROAD_DELAY = 2.5 # seconds
|
ONROAD_DELAY = 2.5 # seconds
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
import pyray as rl
|
||||||
|
from openpilot.selfdrive.ui.layouts.home import HomeLayout, HomeLayoutState, HEAD_BUTTON_FONT_SIZE, SPACING
|
||||||
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||||
|
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||||
|
from openpilot.system.ui.lib.multilang import tr, trn
|
||||||
|
from openpilot.system.ui.widgets.label import gui_label
|
||||||
|
|
||||||
|
BRAND_FONT_SIZE = 48
|
||||||
|
BRAND_DESC_SPACING = 12
|
||||||
|
|
||||||
|
|
||||||
|
class HomeLayoutSP(HomeLayout):
|
||||||
|
def _render_header(self):
|
||||||
|
font = gui_app.font(FontWeight.MEDIUM)
|
||||||
|
|
||||||
|
version_text_width = self.header_rect.width
|
||||||
|
|
||||||
|
if self.update_available:
|
||||||
|
version_text_width -= self.update_notif_rect.width
|
||||||
|
|
||||||
|
highlight_color = rl.Color(75, 95, 255, 255) if self.current_state == HomeLayoutState.UPDATE else rl.Color(54, 77, 239, 255)
|
||||||
|
rl.draw_rectangle_rounded(self.update_notif_rect, 0.3, 10, highlight_color)
|
||||||
|
|
||||||
|
text = tr("UPDATE")
|
||||||
|
text_size = measure_text_cached(font, text, HEAD_BUTTON_FONT_SIZE)
|
||||||
|
text_x = self.update_notif_rect.x + (self.update_notif_rect.width - text_size.x) // 2
|
||||||
|
text_y = self.update_notif_rect.y + (self.update_notif_rect.height - text_size.y) // 2
|
||||||
|
rl.draw_text_ex(font, text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE)
|
||||||
|
|
||||||
|
if self.alert_count > 0:
|
||||||
|
version_text_width -= self.alert_notif_rect.width
|
||||||
|
|
||||||
|
highlight_color = rl.Color(255, 70, 70, 255) if self.current_state == HomeLayoutState.ALERTS else rl.Color(226, 44, 44, 255)
|
||||||
|
rl.draw_rectangle_rounded(self.alert_notif_rect, 0.3, 10, highlight_color)
|
||||||
|
|
||||||
|
alert_text = trn("{} ALERT", "{} ALERTS", self.alert_count).format(self.alert_count)
|
||||||
|
text_size = measure_text_cached(font, alert_text, HEAD_BUTTON_FONT_SIZE)
|
||||||
|
text_x = self.alert_notif_rect.x + (self.alert_notif_rect.width - text_size.x) // 2
|
||||||
|
text_y = self.alert_notif_rect.y + (self.alert_notif_rect.height - text_size.y) // 2
|
||||||
|
rl.draw_text_ex(font, alert_text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE)
|
||||||
|
|
||||||
|
if self.update_available or self.alert_count > 0:
|
||||||
|
version_text_width -= SPACING * 1.5
|
||||||
|
|
||||||
|
version_right = self.header_rect.x + self.header_rect.width
|
||||||
|
version_left = version_right - version_text_width
|
||||||
|
|
||||||
|
brand = "sunnypilot"
|
||||||
|
description = self.params.get("UpdaterCurrentDescription") or ""
|
||||||
|
|
||||||
|
desc_width = 0
|
||||||
|
if description:
|
||||||
|
desc_size = measure_text_cached(gui_app.font(FontWeight.NORMAL), description, BRAND_FONT_SIZE)
|
||||||
|
desc_width = desc_size.x
|
||||||
|
desc_rect = rl.Rectangle(version_right - desc_width, self.header_rect.y, desc_width, self.header_rect.height)
|
||||||
|
gui_label(desc_rect, description, BRAND_FONT_SIZE, rl.WHITE, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT)
|
||||||
|
|
||||||
|
brand_size = measure_text_cached(gui_app.font(FontWeight.AUDIOWIDE), brand, BRAND_FONT_SIZE)
|
||||||
|
spacing = BRAND_DESC_SPACING if description else 0
|
||||||
|
brand_x = version_right - desc_width - spacing - brand_size.x
|
||||||
|
brand_rect = rl.Rectangle(max(version_left, brand_x), self.header_rect.y, brand_size.x, self.header_rect.height)
|
||||||
|
gui_label(brand_rect, brand, BRAND_FONT_SIZE, rl.WHITE, font_weight=FontWeight.AUDIOWIDE)
|
||||||
@@ -20,7 +20,7 @@ class SunnylinkConsentPage(Widget):
|
|||||||
self._done_callback = done_callback
|
self._done_callback = done_callback
|
||||||
self._step = 0
|
self._step = 0
|
||||||
|
|
||||||
self._title = self._child(Label(tr("sunnylink"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT))
|
self._title = self._child(Label(tr("sunnylink"), font_size=90, font_weight=FontWeight.AUDIOWIDE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT))
|
||||||
|
|
||||||
self._content = [
|
self._content = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout
|
||||||
|
from openpilot.system.ui.lib.application import FontWeight
|
||||||
|
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||||
|
|
||||||
|
|
||||||
|
class MiciHomeLayoutSP(MiciHomeLayout):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._openpilot_label = UnifiedLabel("sunnypilot", font_size=88, font_weight=FontWeight.AUDIOWIDE, max_width=480, wrap_text=False)
|
||||||
@@ -4,7 +4,6 @@ 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.
|
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.
|
See the LICENSE.md file in the root directory for more details.
|
||||||
"""
|
"""
|
||||||
from collections.abc import Callable
|
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
|
|
||||||
from openpilot.cereal import custom
|
from openpilot.cereal import custom
|
||||||
@@ -48,10 +47,8 @@ class CurrentModelInfo(Widget):
|
|||||||
self.info_text.render()
|
self.info_text.render()
|
||||||
|
|
||||||
class ModelsLayoutMici(NavScroller):
|
class ModelsLayoutMici(NavScroller):
|
||||||
def __init__(self, back_callback: Callable):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.set_back_callback(back_callback)
|
|
||||||
self.original_back_callback = back_callback
|
|
||||||
self.focused_widget = None
|
self.focused_widget = None
|
||||||
|
|
||||||
self.current_model_info = CurrentModelInfo()
|
self.current_model_info = CurrentModelInfo()
|
||||||
@@ -85,12 +82,10 @@ class ModelsLayoutMici(NavScroller):
|
|||||||
|
|
||||||
return folders
|
return folders
|
||||||
|
|
||||||
def _show_selection_view(self, items, back_callback: Callable):
|
def _push_selection_view(self, items):
|
||||||
self._scroller._items = items
|
scroller = NavScroller()
|
||||||
for item in items:
|
scroller._scroller.add_widgets(items)
|
||||||
item.set_touch_valid_callback(lambda: self._scroller.scroll_panel.is_touch_valid() and self._scroller.enabled)
|
gui_app.push_widget(scroller)
|
||||||
self._scroller.scroll_panel.set_offset(0)
|
|
||||||
self.set_back_callback(back_callback)
|
|
||||||
|
|
||||||
def _show_folders(self):
|
def _show_folders(self):
|
||||||
self.focused_widget = self.select_model_btn
|
self.focused_widget = self.select_model_btn
|
||||||
@@ -112,15 +107,18 @@ class ModelsLayoutMici(NavScroller):
|
|||||||
folder_buttons.insert(0, btn)
|
folder_buttons.insert(0, btn)
|
||||||
else:
|
else:
|
||||||
folder_buttons.append(btn)
|
folder_buttons.append(btn)
|
||||||
self._show_selection_view(folder_buttons, self._reset_main_view)
|
self._push_selection_view(folder_buttons)
|
||||||
|
|
||||||
|
def _pop_to_main(self):
|
||||||
|
gui_app.pop_widgets_to(self)
|
||||||
|
|
||||||
def _select_model(self, bundle):
|
def _select_model(self, bundle):
|
||||||
ui_state.params.put("ModelManager_DownloadIndex", bundle.index)
|
ui_state.params.put("ModelManager_DownloadIndex", bundle.index)
|
||||||
self._reset_main_view()
|
self._pop_to_main()
|
||||||
|
|
||||||
def _select_default(self):
|
def _select_default(self):
|
||||||
ui_state.params.remove("ModelManager_ActiveBundle")
|
ui_state.params.remove("ModelManager_ActiveBundle")
|
||||||
self._reset_main_view()
|
self._pop_to_main()
|
||||||
|
|
||||||
def _select_folder(self, folder_name):
|
def _select_folder(self, folder_name):
|
||||||
favs = ui_state.params.get("ModelManager_Favs")
|
favs = ui_state.params.get("ModelManager_Favs")
|
||||||
@@ -135,13 +133,7 @@ class ModelsLayoutMici(NavScroller):
|
|||||||
btn = BigButton(txt)
|
btn = BigButton(txt)
|
||||||
btn.set_click_callback(lambda b=bundle: self._select_model(b))
|
btn.set_click_callback(lambda b=bundle: self._select_model(b))
|
||||||
btns.append(btn)
|
btns.append(btn)
|
||||||
self._show_selection_view(btns, self._show_folders)
|
self._push_selection_view(btns)
|
||||||
|
|
||||||
def _reset_main_view(self):
|
|
||||||
self._scroller._items = self.main_items # type: ignore[assignment] # ty: ignore[invalid-assignment]
|
|
||||||
self.set_back_callback(self.original_back_callback)
|
|
||||||
self._scroller.scroll_panel.set_offset(0)
|
|
||||||
self._scroller.scroll_to(0)
|
|
||||||
|
|
||||||
def hide_event(self):
|
def hide_event(self):
|
||||||
super().hide_event()
|
super().hide_event()
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ class SettingsLayoutSP(OP.SettingsLayout):
|
|||||||
BIG_ICON_SIZE)
|
BIG_ICON_SIZE)
|
||||||
self.icon_offroad_slider = gui_app.texture("icons_mici/settings/device/lkas.png", BIG_ICON_SIZE, BIG_ICON_SIZE)
|
self.icon_offroad_slider = gui_app.texture("icons_mici/settings/device/lkas.png", BIG_ICON_SIZE, BIG_ICON_SIZE)
|
||||||
|
|
||||||
sunnylink_panel = SunnylinkLayoutMici(back_callback=gui_app.pop_widget)
|
sunnylink_panel = SunnylinkLayoutMici()
|
||||||
sunnylink_btn = SettingsBigButton(tr("sunnylink"), "", gui_app.texture("icons_mici/settings/developer/ssh.png", 55, 55))
|
sunnylink_btn = SettingsBigButton(tr("sunnylink"), "", gui_app.texture("icons_mici/settings/developer/ssh.png", 55, 55))
|
||||||
sunnylink_btn.set_click_callback(lambda: gui_app.push_widget(sunnylink_panel))
|
sunnylink_btn.set_click_callback(lambda: gui_app.push_widget(sunnylink_panel))
|
||||||
|
|
||||||
models_panel = ModelsLayoutMici(back_callback=gui_app.pop_widget)
|
models_panel = ModelsLayoutMici()
|
||||||
models_btn = SettingsBigButton(tr("models"), "", gui_app.texture("../../sunnypilot/selfdrive/assets/offroad/icon_models.png", ICON_SIZE, ICON_SIZE))
|
models_btn = SettingsBigButton(tr("models"), "", gui_app.texture("../../sunnypilot/selfdrive/assets/offroad/icon_models.png", ICON_SIZE, ICON_SIZE))
|
||||||
models_btn.set_click_callback(lambda: gui_app.push_widget(models_panel))
|
models_btn.set_click_callback(lambda: gui_app.push_widget(models_panel))
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ See the LICENSE.md file in the root directory for more details.
|
|||||||
"""
|
"""
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
|
|
||||||
from collections.abc import Callable
|
|
||||||
|
|
||||||
from openpilot.cereal import custom
|
from openpilot.cereal import custom
|
||||||
from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle
|
from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle
|
||||||
@@ -54,9 +53,8 @@ class SunnylinkInfo(Widget):
|
|||||||
self.sponsor_text.render()
|
self.sponsor_text.render()
|
||||||
|
|
||||||
class SunnylinkLayoutMici(NavScroller):
|
class SunnylinkLayoutMici(NavScroller):
|
||||||
def __init__(self, back_callback: Callable):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.set_back_callback(back_callback)
|
|
||||||
self._restore_in_progress = False
|
self._restore_in_progress = False
|
||||||
self._backup_in_progress = False
|
self._backup_in_progress = False
|
||||||
self._sunnylink_enabled = ui_state.params.get("SunnylinkEnabled")
|
self._sunnylink_enabled = ui_state.params.get("SunnylinkEnabled")
|
||||||
|
|||||||
@@ -338,8 +338,11 @@ def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None:
|
|||||||
|
|
||||||
settings_cases: Cases = [
|
settings_cases: Cases = [
|
||||||
lambda: scroll_through_cases(toggle_cases),
|
lambda: scroll_through_cases(toggle_cases),
|
||||||
|
None, # sunnylink (just open and close)
|
||||||
|
None, # models (just open and close)
|
||||||
lambda: scroll_through_cases(network_cases),
|
lambda: scroll_through_cases(network_cases),
|
||||||
lambda: scroll_through_cases(device_cases),
|
lambda: scroll_through_cases(device_cases),
|
||||||
|
lambda: script.wait(WAIT_SHORT), # software
|
||||||
lambda: script.wait(WAIT_SHORT), # pairing
|
lambda: script.wait(WAIT_SHORT), # pairing
|
||||||
lambda: run_actions(lambda: swipe_up(height * 3), lambda: swipe_down(height * 3)), # firehose (scroll down and back up)
|
lambda: run_actions(lambda: swipe_up(height * 3), lambda: swipe_down(height * 3)), # firehose (scroll down and back up)
|
||||||
lambda: scroll_through_cases(developer_cases),
|
lambda: scroll_through_cases(developer_cases),
|
||||||
|
|||||||
Reference in New Issue
Block a user