mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
a82935f326
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services. Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> Co-authored-by: Daniel Koepping <elkoled@gmail.com> Co-authored-by: Danny <dannyirwin31@yahoo.com> Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com> Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com> Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com> Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu> Co-authored-by: Shane Smiskol <shane@smiskol.com> Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com> Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Co-authored-by: stef <19478336+stefpi@users.noreply.github.com> Co-authored-by: whoisdomi <dcantu83@gmail.com> Co-authored-by: Intelli <contact@intelli.software> Co-authored-by: pharmacomaniac <blittle65@gmail.com> Co-authored-by: prabhaavp <prabhaav@msu.edu>
214 lines
10 KiB
Python
214 lines
10 KiB
Python
import os
|
|
import math
|
|
from collections.abc import Callable
|
|
|
|
from openpilot.cereal import messaging, log
|
|
from openpilot.common.basedir import BASEDIR
|
|
from openpilot.common.params import Params
|
|
from openpilot.starpilot.schema_cache import get_cache
|
|
from openpilot.starpilot.ui.calibration_reset import calibration_reset
|
|
from openpilot.common.swaglog import cloudlog
|
|
from openpilot.selfdrive.ui.onroad.cabin_camera_dialog import CabinCameraDialog
|
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
|
from openpilot.selfdrive.ui.layouts.onboarding import TrainingGuide
|
|
from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog
|
|
from openpilot.system.ui.lib.application import FontWeight, gui_app
|
|
from openpilot.system.ui.lib.multilang import multilang, tr, tr_noop
|
|
from openpilot.system.ui.widgets import Widget, DialogResult
|
|
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog
|
|
from openpilot.system.ui.widgets.html_render import HtmlModal
|
|
from openpilot.system.ui.widgets.list_view import text_item, button_item, dual_button_item
|
|
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
|
|
from openpilot.system.ui.widgets.scroller_tici import Scroller
|
|
|
|
# Description constants
|
|
DESCRIPTIONS = {
|
|
'pair_device': tr_noop("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."),
|
|
'cabin_camera': tr_noop("Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)"),
|
|
'reset_driver_monitoring': tr_noop("Clears the saved driver monitoring wheel-side calibration."),
|
|
'reset_calibration': tr_noop("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."),
|
|
'review_guide': tr_noop("Review the rules, features, and limitations of openpilot"),
|
|
}
|
|
|
|
|
|
class DeviceLayout(Widget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self._params = Params()
|
|
self._select_language_dialog: MultiOptionDialog | None = None
|
|
self._fcc_dialog: HtmlModal | None = None
|
|
self._calibration_reset_pending = False
|
|
|
|
items = self._initialize_items()
|
|
self._scroller = self._child(Scroller(items, line_separator=True, spacing=0))
|
|
|
|
ui_state.add_offroad_transition_callback(self._offroad_transition)
|
|
|
|
def _initialize_items(self):
|
|
self._pair_device_btn = button_item(lambda: tr("Pair Device"), lambda: tr("PAIR"), lambda: tr(DESCRIPTIONS['pair_device']),
|
|
callback=lambda: gui_app.push_widget(PairingDialog()))
|
|
self._pair_device_btn.set_visible(lambda: not ui_state.prime_state.is_paired())
|
|
|
|
self._reset_calib_btn = button_item(lambda: tr("Reset Calibration"), lambda: tr("RESET"), lambda: tr(DESCRIPTIONS['reset_calibration']),
|
|
callback=self._reset_calibration_prompt)
|
|
self._reset_calib_btn.set_description_opened_callback(self._update_calib_description)
|
|
self._reset_calib_btn.set_enabled(lambda: not ui_state.engaged and not calibration_reset.pending
|
|
and not self._params.get_bool("OnroadCycleRequested"))
|
|
|
|
self._power_off_btn = dual_button_item(lambda: tr("Reboot"), lambda: tr("Power Off"),
|
|
left_callback=self._reboot_prompt, right_callback=self._power_off_prompt)
|
|
|
|
items = [
|
|
text_item(lambda: tr("Dongle ID"), self._params.get("DongleId") or (lambda: tr("N/A"))),
|
|
text_item(lambda: tr("Serial"), self._params.get("HardwareSerial") or (lambda: tr("N/A"))),
|
|
self._pair_device_btn,
|
|
button_item(lambda: tr("Cabin Camera"), lambda: tr("PREVIEW"), lambda: tr(DESCRIPTIONS['cabin_camera']),
|
|
callback=lambda: gui_app.push_widget(CabinCameraDialog()), enabled=ui_state.is_offroad),
|
|
button_item(lambda: tr("Reset Driver Monitoring"), lambda: tr("RESET"), lambda: tr(DESCRIPTIONS['reset_driver_monitoring']),
|
|
callback=self._reset_driver_monitoring_prompt, enabled=self._can_reset_driver_monitoring),
|
|
self._reset_calib_btn,
|
|
button_item(lambda: tr("Review Training Guide"), lambda: tr("REVIEW"), lambda: tr(DESCRIPTIONS['review_guide']),
|
|
self._on_review_training_guide, enabled=ui_state.is_offroad),
|
|
button_item(lambda: tr("Regulatory"), lambda: tr("VIEW"), callback=self._on_regulatory, enabled=ui_state.is_offroad),
|
|
button_item(lambda: tr("Change Language"), lambda: tr("CHANGE"), callback=self._show_language_dialog),
|
|
self._power_off_btn,
|
|
]
|
|
return items
|
|
|
|
def _offroad_transition(self):
|
|
self._power_off_btn.action_item.right_button.set_visible(ui_state.is_offroad())
|
|
|
|
def _render(self, rect):
|
|
self._scroller.render(rect)
|
|
|
|
def _update_state(self):
|
|
super()._update_state()
|
|
if self._calibration_reset_pending and not calibration_reset.pending:
|
|
self._calibration_reset_pending = False
|
|
self._update_calib_description()
|
|
if error := calibration_reset.take_error():
|
|
gui_app.push_widget(alert_dialog(tr(error)))
|
|
|
|
def _show_language_dialog(self):
|
|
def handle_language_selection(result: DialogResult):
|
|
if result == DialogResult.CONFIRM and self._select_language_dialog:
|
|
selected_language = multilang.languages[self._select_language_dialog.selection]
|
|
multilang.change_language(selected_language)
|
|
self._update_calib_description()
|
|
self._select_language_dialog = None
|
|
|
|
self._select_language_dialog = MultiOptionDialog(tr("Select a language"), multilang.languages, multilang.codes[multilang.language],
|
|
option_font_weight=FontWeight.UNIFONT, callback=handle_language_selection)
|
|
gui_app.push_widget(self._select_language_dialog)
|
|
|
|
def _reset_calibration_prompt(self, action_guard: Callable[[], bool] | None = None):
|
|
if ui_state.engaged:
|
|
gui_app.push_widget(alert_dialog(tr("Disengage to Reset Calibration")))
|
|
return
|
|
|
|
def reset_calibration(result: DialogResult):
|
|
# Check engaged again in case it changed while the dialog was open
|
|
if ui_state.engaged or result != DialogResult.CONFIRM or (action_guard is not None and not action_guard()):
|
|
return
|
|
|
|
self._calibration_reset_pending = calibration_reset.request(self._params) or self._calibration_reset_pending
|
|
|
|
dialog = ConfirmDialog(tr("Are you sure you want to reset calibration?"), tr("Reset"), callback=reset_calibration)
|
|
gui_app.push_widget(dialog)
|
|
|
|
def _can_reset_driver_monitoring(self) -> bool:
|
|
return ui_state.is_offroad() and not ui_state.engaged and not self._params.get_bool("IsDriverViewEnabled")
|
|
|
|
def _reset_driver_monitoring_prompt(self):
|
|
if not self._can_reset_driver_monitoring():
|
|
return
|
|
|
|
def reset_driver_monitoring(result: DialogResult):
|
|
if result == DialogResult.CONFIRM and self._can_reset_driver_monitoring():
|
|
self._params.remove("IsRhdDetected")
|
|
self._params.put_bool("OnroadCycleRequested", True, block=True)
|
|
|
|
gui_app.push_widget(ConfirmDialog(tr("Are you sure you want to reset driver monitoring calibration?"), tr("Reset"),
|
|
callback=reset_driver_monitoring))
|
|
|
|
def _update_calib_description(self):
|
|
desc = tr(DESCRIPTIONS['reset_calibration'])
|
|
|
|
calib_bytes = get_cache(self._params, "CalibrationParams")
|
|
if calib_bytes:
|
|
try:
|
|
calib = messaging.log_from_bytes(calib_bytes, log.Event).extrinsicsCalibration
|
|
|
|
if calib.calStatus != log.ExtrinsicsCalibration.Status.uncalibrated:
|
|
pitch = math.degrees(calib.rpyCalib[1])
|
|
yaw = math.degrees(calib.rpyCalib[2])
|
|
desc += tr(" Your device is pointed {:.1f}° {} and {:.1f}° {}.").format(abs(pitch), tr("down") if pitch > 0 else tr("up"),
|
|
abs(yaw), tr("left") if yaw > 0 else tr("right"))
|
|
except Exception:
|
|
cloudlog.exception("invalid CalibrationParams")
|
|
|
|
lag_perc = 0
|
|
lag_bytes = get_cache(self._params, "LiveDelay")
|
|
if lag_bytes:
|
|
try:
|
|
lag_perc = messaging.log_from_bytes(lag_bytes, log.Event).lateralDelay.calPerc
|
|
except Exception:
|
|
cloudlog.exception("invalid LiveDelay")
|
|
if lag_perc < 100:
|
|
desc += tr("<br><br>Steering lag calibration is {}% complete.").format(lag_perc)
|
|
else:
|
|
desc += tr("<br><br>Steering lag calibration is complete.")
|
|
|
|
torque_bytes = get_cache(self._params, "LiveTorqueParameters")
|
|
if torque_bytes:
|
|
try:
|
|
torque = messaging.log_from_bytes(torque_bytes, log.Event).lateralTorqueParameters
|
|
# don't add for non-torque cars
|
|
if torque.useParams:
|
|
torque_perc = torque.calPerc
|
|
if torque_perc < 100:
|
|
desc += tr(" Steering torque response calibration is {}% complete.").format(torque_perc)
|
|
else:
|
|
desc += tr(" Steering torque response calibration is complete.")
|
|
except Exception:
|
|
cloudlog.exception("invalid LiveTorqueParameters")
|
|
|
|
desc += "<br><br>"
|
|
desc += tr("openpilot is continuously calibrating, resetting is rarely required. " +
|
|
"Resetting calibration will restart openpilot if the car is powered on.")
|
|
|
|
self._reset_calib_btn.set_description(desc)
|
|
|
|
def _reboot_prompt(self, action_guard: Callable[[], bool] | None = None):
|
|
if ui_state.engaged:
|
|
gui_app.push_widget(alert_dialog(tr("Disengage to Reboot")))
|
|
return
|
|
|
|
def perform_reboot(result: DialogResult):
|
|
if not ui_state.engaged and result == DialogResult.CONFIRM and (action_guard is None or action_guard()):
|
|
self._params.put_bool("DoReboot", True)
|
|
|
|
dialog = ConfirmDialog(tr("Are you sure you want to reboot?"), tr("Reboot"), callback=perform_reboot)
|
|
gui_app.push_widget(dialog)
|
|
|
|
def _power_off_prompt(self, action_guard: Callable[[], bool] | None = None):
|
|
if ui_state.engaged:
|
|
gui_app.push_widget(alert_dialog(tr("Disengage to Power Off")))
|
|
return
|
|
|
|
def perform_power_off(result: DialogResult):
|
|
if not ui_state.engaged and result == DialogResult.CONFIRM and (action_guard is None or action_guard()):
|
|
self._params.put_bool("DoShutdown", True)
|
|
|
|
dialog = ConfirmDialog(tr("Are you sure you want to power off?"), tr("Power Off"), callback=perform_power_off)
|
|
gui_app.push_widget(dialog)
|
|
|
|
def _on_regulatory(self):
|
|
if not self._fcc_dialog:
|
|
self._fcc_dialog = HtmlModal(os.path.join(BASEDIR, "openpilot/selfdrive/assets/offroad/fcc.html"))
|
|
gui_app.push_widget(self._fcc_dialog)
|
|
|
|
def _on_review_training_guide(self):
|
|
gui_app.push_widget(TrainingGuide())
|