From 1b590b15d625cd1cb0d096bcb4fac2eb67412a20 Mon Sep 17 00:00:00 2001 From: James <91348155+FrogAi@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:00:00 -0700 Subject: [PATCH] FrogPilot base --- cereal/messaging/__init__.py | 6 ++ cereal/services.py | 2 + common/constants.py | 2 + common/params.cc | 9 ++ common/params.h | 6 ++ common/params_keys.h | 2 + common/params_pyx.pyx | 13 +++ common/util.h | 2 + frogpilot/common/frogpilot_functions.py | 26 +++++ frogpilot/frogpilot_process.py | 82 +++++++++++++++ frogpilot/system/environment_variables | Bin 0 -> 144260 bytes frogpilot/ui/frogpilot_ui.cc | 35 +++++++ frogpilot/ui/frogpilot_ui.h | 36 +++++++ .../qt/onroad/frogpilot_annotated_camera.cc | 46 +++++++++ .../ui/qt/onroad/frogpilot_annotated_camera.h | 52 ++++++++++ frogpilot/ui/qt/onroad/frogpilot_buttons.cc | 1 + frogpilot/ui/qt/onroad/frogpilot_buttons.h | 3 + frogpilot/ui/qt/onroad/frogpilot_onroad.cc | 26 +++++ frogpilot/ui/qt/onroad/frogpilot_onroad.h | 24 +++++ frogpilot/ui/qt/widgets/frogpilot_controls.cc | 94 ++++++++++++++++++ frogpilot/ui/qt/widgets/frogpilot_controls.h | 19 ++++ launch_chffrplus.sh | 2 + launch_env.sh | 3 + opendbc_repo/opendbc/car/body/carstate.py | 2 + opendbc_repo/opendbc/car/car_helpers.py | 4 + opendbc_repo/opendbc/car/chrysler/carstate.py | 8 +- opendbc_repo/opendbc/car/ford/carstate.py | 2 + opendbc_repo/opendbc/car/gm/carcontroller.py | 2 + opendbc_repo/opendbc/car/gm/carstate.py | 2 + opendbc_repo/opendbc/car/gm/fingerprints.py | 1 + opendbc_repo/opendbc/car/gm/interface.py | 2 + opendbc_repo/opendbc/car/gm/values.py | 3 + opendbc_repo/opendbc/car/honda/carstate.py | 2 + opendbc_repo/opendbc/car/honda/interface.py | 0 opendbc_repo/opendbc/car/hyundai/carstate.py | 4 + opendbc_repo/opendbc/car/hyundai/values.py | 3 + opendbc_repo/opendbc/car/interfaces.py | 13 +++ opendbc_repo/opendbc/car/mazda/carstate.py | 2 + opendbc_repo/opendbc/car/mazda/interface.py | 2 + opendbc_repo/opendbc/car/nissan/carstate.py | 8 +- opendbc_repo/opendbc/car/psa/carstate.py | 3 + opendbc_repo/opendbc/car/rivian/carstate.py | 2 + .../opendbc/car/subaru/carcontroller.py | 11 ++ opendbc_repo/opendbc/car/subaru/carstate.py | 2 + opendbc_repo/opendbc/car/subaru/subarucan.py | 3 + opendbc_repo/opendbc/car/subaru/values.py | 1 + opendbc_repo/opendbc/car/tesla/carstate.py | 2 + .../opendbc/car/torque_data/params.toml | 2 + .../opendbc/car/torque_data/substitute.toml | 2 + .../opendbc/car/toyota/carcontroller.py | 7 ++ opendbc_repo/opendbc/car/toyota/carstate.py | 5 + opendbc_repo/opendbc/car/toyota/values.py | 3 + .../opendbc/car/volkswagen/carstate.py | 6 ++ opendbc_repo/opendbc/safety/__init__.py | 2 + opendbc_repo/opendbc/safety/declarations.h | 4 + opendbc_repo/opendbc/safety/modes/chrysler.h | 2 + opendbc_repo/opendbc/safety/modes/ford.h | 2 + opendbc_repo/opendbc/safety/modes/gm.h | 2 + opendbc_repo/opendbc/safety/modes/hyundai.h | 10 ++ .../opendbc/safety/modes/hyundai_canfd.h | 4 + .../opendbc/safety/modes/hyundai_common.h | 10 ++ opendbc_repo/opendbc/safety/modes/mazda.h | 2 + opendbc_repo/opendbc/safety/modes/nissan.h | 2 + opendbc_repo/opendbc/safety/modes/psa.h | 8 ++ opendbc_repo/opendbc/safety/modes/rivian.h | 2 + opendbc_repo/opendbc/safety/modes/subaru.h | 2 + .../opendbc/safety/modes/subaru_preglobal.h | 2 + opendbc_repo/opendbc/safety/modes/tesla.h | 2 + opendbc_repo/opendbc/safety/modes/toyota.h | 3 + opendbc_repo/opendbc/safety/safety.h | 6 ++ opendbc_repo/opendbc/safety/tests/common.py | 4 + .../opendbc/safety/tests/hyundai_common.py | 2 + .../opendbc/safety/tests/test_chrysler.py | 2 + .../opendbc/safety/tests/test_ford.py | 2 + opendbc_repo/opendbc/safety/tests/test_gm.py | 2 + .../opendbc/safety/tests/test_honda.py | 3 + .../safety/tests/test_hyundai_canfd.py | 5 + .../opendbc/safety/tests/test_mazda.py | 2 + .../opendbc/safety/tests/test_nissan.py | 4 + .../opendbc/safety/tests/test_subaru.py | 2 + .../safety/tests/test_subaru_preglobal.py | 2 + .../opendbc/safety/tests/test_tesla.py | 2 + .../opendbc/safety/tests/test_toyota.py | 3 + .../safety/tests/test_volkswagen_mqb.py | 2 + .../safety/tests/test_volkswagen_pq.py | 2 + selfdrive/car/card.py | 13 +++ selfdrive/car/cruise.py | 2 + selfdrive/controls/controlsd.py | 4 + selfdrive/controls/lib/desire_helper.py | 12 +++ .../controls/lib/longitudinal_planner.py | 3 + selfdrive/controls/plannerd.py | 4 + selfdrive/controls/radard.py | 17 ++++ selfdrive/locationd/lagd.py | 4 + selfdrive/locationd/paramsd.py | 4 + selfdrive/locationd/torqued.py | 4 + selfdrive/modeld/modeld.py | 8 ++ selfdrive/monitoring/dmonitoringd.py | 3 + selfdrive/pandad/panda_safety.cc | 4 + selfdrive/pandad/pandad.cc | 8 +- selfdrive/selfdrived/events.py | 12 +++ selfdrive/selfdrived/selfdrived.py | 12 +++ selfdrive/ui/SConscript | 11 ++ selfdrive/ui/qt/home.cc | 23 ++++- selfdrive/ui/qt/home.h | 6 +- selfdrive/ui/qt/network/wifi_manager.h | 2 + selfdrive/ui/qt/offroad/developer_panel.cc | 10 ++ selfdrive/ui/qt/offroad/developer_panel.h | 4 + selfdrive/ui/qt/offroad/settings.cc | 19 +++- selfdrive/ui/qt/offroad/settings.h | 11 ++ selfdrive/ui/qt/offroad/software_settings.cc | 15 +++ selfdrive/ui/qt/onroad/alerts.cc | 19 +++- selfdrive/ui/qt/onroad/alerts.h | 7 +- selfdrive/ui/qt/onroad/annotated_camera.cc | 24 ++++- selfdrive/ui/qt/onroad/annotated_camera.h | 11 +- selfdrive/ui/qt/onroad/buttons.cc | 12 ++- selfdrive/ui/qt/onroad/buttons.h | 8 +- selfdrive/ui/qt/onroad/driver_monitoring.cc | 9 ++ selfdrive/ui/qt/onroad/driver_monitoring.h | 5 + selfdrive/ui/qt/onroad/hud.cc | 9 ++ selfdrive/ui/qt/onroad/hud.h | 5 + selfdrive/ui/qt/onroad/model.cc | 19 +++- selfdrive/ui/qt/onroad/model.h | 9 ++ selfdrive/ui/qt/onroad/onroad_home.cc | 48 ++++++++- selfdrive/ui/qt/onroad/onroad_home.h | 10 +- selfdrive/ui/qt/sidebar.cc | 23 ++++- selfdrive/ui/qt/sidebar.h | 11 +- selfdrive/ui/qt/widgets/controls.h | 4 + selfdrive/ui/qt/widgets/input.cc | 7 ++ selfdrive/ui/qt/widgets/input.h | 4 + selfdrive/ui/qt/widgets/ssh_keys.cc | 1 + selfdrive/ui/qt/window.cc | 4 + selfdrive/ui/qt/window.h | 2 + selfdrive/ui/soundd.py | 16 +++ selfdrive/ui/ui.cc | 58 +++++++++-- selfdrive/ui/ui.h | 18 +++- system/hardware/hardwared.py | 10 ++ system/hardware/hw.h | 9 +- system/loggerd/config.py | 3 + system/loggerd/uploader.py | 5 + system/manager/manager.py | 18 +++- system/manager/process_config.py | 14 ++- system/timed.py | 9 ++ system/ui/spinner.py | 2 + system/updated/updated.py | 6 ++ 144 files changed, 1297 insertions(+), 41 deletions(-) create mode 100644 frogpilot/common/frogpilot_functions.py create mode 100644 frogpilot/frogpilot_process.py create mode 100755 frogpilot/system/environment_variables create mode 100644 frogpilot/ui/frogpilot_ui.cc create mode 100644 frogpilot/ui/frogpilot_ui.h create mode 100644 frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc create mode 100644 frogpilot/ui/qt/onroad/frogpilot_annotated_camera.h create mode 100644 frogpilot/ui/qt/onroad/frogpilot_buttons.cc create mode 100644 frogpilot/ui/qt/onroad/frogpilot_buttons.h create mode 100644 frogpilot/ui/qt/onroad/frogpilot_onroad.cc create mode 100644 frogpilot/ui/qt/onroad/frogpilot_onroad.h create mode 100644 frogpilot/ui/qt/widgets/frogpilot_controls.cc create mode 100644 frogpilot/ui/qt/widgets/frogpilot_controls.h mode change 100755 => 100644 opendbc_repo/opendbc/car/honda/interface.py mode change 100755 => 100644 selfdrive/car/card.py diff --git a/cereal/messaging/__init__.py b/cereal/messaging/__init__.py index 0ad846f0..275bcbfb 100644 --- a/cereal/messaging/__init__.py +++ b/cereal/messaging/__init__.py @@ -197,6 +197,8 @@ class SubMaster: self.data[s] = getattr(data.as_reader(), s) self.freq_tracker[s] = FrequencyTracker(SERVICE_LIST[s].frequency, self.update_freq, s == poll) + # FrogPilot variables + def __getitem__(self, s: str) -> capnp.lib.capnp._DynamicStructReader: return self.data[s] @@ -248,6 +250,8 @@ class SubMaster: def all_checks(self, service_list: Optional[List[str]] = None) -> bool: return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list) + # FrogPilot variables + class PubMaster: def __init__(self, services: List[str]): @@ -269,3 +273,5 @@ class PubMaster: def all_readers_updated(self, s: str) -> bool: return self.sock[s].all_readers_updated() # type: ignore + + # FrogPilot variables diff --git a/cereal/services.py b/cereal/services.py index e7350ace..ea99e8c5 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -102,6 +102,8 @@ _services: dict[str, tuple] = { "customReservedRawData0": (True, 0.), "customReservedRawData1": (True, 0.), "customReservedRawData2": (True, 0.), + + # FrogPilot variables } SERVICE_LIST = {name: Service(*vals) for idx, (name, vals) in enumerate(_services.items())} diff --git a/common/constants.py b/common/constants.py index 7ca425c4..1653076b 100644 --- a/common/constants.py +++ b/common/constants.py @@ -19,5 +19,7 @@ class CV: # Mass LB_TO_KG = 0.453592 + # FrogPilot variables + ACCELERATION_DUE_TO_GRAVITY = 9.81 # m/s^2 diff --git a/common/params.cc b/common/params.cc index 6af00fe9..7ce03158 100644 --- a/common/params.cc +++ b/common/params.cc @@ -94,6 +94,8 @@ private: Params::Params(const std::string &path) { params_prefix = "/" + util::getenv("OPENPILOT_PREFIX", "d"); params_path = ensure_params_path(params_prefix, path); + + // FrogPilot variables } Params::~Params() { @@ -169,6 +171,9 @@ int Params::put(const char* key, const char* value, size_t value_size) { int Params::remove(const std::string &key) { FileLock file_lock(params_path + "/.lock"); int result = unlink(getParamPath(key).c_str()); + + // FrogPilot variables + if (result != 0) { return result; } @@ -215,6 +220,8 @@ void Params::clearAll(ParamKeyFlag key_flag) { auto it = keys.find(de->d_name); if (it == keys.end() || (it->second.flags & key_flag)) { unlink(getParamPath(de->d_name).c_str()); + + // FrogPilot variables } } } @@ -240,3 +247,5 @@ void Params::asyncWriteThread() { put(p.first, p.second); } } + +// FrogPilot variables diff --git a/common/params.h b/common/params.h index 8169063a..d6b13030 100644 --- a/common/params.h +++ b/common/params.h @@ -35,6 +35,8 @@ struct ParamKeyAttributes { uint32_t flags; ParamKeyType type; std::optional default_value = std::nullopt; + + // FrogPilot variables }; class Params { @@ -78,6 +80,8 @@ public: putNonBlocking(key, val ? "1" : "0"); } + // FrogPilot variables + private: void asyncWriteThread(); @@ -87,4 +91,6 @@ private: // for nonblocking write std::future future; SafeQueue> queue; + + // FrogPilot variables }; diff --git a/common/params_keys.h b/common/params_keys.h index b71f6298..e7979f51 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -131,4 +131,6 @@ inline static std::unordered_map keys = { {"UptimeOffroad", {PERSISTENT, FLOAT, "0.0"}}, {"UptimeOnroad", {PERSISTENT, FLOAT, "0.0"}}, {"Version", {PERSISTENT, STRING}}, + + // FrogPilot variables }; diff --git a/common/params_pyx.pyx b/common/params_pyx.pyx index 93c550f2..5e562b2b 100644 --- a/common/params_pyx.pyx +++ b/common/params_pyx.pyx @@ -20,6 +20,8 @@ cdef extern from "common/params.h": CLEAR_ON_IGNITION_ON ALL + # FrogPilot variables + cpdef enum ParamKeyType: STRING BOOL @@ -45,6 +47,8 @@ cdef extern from "common/params.h": void clearAll(ParamKeyFlag) vector[string] allKeys() + # FrogPilot variables + PYTHON_2_CPP = { (str, STRING): lambda v: v, (builtins.bool, BOOL): lambda v: "1" if v else "0", @@ -75,12 +79,19 @@ cdef class Params: cdef c_Params* p cdef str d + # FrogPilot variables + def __cinit__(self, d=""): cdef string path = d.encode() + + # FrogPilot variables + with nogil: self.p = new c_Params(path) self.d = d + # FrogPilot variables + def __reduce__(self): return (type(self), (self.d,)) @@ -194,3 +205,5 @@ cdef class Params: cdef string k = self.check_key(key) cdef ParamKeyType t = self.p.getKeyType(k) return self._cpp2python(t, value, None, key) + + # FrogPilot variables diff --git a/common/util.h b/common/util.h index f46db4d9..7bccadb8 100644 --- a/common/util.h +++ b/common/util.h @@ -37,6 +37,8 @@ const double MS_TO_MPH = MS_TO_KPH * KM_TO_MILE; const double METER_TO_MILE = KM_TO_MILE / 1000.0; const double METER_TO_FOOT = 3.28084; +// FrogPilot variables + #define ALIGNED_SIZE(x, align) (((x) + (align)-1) & ~((align)-1)) namespace util { diff --git a/frogpilot/common/frogpilot_functions.py b/frogpilot/common/frogpilot_functions.py new file mode 100644 index 00000000..e5fc603f --- /dev/null +++ b/frogpilot/common/frogpilot_functions.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import threading +import time + +from openpilot.common.time_helpers import system_time_valid +from openpilot.system.hardware import HARDWARE + + +def frogpilot_boot_functions(): + def boot_thread(): + while not system_time_valid(): + print("Waiting for system time to become valid...") + time.sleep(1) + + threading.Thread(target=boot_thread, daemon=True).start() + + +def install_frogpilot(): + paths = [ + ] + for path in paths: + path.mkdir(parents=True, exist_ok=True) + + +def uninstall_frogpilot(): + HARDWARE.uninstall() diff --git a/frogpilot/frogpilot_process.py b/frogpilot/frogpilot_process.py new file mode 100644 index 00000000..240d2ef9 --- /dev/null +++ b/frogpilot/frogpilot_process.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +import datetime +import time + +from cereal import messaging +from openpilot.common.params import Params +from openpilot.common.realtime import DT_MDL, Priority, Ratekeeper, config_realtime_process +from openpilot.common.time_helpers import system_time_valid + +ASSET_CHECK_RATE = (1 / DT_MDL) + +def check_assets(): + +def transition_offroad(time_validated, sm, params): + +def transition_onroad(): + +def update_checks(now, params, boot_run=False): + while not (is_url_pingable("https://github.com") or is_url_pingable("https://gitlab.com")): + time.sleep(60) + + time.sleep(1) + +def frogpilot_thread(): + rate_keeper = Ratekeeper(1 / DT_MDL, None) + + config_realtime_process(5, Priority.CTRL_LOW) + + sm = messaging.SubMaster(["carControl", "carState", "controlsState", "deviceState", "driverMonitoringState", + "gpsLocation", "gpsLocationExternal", "liveParameters", "managerState", "modelV2", + "onroadEvents", "pandaStates", "radarState", "selfdriveState"], + poll="modelV2") + + params = Params() + + run_update_checks = False + started_previously = False + time_validated = False + + while True: + sm.update() + + now = datetime.datetime.now(datetime.timezone.utc) + + started = sm["deviceState"].started + + if not started and started_previously: + transition_offroad(time_validated, sm, params) + + run_update_checks = True + elif started and not started_previously: + transition_onroad() + + if started and sm.updated["modelV2"]: + elif not started: + + started_previously = started + + if rate_keeper.frame % ASSET_CHECK_RATE == 0: + check_assets() + + run_update_checks |= now.second == 0 and (now.minute % 60 == 0) + run_update_checks &= time_validated + + if run_update_checks: + thread_manager.run_with_lock(update_checks, (now, params)) + + run_update_checks = False + elif not time_validated: + time_validated = system_time_valid() + if not time_validated: + continue + + thread_manager.run_with_lock(update_checks, (now, params, True)) + + rate_keeper.keep_time() + +def main(): + frogpilot_thread() + +if __name__ == "__main__": + main() diff --git a/frogpilot/system/environment_variables b/frogpilot/system/environment_variables new file mode 100755 index 0000000000000000000000000000000000000000..088ed20a3e96b3c25dbace320d09158cbb0071af GIT binary patch literal 144260 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Πo{VZ<3^Zn&}vgH{=jEz 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 + + SubMaster &fpsm = *(fs->sm); + fpsm.update(0); + + if (fpsm.updated("deviceState")) { + const cereal::DeviceState::Reader &deviceState = fpsm["deviceState"].getDeviceState(); + } + if (fpsm.updated("selfdriveState")) { + const cereal::SelfdriveState::Reader &selfdriveState = fpsm["selfdriveState"].getSelfdriveState(); + frogpilot_scene.enabled = selfdriveState.getEnabled(); + } +} + +FrogPilotUIState::FrogPilotUIState(QObject *parent) : QObject(parent) { + sm = std::make_unique>({ + "carControl", "deviceState", + "liveDelay", + "liveParameters", "liveTorqueParameters", "liveTracks", "selfdriveState" + }); + + wifi = new WifiManager(this); +} + +FrogPilotUIState *frogpilotUIState() { + static FrogPilotUIState frogpilot_ui_state; + return &frogpilot_ui_state; +} + +void FrogPilotUIState::update() { + update_state(this); +} diff --git a/frogpilot/ui/frogpilot_ui.h b/frogpilot/ui/frogpilot_ui.h new file mode 100644 index 00000000..a784f33d --- /dev/null +++ b/frogpilot/ui/frogpilot_ui.h @@ -0,0 +1,36 @@ +#pragma once + +#include "cereal/messaging/messaging.h" +#include "selfdrive/ui/qt/network/wifi_manager.h" + +#include "frogpilot/ui/qt/widgets/frogpilot_controls.h" + +struct FrogPilotUIScene { + bool enabled; + bool parked; + bool reverse; + bool standstill; + + int started_timer; +}; + +class FrogPilotUIState : public QObject { + Q_OBJECT + +public: + explicit FrogPilotUIState(QObject *parent = nullptr); + + void update(); + + std::unique_ptr sm; + + FrogPilotUIScene frogpilot_scene; + + Params params; + + WifiManager *wifi; + +signals: +}; + +FrogPilotUIState *frogpilotUIState(); diff --git a/frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc b/frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc new file mode 100644 index 00000000..344211a3 --- /dev/null +++ b/frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc @@ -0,0 +1,46 @@ +#include "frogpilot/ui/qt/onroad/frogpilot_annotated_camera.h" + +FrogPilotAnnotatedCameraWidget::FrogPilotAnnotatedCameraWidget(QWidget *parent) : QWidget(parent) { + QSize iconSize(img_size / 4, img_size / 4); +} + +void FrogPilotAnnotatedCameraWidget::showEvent(QShowEvent *event) { +} + +void FrogPilotAnnotatedCameraWidget::updateState(const UIState &s, const FrogPilotUIState &fs) { + const UIScene &scene = s.scene; + + const SubMaster &sm = *(s.sm); + const SubMaster &fpsm = *(fs.sm); + + const cereal::CarState::Reader &carState = sm["carState"].getCarState(); + const cereal::ModelDataV2::Reader &modelV2 = sm["modelV2"].getModelV2(); + const cereal::SelfdriveState::Reader &selfdriveState = sm["selfdriveState"].getSelfdriveState(); + + if (scene.is_metric || frogpilot_toggles.value("use_si_metrics").toBool()) { + leadDistanceUnit = tr(" meters"); + leadSpeedUnit = frogpilot_toggles.value("use_si_metrics").toBool() ? tr(" m/s") : tr(" km/h"); + speedUnit = scene.is_metric ? tr("km/h") : tr("mph"); + + distanceConversion = 1.0f; + speedConversion = scene.is_metric ? MS_TO_KPH : MS_TO_MPH; + speedConversionMetrics = frogpilot_toggles.value("use_si_metrics").toBool() ? 1.0f : MS_TO_KPH; + } else { + leadDistanceUnit = tr(" feet"); + leadSpeedUnit = tr(" mph"); + speedUnit = tr("mph"); + + distanceConversion = METER_TO_FOOT; + speedConversion = MS_TO_MPH; + speedConversionMetrics = MS_TO_MPH; + } + + hideBottomIcons = selfdriveState.getAlertSize() != cereal::SelfdriveState::AlertSize::NONE; +} + +void FrogPilotAnnotatedCameraWidget::mousePressEvent(QMouseEvent *mouseEvent) { + mouseEvent->ignore(); +} + +void FrogPilotAnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p, UIState &s) { +} diff --git a/frogpilot/ui/qt/onroad/frogpilot_annotated_camera.h b/frogpilot/ui/qt/onroad/frogpilot_annotated_camera.h new file mode 100644 index 00000000..527b02cc --- /dev/null +++ b/frogpilot/ui/qt/onroad/frogpilot_annotated_camera.h @@ -0,0 +1,52 @@ +#pragma once + +#include "selfdrive/ui/qt/onroad/buttons.h" +#include "selfdrive/ui/qt/widgets/cameraview.h" + +const int widget_size = img_size + (UI_BORDER_SIZE / 2); + +class FrogPilotAnnotatedCameraWidget : public QWidget { + Q_OBJECT + +public: + explicit FrogPilotAnnotatedCameraWidget(QWidget *parent = 0); + + void mousePressEvent(QMouseEvent *mouseEvent) override; + void paintFrogPilotWidgets(QPainter &p, UIState &s); + void updateState(const UIState &s, const FrogPilotUIState &fs); + + bool hideBottomIcons; + bool isCruiseSet; + bool rightHandDM; + + float speed; + + FrogPilotUIScene frogpilot_scene; + + QColor blueColor(int alpha = 255) { return QColor(0, 0, 255, alpha); } + QColor purpleColor(int alpha = 255) { return QColor(128, 0, 128, alpha); } + QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); } + + QPoint dmIconPosition; + QPoint experimentalButtonPosition; + + QRect setSpeedRect; + + QSize defaultSize; + +protected: + void showEvent(QShowEvent *event) override; + +private: + float distanceConversion; + float setSpeed; + float speedConversion; + float speedConversionMetrics; + + QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); } + QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); } + + QString leadDistanceUnit; + QString leadSpeedUnit; + QString speedUnit; +}; diff --git a/frogpilot/ui/qt/onroad/frogpilot_buttons.cc b/frogpilot/ui/qt/onroad/frogpilot_buttons.cc new file mode 100644 index 00000000..dcf07008 --- /dev/null +++ b/frogpilot/ui/qt/onroad/frogpilot_buttons.cc @@ -0,0 +1 @@ +#include "frogpilot/ui/qt/onroad/frogpilot_buttons.h" diff --git a/frogpilot/ui/qt/onroad/frogpilot_buttons.h b/frogpilot/ui/qt/onroad/frogpilot_buttons.h new file mode 100644 index 00000000..d427996f --- /dev/null +++ b/frogpilot/ui/qt/onroad/frogpilot_buttons.h @@ -0,0 +1,3 @@ +#pragma once + +#include "selfdrive/ui/qt/onroad/buttons.h" diff --git a/frogpilot/ui/qt/onroad/frogpilot_onroad.cc b/frogpilot/ui/qt/onroad/frogpilot_onroad.cc new file mode 100644 index 00000000..e9e0ea85 --- /dev/null +++ b/frogpilot/ui/qt/onroad/frogpilot_onroad.cc @@ -0,0 +1,26 @@ +#include "frogpilot/ui/qt/onroad/frogpilot_onroad.h" + +FrogPilotOnroadWindow::FrogPilotOnroadWindow(QWidget *parent) : QWidget(parent) { +} + +void FrogPilotOnroadWindow::resizeEvent(QResizeEvent *event) { + rect = QWidget::rect(); + marginRegion = QRegion(rect) - QRegion(rect.marginsRemoved(QMargins(UI_BORDER_SIZE, UI_BORDER_SIZE, UI_BORDER_SIZE, UI_BORDER_SIZE))); +} + +void FrogPilotOnroadWindow::updateState(const UIState &s, const FrogPilotUIState &fs) { + const SubMaster &sm = *(s.sm); + const SubMaster &fpsm = *(fs.sm); + + const cereal::CarState::Reader &carState = sm["carState"].getCarState(); + const cereal::CarControl::Reader &carControl = fpsm["carControl"].getCarControl(); + + update(); +} + +void FrogPilotOnroadWindow::paintEvent(QPaintEvent *event) { + QPainter p(this); + + p.setClipRegion(marginRegion); + p.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); +} diff --git a/frogpilot/ui/qt/onroad/frogpilot_onroad.h b/frogpilot/ui/qt/onroad/frogpilot_onroad.h new file mode 100644 index 00000000..49bb02c4 --- /dev/null +++ b/frogpilot/ui/qt/onroad/frogpilot_onroad.h @@ -0,0 +1,24 @@ +#pragma once + +#include "selfdrive/ui/qt/onroad/annotated_camera.h" + +class FrogPilotOnroadWindow : public QWidget { + Q_OBJECT + +public: + FrogPilotOnroadWindow(QWidget* parent = 0); + + void updateState(const UIState &s, const FrogPilotUIState &fs); + + FrogPilotUIScene frogpilot_scene; + + QColor bg; + +private: + void paintEvent(QPaintEvent *event); + void resizeEvent(QResizeEvent *event); + + QRect rect; + + QRegion marginRegion; +}; diff --git a/frogpilot/ui/qt/widgets/frogpilot_controls.cc b/frogpilot/ui/qt/widgets/frogpilot_controls.cc new file mode 100644 index 00000000..e35184bf --- /dev/null +++ b/frogpilot/ui/qt/widgets/frogpilot_controls.cc @@ -0,0 +1,94 @@ +#include "selfdrive/ui/ui.h" + +#include "frogpilot/ui/frogpilot_ui.h" + +void clearMovie(QSharedPointer &movie, QWidget *parent) { + if (!movie) { + return; + } + + QObject::disconnect(movie.data(), nullptr, parent, nullptr); + movie->stop(); + movie.reset(); +} + +void loadGif(const QString &gifPath, QSharedPointer &movie, const QSize &size, QWidget *parent) { + if (!parent || gifPath.isEmpty()) { + return; + } + + if (movie && movie->fileName() == gifPath && movie->state() == QMovie::Running) { + if (movie->scaledSize() != size) { + movie->setScaledSize(size); + } + return; + } + + if (!QFileInfo::exists(gifPath)) { + clearMovie(movie, parent); + return; + } + + clearMovie(movie, parent); + + movie = QSharedPointer::create(gifPath); + movie->setCacheMode(QMovie::CacheAll); + movie->setScaledSize(size); + + QPointer safeParent(parent); + QObject::connect(movie.data(), &QMovie::frameChanged, parent, [safeParent]() { + if (safeParent && safeParent->isVisible()) { + safeParent->update(); + } + }, Qt::UniqueConnection); + + movie->start(); +} + +void loadImage(const QString &basePath, QPixmap &pixmap, QSharedPointer &movie, const QSize &size, QWidget *parent) { + if (!parent || basePath.isEmpty()) { + return; + } + + static QHash pixmapCache; + QString cacheKey = basePath + QString("_%1x%2").arg(size.width()).arg(size.height()); + + QString gifPath = basePath + ".gif"; + if (QFileInfo::exists(gifPath)) { + loadGif(gifPath, movie, size, parent); + if (!pixmap.isNull()) { + pixmap = QPixmap(); + } + return; + } + + clearMovie(movie, parent); + + if (pixmapCache.contains(cacheKey)) { + QPixmap &cached = pixmapCache[cacheKey]; + if (pixmap.cacheKey() != cached.cacheKey()) { + pixmap = cached; + parent->update(); + } + return; + } + + QString pngPath = basePath + ".png"; + if (!QFileInfo::exists(pngPath)) { + if (!pixmap.isNull()) { + pixmap = QPixmap(); + parent->update(); + } + return; + } + + QPixmap loadedPixmap(pngPath); + if (!loadedPixmap.isNull()) { + pixmap = loadedPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + pixmapCache.insert(cacheKey, pixmap); + } else { + pixmap = QPixmap(); + } + + parent->update(); +} diff --git a/frogpilot/ui/qt/widgets/frogpilot_controls.h b/frogpilot/ui/qt/widgets/frogpilot_controls.h new file mode 100644 index 00000000..f930059c --- /dev/null +++ b/frogpilot/ui/qt/widgets/frogpilot_controls.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/widgets/controls.h" + +void loadGif(const QString &gifPath, QSharedPointer &movie, const QSize &size, QWidget *parent); +void loadImage(const QString &basePath, QPixmap &pixmap, QSharedPointer &movie, const QSize &size, QWidget *parent); diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 13545b89..2f605625 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -16,6 +16,8 @@ function agnos_init { sudo chgrp gpu /dev/adsprpc-smd /dev/ion /dev/kgsl-3d0 sudo chmod 660 /dev/adsprpc-smd /dev/ion /dev/kgsl-3d0 + # FrogPilot variables + # Check if AGNOS update is required if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then AGNOS_PY="$DIR/system/hardware/tici/agnos.py" diff --git a/launch_env.sh b/launch_env.sh index e4a6b669..a35bd2b0 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -20,3 +20,6 @@ if [ -z "$AGNOS_VERSION" ]; then fi export STAGING_ROOT="/data/safe_staging" + +# FrogPilot variables +eval "$(/data/openpilot/frogpilot/system/environment_variables)" diff --git a/opendbc_repo/opendbc/car/body/carstate.py b/opendbc_repo/opendbc/car/body/carstate.py index b346a3df..8bb0adbd 100644 --- a/opendbc_repo/opendbc/car/body/carstate.py +++ b/opendbc_repo/opendbc/car/body/carstate.py @@ -28,6 +28,8 @@ class CarState(CarStateBase): ret.cruiseState.enabled = True ret.cruiseState.available = True + # FrogPilot variables + return ret @staticmethod diff --git a/opendbc_repo/opendbc/car/car_helpers.py b/opendbc_repo/opendbc/car/car_helpers.py index dfda0155..f7b587f3 100644 --- a/opendbc_repo/opendbc/car/car_helpers.py +++ b/opendbc_repo/opendbc/car/car_helpers.py @@ -13,6 +13,8 @@ from opendbc.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN FRAME_FINGERPRINT = 100 # 1s +# FrogPilot variables + def load_interfaces(brand_names): ret = {} @@ -163,6 +165,8 @@ def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multip CP.fingerprintSource = source CP.fuzzyFingerprint = not exact_match + # FrogPilot variables + return interfaces[CP.carFingerprint](CP) diff --git a/opendbc_repo/opendbc/car/chrysler/carstate.py b/opendbc_repo/opendbc/car/chrysler/carstate.py index 7c8fd505..1225ad3c 100644 --- a/opendbc_repo/opendbc/car/chrysler/carstate.py +++ b/opendbc_repo/opendbc/car/chrysler/carstate.py @@ -27,6 +27,8 @@ class CarState(CarStateBase): # RealFast variables self.button_message = "CRUISE_BUTTONS_ALT" if FPCP.flags & ChryslerFrogPilotFlags.RAM_HD_ALT_BUTTONS else "CRUISE_BUTTONS" + # FrogPilot variables + def update(self, can_parsers) -> structs.CarState: cp = can_parsers[Bus.pt] cp_cam = can_parsers[Bus.cam] @@ -98,7 +100,11 @@ class CarState(CarStateBase): self.lkas_car_model = cp_cam.vl["DAS_6"]["CAR_MODEL"] self.button_counter = cp.vl[self.button_message]["COUNTER"] - ret.buttonEvents = create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}) + buttonEvents = create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}) + + # FrogPilot variables + + ret.buttonEvents = buttonEvents return ret diff --git a/opendbc_repo/opendbc/car/ford/carstate.py b/opendbc_repo/opendbc/car/ford/carstate.py index d0d45f70..d3d29404 100644 --- a/opendbc_repo/opendbc/car/ford/carstate.py +++ b/opendbc_repo/opendbc/car/ford/carstate.py @@ -113,6 +113,8 @@ class CarState(CarStateBase): *create_button_events(self.lc_button, prev_lc_button, {1: ButtonType.lkas}), ] + # FrogPilot variables + return ret @staticmethod diff --git a/opendbc_repo/opendbc/car/gm/carcontroller.py b/opendbc_repo/opendbc/car/gm/carcontroller.py index 44f1afb5..fb2d48c0 100644 --- a/opendbc_repo/opendbc/car/gm/carcontroller.py +++ b/opendbc_repo/opendbc/car/gm/carcontroller.py @@ -141,6 +141,8 @@ class CarController(CarControllerBase): at_full_stop = at_full_stop and stopping friction_brake_bus = CanBus.POWERTRAIN + # FrogPilot variables + # GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, CC.enabled, at_full_stop)) can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, friction_brake_bus, self.apply_brake, diff --git a/opendbc_repo/opendbc/car/gm/carstate.py b/opendbc_repo/opendbc/car/gm/carstate.py index d8196005..8972bb54 100644 --- a/opendbc_repo/opendbc/car/gm/carstate.py +++ b/opendbc_repo/opendbc/car/gm/carstate.py @@ -182,6 +182,8 @@ class CarState(CarStateBase): if self.CP.transmissionType == TransmissionType.direct: self.single_pedal_mode = ret.gearShifter == GearShifter.low or pt_cp.vl["EVDriveMode"]["SinglePedalModeActive"] == 1 + # FrogPilot variables + return ret @staticmethod diff --git a/opendbc_repo/opendbc/car/gm/fingerprints.py b/opendbc_repo/opendbc/car/gm/fingerprints.py index 7a879a16..f0fc9248 100644 --- a/opendbc_repo/opendbc/car/gm/fingerprints.py +++ b/opendbc_repo/opendbc/car/gm/fingerprints.py @@ -175,6 +175,7 @@ FINGERPRINTS = { { 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 510: 8, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 647: 3, 707: 8, 717: 5, 723: 2, 753: 5, 761: 7, 800: 6, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 872: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1914: 7, 1919: 7, 1920: 7 }], + # FrogPilot variables } FW_VERSIONS: dict[str, dict[tuple, list[bytes]]] = { diff --git a/opendbc_repo/opendbc/car/gm/interface.py b/opendbc_repo/opendbc/car/gm/interface.py index 7c57c825..54b87e87 100755 --- a/opendbc_repo/opendbc/car/gm/interface.py +++ b/opendbc_repo/opendbc/car/gm/interface.py @@ -276,4 +276,6 @@ class CarInterface(CarInterfaceBase): if candidate in CC_ONLY_CAR: ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.FLAG_GM_NO_ACC.value + # FrogPilot variables + return ret diff --git a/opendbc_repo/opendbc/car/gm/values.py b/opendbc_repo/opendbc/car/gm/values.py index ca72359c..ef2474ba 100644 --- a/opendbc_repo/opendbc/car/gm/values.py +++ b/opendbc_repo/opendbc/car/gm/values.py @@ -245,6 +245,7 @@ class CAR(Platforms): [GMCarDocs("Chevrolet Trailblazer 2021-22 (NO ACC)")], CHEVROLET_TRAILBLAZER.specs, ) + # FrogPilot variables class CruiseButtons: @@ -350,3 +351,5 @@ CAMERA_ACC_CAR.update(CC_ONLY_CAR) EV_CAR.update(CAR.CHEVROLET_VOLT, CAR.CHEVROLET_VOLT_2019, CAR.CHEVROLET_BOLT_EUV) DBC = CAR.create_dbc_map() + +# FrogPilot variables diff --git a/opendbc_repo/opendbc/car/honda/carstate.py b/opendbc_repo/opendbc/car/honda/carstate.py index 33449196..fe444a0c 100644 --- a/opendbc_repo/opendbc/car/honda/carstate.py +++ b/opendbc_repo/opendbc/car/honda/carstate.py @@ -219,6 +219,8 @@ class CarState(CarStateBase): *create_button_events(self.cruise_setting, prev_cruise_setting, SETTINGS_BUTTONS_DICT), ] + # FrogPilot variables + return ret def get_can_parsers(self, CP): diff --git a/opendbc_repo/opendbc/car/honda/interface.py b/opendbc_repo/opendbc/car/honda/interface.py old mode 100755 new mode 100644 diff --git a/opendbc_repo/opendbc/car/hyundai/carstate.py b/opendbc_repo/opendbc/car/hyundai/carstate.py index 60978970..ad68850e 100644 --- a/opendbc_repo/opendbc/car/hyundai/carstate.py +++ b/opendbc_repo/opendbc/car/hyundai/carstate.py @@ -202,6 +202,8 @@ class CarState(CarStateBase): self.low_speed_alert = False ret.lowSpeedAlert = self.low_speed_alert + # FrogPilot variables + return ret def update_canfd(self, can_parsers) -> structs.CarState: @@ -293,6 +295,8 @@ class CarState(CarStateBase): ret.blockPcmEnable = not self.recent_button_interaction() + # FrogPilot variables + return ret def get_can_parsers_canfd(self, CP): diff --git a/opendbc_repo/opendbc/car/hyundai/values.py b/opendbc_repo/opendbc/car/hyundai/values.py index e239298a..178b16ad 100644 --- a/opendbc_repo/opendbc/car/hyundai/values.py +++ b/opendbc_repo/opendbc/car/hyundai/values.py @@ -68,6 +68,9 @@ class HyundaiSafetyFlags(IntFlag): ALT_LIMITS_2 = 512 +# FrogPilot variables + + class HyundaiFlags(IntFlag): # Dynamic Flags diff --git a/opendbc_repo/opendbc/car/interfaces.py b/opendbc_repo/opendbc/car/interfaces.py index 8328062f..268de6cc 100644 --- a/opendbc_repo/opendbc/car/interfaces.py +++ b/opendbc_repo/opendbc/car/interfaces.py @@ -20,6 +20,8 @@ from opendbc.can import CANParser GearShifter = structs.CarState.GearShifter ButtonType = structs.CarState.ButtonEvent.Type +# FrogPilot variables + V_CRUISE_MAX = 145 MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS ACCEL_MAX = 2.0 @@ -108,6 +110,8 @@ class CarInterfaceBase(ABC): dbc_names = {bus: cp.dbc_name for bus, cp in self.can_parsers.items()} self.CC: CarControllerBase = self.CarController(dbc_names, CP) + # FrogPilot variables + def apply(self, c: structs.CarControl, now_nanos: int | None = None) -> tuple[structs.CarControl.Actuators, list[CanData]]: if now_nanos is None: now_nanos = int(time.monotonic() * 1e9) @@ -149,8 +153,12 @@ class CarInterfaceBase(ABC): ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, ret.tireStiffnessFactor) + # FrogPilot variables + return ret + # FrogPilot variables + @staticmethod @abstractmethod def _get_params(ret: structs.CarParams, candidate, fingerprint: dict[int, dict[int, int]], @@ -259,6 +267,8 @@ class CarInterfaceBase(ABC): # save for next iteration self.CS.out = ret + # FrogPilot variables + return ret @@ -287,6 +297,9 @@ class CarStateBase(ABC): K = get_kalman_gain(DT_CTRL, np.array(A), np.array(C), np.array(Q), R) self.v_ego_kf = KF1D(x0=x0, A=A, C=C[0], K=K) + # FrogPilot variables + self.CC: structs.CarControl = structs.CarControl.new_message() + @abstractmethod def update(self, can_parsers) -> structs.CarState: pass diff --git a/opendbc_repo/opendbc/car/mazda/carstate.py b/opendbc_repo/opendbc/car/mazda/carstate.py index 074a13c2..6b7bdd25 100644 --- a/opendbc_repo/opendbc/car/mazda/carstate.py +++ b/opendbc_repo/opendbc/car/mazda/carstate.py @@ -115,6 +115,8 @@ class CarState(CarStateBase): # TODO: add button types for inc and dec ret.buttonEvents = create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}) + # FrogPilot variables + return ret @staticmethod diff --git a/opendbc_repo/opendbc/car/mazda/interface.py b/opendbc_repo/opendbc/car/mazda/interface.py index 814846e8..e59497ea 100755 --- a/opendbc_repo/opendbc/car/mazda/interface.py +++ b/opendbc_repo/opendbc/car/mazda/interface.py @@ -29,4 +29,6 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.41 + # FrogPilot variables + return ret diff --git a/opendbc_repo/opendbc/car/nissan/carstate.py b/opendbc_repo/opendbc/car/nissan/carstate.py index 8f38590a..c0533765 100644 --- a/opendbc_repo/opendbc/car/nissan/carstate.py +++ b/opendbc_repo/opendbc/car/nissan/carstate.py @@ -24,6 +24,8 @@ class CarState(CarStateBase): self.distance_button = 0 + # FrogPilot variables + def update(self, can_parsers) -> structs.CarState: cp = can_parsers[Bus.pt] cp_cam = can_parsers[Bus.cam] @@ -126,7 +128,11 @@ class CarState(CarStateBase): self.lkas_hud_msg = copy.copy(cp_adas.vl["PROPILOT_HUD"]) self.lkas_hud_info_msg = copy.copy(cp_adas.vl["PROPILOT_HUD_INFO_MSG"]) - ret.buttonEvents = create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}) + buttonEvents = create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}) + + # FrogPilot variables + + ret.buttonEvents = buttonEvents return ret diff --git a/opendbc_repo/opendbc/car/psa/carstate.py b/opendbc_repo/opendbc/car/psa/carstate.py index 81335ef5..bfc88691 100644 --- a/opendbc_repo/opendbc/car/psa/carstate.py +++ b/opendbc_repo/opendbc/car/psa/carstate.py @@ -62,6 +62,9 @@ class CarState(CarStateBase): # lock info ret.doorOpen = any((cp_cam.vl['Dat_BSI']['DRIVER_DOOR'], cp_cam.vl['Dat_BSI']['PASSENGER_DOOR'])) ret.seatbeltUnlatched = cp_cam.vl['RESTRAINTS']['DRIVER_SEATBELT'] != 2 + + # FrogPilot variables + return ret @staticmethod diff --git a/opendbc_repo/opendbc/car/rivian/carstate.py b/opendbc_repo/opendbc/car/rivian/carstate.py index 001d705e..222a55eb 100644 --- a/opendbc_repo/opendbc/car/rivian/carstate.py +++ b/opendbc_repo/opendbc/car/rivian/carstate.py @@ -92,6 +92,8 @@ class CarState(CarStateBase): self.sccm_wheel_touch = copy.copy(cp.vl["SCCM_WheelTouch"]) self.vdm_adas_status = copy.copy(cp.vl["VDM_AdasSts"]) + # FrogPilot variables + return ret @staticmethod diff --git a/opendbc_repo/opendbc/car/subaru/carcontroller.py b/opendbc_repo/opendbc/car/subaru/carcontroller.py index 3b9abf59..29cdd982 100644 --- a/opendbc_repo/opendbc/car/subaru/carcontroller.py +++ b/opendbc_repo/opendbc/car/subaru/carcontroller.py @@ -11,6 +11,8 @@ from opendbc.car.subaru.values import DBC, GLOBAL_ES_ADDR, CanBus, CarController MAX_STEER_RATE = 25 # deg/s MAX_STEER_RATE_FRAMES = 7 # tx control frames needed before torque can be cut +# FrogPilot variables + class CarController(CarControllerBase): def __init__(self, dbc_names, CP): @@ -23,6 +25,8 @@ class CarController(CarControllerBase): self.p = CarControllerParams(CP) self.packer = CANPacker(DBC[CP.carFingerprint][Bus.pt]) + # FrogPilot variables + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl @@ -57,6 +61,8 @@ class CarController(CarControllerBase): self.apply_torque_last = apply_torque + # FrogPilot variables + # *** longitudinal *** if CC.longActive: @@ -93,6 +99,7 @@ class CarController(CarControllerBase): can_sends.append(subarucan.create_preglobal_es_distance(self.packer, cruise_button, CS.es_distance_msg)) + # FrogPilot variables else: if self.frame % 10 == 0: can_sends.append(subarucan.create_es_dashstatus(self.packer, self.frame // 10, CS.es_dashstatus_msg, CC.enabled, @@ -105,6 +112,8 @@ class CarController(CarControllerBase): if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT: can_sends.append(subarucan.create_es_infotainment(self.packer, self.frame // 10, CS.es_infotainment_msg, hud_control.visualAlert)) + # FrogPilot variables + if self.CP.openpilotLongitudinalControl: if self.frame % 5 == 0: can_sends.append(subarucan.create_es_status(self.packer, self.frame // 5, CS.es_status_msg, @@ -142,3 +151,5 @@ class CarController(CarControllerBase): self.frame += 1 return new_actuators, can_sends + + # FrogPilot variables diff --git a/opendbc_repo/opendbc/car/subaru/carstate.py b/opendbc_repo/opendbc/car/subaru/carstate.py index 76bc4ff8..ddfc6f60 100644 --- a/opendbc_repo/opendbc/car/subaru/carstate.py +++ b/opendbc_repo/opendbc/car/subaru/carstate.py @@ -123,6 +123,8 @@ class CarState(CarStateBase): if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT: self.es_infotainment_msg = copy.copy(cp_cam.vl["ES_Infotainment"]) + # FrogPilot variables + return ret @staticmethod diff --git a/opendbc_repo/opendbc/car/subaru/subarucan.py b/opendbc_repo/opendbc/car/subaru/subarucan.py index 64cab421..a8a5458b 100644 --- a/opendbc_repo/opendbc/car/subaru/subarucan.py +++ b/opendbc_repo/opendbc/car/subaru/subarucan.py @@ -334,3 +334,6 @@ def subaru_checksum(address: int, sig, d: bytearray) -> int: for i in range(1, len(d)): s += d[i] return s & 0xFF + + +# FrogPilot variables diff --git a/opendbc_repo/opendbc/car/subaru/values.py b/opendbc_repo/opendbc/car/subaru/values.py index a702d394..54d43da8 100644 --- a/opendbc_repo/opendbc/car/subaru/values.py +++ b/opendbc_repo/opendbc/car/subaru/values.py @@ -26,6 +26,7 @@ class CarControllerParams: elif CP.carFingerprint == CAR.SUBARU_IMPREZA_2020: self.STEER_DELTA_UP = 35 self.STEER_MAX = 1439 + # FrogPilot variables else: self.STEER_MAX = 2047 diff --git a/opendbc_repo/opendbc/car/tesla/carstate.py b/opendbc_repo/opendbc/car/tesla/carstate.py index fa16116b..5f73e4c7 100644 --- a/opendbc_repo/opendbc/car/tesla/carstate.py +++ b/opendbc_repo/opendbc/car/tesla/carstate.py @@ -117,6 +117,8 @@ class CarState(CarStateBase): # Messages needed by carcontroller self.das_control = copy.copy(cp_ap_party.vl["DAS_control"]) + # FrogPilot variables + return ret @staticmethod diff --git a/opendbc_repo/opendbc/car/torque_data/params.toml b/opendbc_repo/opendbc/car/torque_data/params.toml index fde2ca22..d2cfd4cc 100644 --- a/opendbc_repo/opendbc/car/torque_data/params.toml +++ b/opendbc_repo/opendbc/car/torque_data/params.toml @@ -80,3 +80,5 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "VOLKSWAGEN_JETTA_MK7" = [1.2271623034089392, 1.216955117387, 0.19437384688370712] "VOLKSWAGEN_PASSAT_MK8" = [1.3432120736752917, 1.7087275587362314, 0.19444383787326647] "VOLKSWAGEN_TIGUAN_MK2" = [0.9711965500094828, 1.0001565939459098, 0.1465626137072916] + +# FrogPilot variables diff --git a/opendbc_repo/opendbc/car/torque_data/substitute.toml b/opendbc_repo/opendbc/car/torque_data/substitute.toml index 238dd7c4..e308be89 100644 --- a/opendbc_repo/opendbc/car/torque_data/substitute.toml +++ b/opendbc_repo/opendbc/car/torque_data/substitute.toml @@ -95,3 +95,5 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "CHEVROLET_EQUINOX_CC" = "CHEVROLET_EQUINOX" "CHEVROLET_SUBURBAN_CC" = "CHEVROLET_SILVERADO" "CHEVROLET_TRAILBLAZER_CC" = "CHEVROLET_TRAILBLAZER" + +# FrogPilot variables diff --git a/opendbc_repo/opendbc/car/toyota/carcontroller.py b/opendbc_repo/opendbc/car/toyota/carcontroller.py index 627a2e1b..65315fc3 100644 --- a/opendbc_repo/opendbc/car/toyota/carcontroller.py +++ b/opendbc_repo/opendbc/car/toyota/carcontroller.py @@ -34,6 +34,8 @@ MAX_STEER_RATE_FRAMES = 18 # tx control frames needed before torque can be cut # EPS allows user torque above threshold for 50 frames before permanently faulting MAX_USER_TORQUE = 500 +# FrogPilot variables + def get_long_tune(CP, params): if CP.carFingerprint in TSS2_CAR: @@ -78,6 +80,8 @@ class CarController(CarControllerBase): self.secoc_acc_message_counter = 0 self.secoc_prev_reset_counter = 0 + # FrogPilot variables + def update(self, CC, CS, now_nanos): actuators = CC.actuators stopping = actuators.longControlState == LongCtrlState.stopping @@ -319,4 +323,7 @@ class CarController(CarControllerBase): new_actuators.accel = self.accel self.frame += 1 + + # FrogPilot variables + return new_actuators, can_sends diff --git a/opendbc_repo/opendbc/car/toyota/carstate.py b/opendbc_repo/opendbc/car/toyota/carstate.py index 3fb22740..43c2da73 100644 --- a/opendbc_repo/opendbc/car/toyota/carstate.py +++ b/opendbc_repo/opendbc/car/toyota/carstate.py @@ -53,6 +53,8 @@ class CarState(CarStateBase): self.gvc = 0.0 self.secoc_synchronization = None + # FrogPilot variables + def update(self, can_parsers) -> structs.CarState: cp = can_parsers[Bus.pt] cp_cam = can_parsers[Bus.cam] @@ -198,7 +200,10 @@ class CarState(CarStateBase): buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise}) + # FrogPilot variables + ret.buttonEvents = buttonEvents + return ret @staticmethod diff --git a/opendbc_repo/opendbc/car/toyota/values.py b/opendbc_repo/opendbc/car/toyota/values.py index 0b85d386..9db6457a 100644 --- a/opendbc_repo/opendbc/car/toyota/values.py +++ b/opendbc_repo/opendbc/car/toyota/values.py @@ -80,6 +80,9 @@ class ToyotaFlags(IntFlag): SNG_WITHOUT_DSU_DEPRECATED = 512 +# FrogPilot variables + + def dbc_dict(pt, radar): return {Bus.pt: pt, Bus.radar: radar} diff --git a/opendbc_repo/opendbc/car/volkswagen/carstate.py b/opendbc_repo/opendbc/car/volkswagen/carstate.py index a43a1665..e1228903 100644 --- a/opendbc_repo/opendbc/car/volkswagen/carstate.py +++ b/opendbc_repo/opendbc/car/volkswagen/carstate.py @@ -137,6 +137,9 @@ class CarState(CarStateBase): ret.lowSpeedAlert = self.update_low_speed_alert(ret.vEgo) self.frame += 1 + + # FrogPilot variables + return ret def update_pq(self, pt_cp, cam_cp, ext_cp) -> structs.CarState: @@ -228,6 +231,9 @@ class CarState(CarStateBase): ret.lowSpeedAlert = self.update_low_speed_alert(ret.vEgo) self.frame += 1 + + # FrogPilot variables + return ret def update_mlb(self, pt_cp, cam_cp, ext_cp) -> structs.CarState: diff --git a/opendbc_repo/opendbc/safety/__init__.py b/opendbc_repo/opendbc/safety/__init__.py index 5d4c2464..91063855 100644 --- a/opendbc_repo/opendbc/safety/__init__.py +++ b/opendbc_repo/opendbc/safety/__init__.py @@ -8,3 +8,5 @@ class ALTERNATIVE_EXPERIENCE: DISABLE_STOCK_AEB = 2 RAISE_LONGITUDINAL_LIMITS_TO_ISO_MAX = 8 ALLOW_AEB = 16 + + # FrogPilot variables diff --git a/opendbc_repo/opendbc/safety/declarations.h b/opendbc_repo/opendbc/safety/declarations.h index b83b35fc..18b451b8 100644 --- a/opendbc_repo/opendbc/safety/declarations.h +++ b/opendbc_repo/opendbc/safety/declarations.h @@ -268,6 +268,8 @@ extern bool acc_main_on; // referred to as "ACC off" in ISO 15622:2018 extern int cruise_button_prev; extern bool safety_rx_checks_invalid; +// FrogPilot variables + // for safety modes with torque steering control extern int desired_torque_last; // last desired steer torque extern int rt_torque_last; // last desired torque for real time check @@ -309,6 +311,8 @@ extern bool enable_gas_interceptor; // This flag allows AEB to be commanded from openpilot. #define ALT_EXP_ALLOW_AEB 16 +// FrogPilot variables + extern int alternative_experience; // time since safety mode has been changed diff --git a/opendbc_repo/opendbc/safety/modes/chrysler.h b/opendbc_repo/opendbc/safety/modes/chrysler.h index 70e2a3de..30d9096a 100644 --- a/opendbc_repo/opendbc/safety/modes/chrysler.h +++ b/opendbc_repo/opendbc/safety/modes/chrysler.h @@ -78,6 +78,8 @@ static void chrysler_rx_hook(const CANPacket_t *msg) { if ((msg->bus == das_3_bus) && (msg->addr == chrysler_addrs->DAS_3)) { bool cruise_engaged = GET_BIT(msg, 21U); pcm_cruise_check(cruise_engaged); + + // FrogPilot variables } // TODO: use the same message for both diff --git a/opendbc_repo/opendbc/safety/modes/ford.h b/opendbc_repo/opendbc/safety/modes/ford.h index 4b35cf00..447cc7d1 100644 --- a/opendbc_repo/opendbc/safety/modes/ford.h +++ b/opendbc_repo/opendbc/safety/modes/ford.h @@ -158,6 +158,8 @@ static void ford_rx_hook(const CANPacket_t *msg) { unsigned int cruise_state = msg->data[1] & 0x07U; bool cruise_engaged = (cruise_state == 4U) || (cruise_state == 5U); pcm_cruise_check(cruise_engaged); + + // FrogPilot variables } } } diff --git a/opendbc_repo/opendbc/safety/modes/gm.h b/opendbc_repo/opendbc/safety/modes/gm.h index a4004e42..73806c9d 100644 --- a/opendbc_repo/opendbc/safety/modes/gm.h +++ b/opendbc_repo/opendbc/safety/modes/gm.h @@ -123,6 +123,8 @@ static void gm_rx_hook(const CANPacket_t *msg) { gas_pressed = gas_interceptor > GM_GAS_INTERCEPTOR_THRESHOLD; } } + + // FrogPilot variables } static bool gm_tx_hook(const CANPacket_t *msg) { diff --git a/opendbc_repo/opendbc/safety/modes/hyundai.h b/opendbc_repo/opendbc/safety/modes/hyundai.h index b867f185..70903e7a 100644 --- a/opendbc_repo/opendbc/safety/modes/hyundai.h +++ b/opendbc_repo/opendbc/safety/modes/hyundai.h @@ -52,6 +52,8 @@ const LongitudinalLimits HYUNDAI_LONG_LIMITS = { #define HYUNDAI_FCEV_GAS_ADDR_CHECK \ {.msg = {{0x91, 0, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ +// FrogPilot variables + static const CanMsg HYUNDAI_TX_MSGS[] = { HYUNDAI_COMMON_TX_MSGS(0) }; @@ -173,6 +175,8 @@ static void hyundai_rx_hook(const CANPacket_t *msg) { if (msg->addr == 0x394U) { brake_pressed = ((msg->data[5] >> 5U) & 0x3U) == 0x2U; } + + // FrogPilot variables } } @@ -279,6 +283,8 @@ static safety_config hyundai_init(uint16_t param) { HYUNDAI_FCEV_GAS_ADDR_CHECK }; + // FrogPilot variables + if (hyundai_fcev_gas_signal) { SET_RX_CHECKS(hyundai_fcev_long_rx_checks, ret); } else { @@ -296,6 +302,8 @@ static safety_config hyundai_init(uint16_t param) { HYUNDAI_SCC12_ADDR_CHECK(2) }; + // FrogPilot variables + ret = BUILD_SAFETY_CFG(hyundai_cam_scc_rx_checks, HYUNDAI_CAMERA_SCC_TX_MSGS); } else { static RxCheck hyundai_rx_checks[] = { @@ -309,6 +317,8 @@ static safety_config hyundai_init(uint16_t param) { HYUNDAI_FCEV_GAS_ADDR_CHECK }; + // FrogPilot variables + SET_TX_MSGS(HYUNDAI_TX_MSGS, ret); if (hyundai_fcev_gas_signal) { SET_RX_CHECKS(hyundai_fcev_rx_checks, ret); diff --git a/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h b/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h index 7c345bf4..fca50984 100644 --- a/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h +++ b/opendbc_repo/opendbc/safety/modes/hyundai_canfd.h @@ -88,9 +88,13 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *msg) { if (msg->addr == 0x1cfU) { cruise_button = msg->data[2] & 0x7U; main_button = GET_BIT(msg, 19U); + + // FrogPilot variables } else { cruise_button = (msg->data[4] >> 4) & 0x7U; main_button = GET_BIT(msg, 34U); + + // FrogPilot variables } hyundai_common_cruise_buttons_check(cruise_button, main_button); } diff --git a/opendbc_repo/opendbc/safety/modes/hyundai_common.h b/opendbc_repo/opendbc/safety/modes/hyundai_common.h index 3cca057d..6a11f852 100644 --- a/opendbc_repo/opendbc/safety/modes/hyundai_common.h +++ b/opendbc_repo/opendbc/safety/modes/hyundai_common.h @@ -42,6 +42,8 @@ bool hyundai_fcev_gas_signal = false; extern bool hyundai_alt_limits_2; bool hyundai_alt_limits_2 = false; +// FrogPilot variables + static uint8_t hyundai_last_button_interaction; // button messages since the user pressed an enable button void hyundai_common_init(uint16_t param) { @@ -53,6 +55,8 @@ void hyundai_common_init(uint16_t param) { const uint16_t HYUNDAI_PARAM_FCEV_GAS = 256; const uint16_t HYUNDAI_PARAM_ALT_LIMITS_2 = 512; + // FrogPilot variables + hyundai_ev_gas_signal = GET_FLAG(param, HYUNDAI_PARAM_EV_GAS); hyundai_hybrid_gas_signal = !hyundai_ev_gas_signal && GET_FLAG(param, HYUNDAI_PARAM_HYBRID_GAS); hyundai_camera_scc = GET_FLAG(param, HYUNDAI_PARAM_CAMERA_SCC); @@ -61,6 +65,8 @@ void hyundai_common_init(uint16_t param) { hyundai_fcev_gas_signal = GET_FLAG(param, HYUNDAI_PARAM_FCEV_GAS); hyundai_alt_limits_2 = GET_FLAG(param, HYUNDAI_PARAM_ALT_LIMITS_2); + // FrogPilot variables + hyundai_last_button_interaction = HYUNDAI_PREV_BUTTON_SAMPLES; #ifdef ALLOW_DEBUG @@ -110,6 +116,8 @@ void hyundai_common_cruise_buttons_check(const int cruise_button, const bool mai cruise_button_prev = cruise_button; } + + // FrogPilot variables } #ifdef CANFD @@ -138,3 +146,5 @@ uint32_t hyundai_common_canfd_compute_checksum(const CANPacket_t *msg) { return crc; } #endif + +// FrogPilot variables diff --git a/opendbc_repo/opendbc/safety/modes/mazda.h b/opendbc_repo/opendbc/safety/modes/mazda.h index f95cfaf8..b4624c93 100644 --- a/opendbc_repo/opendbc/safety/modes/mazda.h +++ b/opendbc_repo/opendbc/safety/modes/mazda.h @@ -34,6 +34,8 @@ static void mazda_rx_hook(const CANPacket_t *msg) { if (msg->addr == MAZDA_CRZ_CTRL) { bool cruise_engaged = msg->data[0] & 0x8U; pcm_cruise_check(cruise_engaged); + + // FrogPilot variables } if (msg->addr == MAZDA_ENGINE_DATA) { diff --git a/opendbc_repo/opendbc/safety/modes/nissan.h b/opendbc_repo/opendbc/safety/modes/nissan.h index 9289935f..f41034c4 100644 --- a/opendbc_repo/opendbc/safety/modes/nissan.h +++ b/opendbc_repo/opendbc/safety/modes/nissan.h @@ -50,6 +50,8 @@ static void nissan_rx_hook(const CANPacket_t *msg) { bool cruise_engaged = (msg->data[0] >> 3) & 1U; pcm_cruise_check(cruise_engaged); } + + // FrogPilot variables } diff --git a/opendbc_repo/opendbc/safety/modes/psa.h b/opendbc_repo/opendbc/safety/modes/psa.h index 6bbd7b9c..971922e6 100644 --- a/opendbc_repo/opendbc/safety/modes/psa.h +++ b/opendbc_repo/opendbc/safety/modes/psa.h @@ -10,6 +10,8 @@ #define PSA_DAT_BSI 1042U // RX from BSI, brake #define PSA_LANE_KEEP_ASSIST 1010U // TX from OP, EPS +// FrogPilot variables + // CAN bus #define PSA_MAIN_BUS 0U #define PSA_ADAS_BUS 1U @@ -21,6 +23,7 @@ static uint8_t psa_get_counter(const CANPacket_t *msg) { cnt = (msg->data[3] >> 4) & 0xFU; } else if (msg->addr == PSA_HS2_DYN_ABR_38D) { cnt = (msg->data[5] >> 4) & 0xFU; + // FrogPilot variables } else { } return cnt; @@ -32,6 +35,7 @@ static uint32_t psa_get_checksum(const CANPacket_t *msg) { chksum = msg->data[5] & 0xFU; } else if (msg->addr == PSA_HS2_DYN_ABR_38D) { chksum = msg->data[5] & 0xFU; + // FrogPilot variables } else { } return chksum; @@ -59,6 +63,7 @@ static uint32_t psa_compute_checksum(const CANPacket_t *msg) { chk = _psa_compute_checksum(msg, 0x4, 5); } else if (msg->addr == PSA_HS2_DYN_ABR_38D) { chk = _psa_compute_checksum(msg, 0x7, 5); + // FrogPilot variables } else { } return chk; @@ -84,6 +89,7 @@ static void psa_rx_hook(const CANPacket_t *msg) { if (msg->addr == PSA_HS2_DAT_MDD_CMD_452) { pcm_cruise_check((msg->data[2U] >> 7U) & 1U); // RVV_ACC_ACTIVATION_REQ } + // FrogPilot variables } @@ -136,6 +142,8 @@ static safety_config psa_init(uint16_t param) { {.msg = {{PSA_STEERING, PSA_MAIN_BUS, 7, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // driver torque {.msg = {{PSA_DYN_CMM, PSA_MAIN_BUS, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // gas pedal {.msg = {{PSA_DAT_BSI, PSA_CAM_BUS, 8, 20U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // brake + + // FrogPilot variables }; return BUILD_SAFETY_CFG(psa_rx_checks, PSA_TX_MSGS); diff --git a/opendbc_repo/opendbc/safety/modes/rivian.h b/opendbc_repo/opendbc/safety/modes/rivian.h index 35645b7c..879034fc 100644 --- a/opendbc_repo/opendbc/safety/modes/rivian.h +++ b/opendbc_repo/opendbc/safety/modes/rivian.h @@ -97,6 +97,8 @@ static void rivian_rx_hook(const CANPacket_t *msg) { if (msg->addr == 0x100U) { const int feature_status = msg->data[2] >> 5U; pcm_cruise_check(feature_status == 1); + + // FrogPilot variables } } } diff --git a/opendbc_repo/opendbc/safety/modes/subaru.h b/opendbc_repo/opendbc/safety/modes/subaru.h index 9b01d39d..38167047 100644 --- a/opendbc_repo/opendbc/safety/modes/subaru.h +++ b/opendbc_repo/opendbc/safety/modes/subaru.h @@ -109,6 +109,8 @@ static void subaru_rx_hook(const CANPacket_t *msg) { if ((msg->addr == MSG_SUBARU_CruiseControl) && (msg->bus == alt_main_bus)) { bool cruise_engaged = (msg->data[5] >> 1) & 1U; pcm_cruise_check(cruise_engaged); + + // FrogPilot variables } // update vehicle moving with any non-zero wheel speed diff --git a/opendbc_repo/opendbc/safety/modes/subaru_preglobal.h b/opendbc_repo/opendbc/safety/modes/subaru_preglobal.h index 4ddcdc10..1d025410 100644 --- a/opendbc_repo/opendbc/safety/modes/subaru_preglobal.h +++ b/opendbc_repo/opendbc/safety/modes/subaru_preglobal.h @@ -33,6 +33,8 @@ static void subaru_preglobal_rx_hook(const CANPacket_t *msg) { if (msg->addr == MSG_SUBARU_PG_CruiseControl) { bool cruise_engaged = (msg->data[6] >> 1) & 1U; pcm_cruise_check(cruise_engaged); + + // FrogPilot variables } // update vehicle moving with any non-zero wheel speed diff --git a/opendbc_repo/opendbc/safety/modes/tesla.h b/opendbc_repo/opendbc/safety/modes/tesla.h index b8f41f04..1a258600 100644 --- a/opendbc_repo/opendbc/safety/modes/tesla.h +++ b/opendbc_repo/opendbc/safety/modes/tesla.h @@ -159,6 +159,8 @@ static void tesla_rx_hook(const CANPacket_t *msg) { cruise_engaged = cruise_engaged && !tesla_autopark; pcm_cruise_check(cruise_engaged); + + // FrogPilot variables } if (msg->addr == 0x155U) { diff --git a/opendbc_repo/opendbc/safety/modes/toyota.h b/opendbc_repo/opendbc/safety/modes/toyota.h index c2a46627..0e58d8db 100644 --- a/opendbc_repo/opendbc/safety/modes/toyota.h +++ b/opendbc_repo/opendbc/safety/modes/toyota.h @@ -39,6 +39,7 @@ #define TOYOTA_COMMON_RX_CHECKS(lta) \ {.msg = {{ 0xaa, 0, 8, 83U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ {.msg = {{0x260, 0, 8, 50U, .ignore_counter = true, .ignore_quality_flag=!(lta)}, { 0 }, { 0 }}}, \ + /* FrogPilot Variables */ \ #define TOYOTA_RX_CHECKS(lta) \ TOYOTA_COMMON_RX_CHECKS(lta) \ @@ -159,6 +160,8 @@ static void toyota_rx_hook(const CANPacket_t *msg) { UPDATE_VEHICLE_SPEED(speed / 4.0 * 0.01 * KPH_TO_MS); } + + // FrogPilot variables } } diff --git a/opendbc_repo/opendbc/safety/safety.h b/opendbc_repo/opendbc/safety/safety.h index 6211ab2e..2c0683e3 100644 --- a/opendbc_repo/opendbc/safety/safety.h +++ b/opendbc_repo/opendbc/safety/safety.h @@ -60,6 +60,8 @@ bool acc_main_on = false; // referred to as "ACC off" in ISO 15622:2018 int cruise_button_prev = 0; bool safety_rx_checks_invalid = false; +// FrogPilot variables + // for safety modes with torque steering control int desired_torque_last = 0; // last desired steer torque int rt_torque_last = 0; // last desired torque for real time check @@ -366,6 +368,8 @@ static void generic_rx_checks(void) { controls_allowed = false; } steering_disengage_prev = steering_disengage; + + // FrogPilot variables } static void stock_ecu_check(bool stock_ecu_detected) { @@ -489,6 +493,8 @@ int set_safety_hooks(uint16_t mode, uint16_t param) { // OPGM variables enable_gas_interceptor = false; + // FrogPilot variables + return set_status; } diff --git a/opendbc_repo/opendbc/safety/tests/common.py b/opendbc_repo/opendbc/safety/tests/common.py index 20f7cf7a..5440b455 100644 --- a/opendbc_repo/opendbc/safety/tests/common.py +++ b/opendbc_repo/opendbc/safety/tests/common.py @@ -301,6 +301,8 @@ class TorqueSteeringSafetyTestBase(SafetyTestBase, abc.ABC): for _ in range(10): self.assertFalse(self._tx(self._torque_cmd_msg(self.MAX_TORQUE, 1))) + # FrogPilot variables + class SteerRequestCutSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC): @@ -806,6 +808,8 @@ class AngleSteeringSafetyTest(VehicleSpeedSafetyTest): for _ in range(5): self.assertTrue(self._tx(self._angle_cmd_msg(0, True, increment_timer=False))) + # FrogPilot variables + class SafetyTest(SafetyTestBase): TX_MSGS: list[list[int]] | None = None diff --git a/opendbc_repo/opendbc/safety/tests/hyundai_common.py b/opendbc_repo/opendbc/safety/tests/hyundai_common.py index 5d79155a..df7dd8c7 100644 --- a/opendbc_repo/opendbc/safety/tests/hyundai_common.py +++ b/opendbc_repo/opendbc/safety/tests/hyundai_common.py @@ -71,6 +71,8 @@ class HyundaiButtonBase: self.assertEqual(controls_allowed, self.safety.get_controls_allowed()) self._rx(self._button_msg(Buttons.NONE)) + # FrogPilot variables + class HyundaiLongitudinalBase(common.LongitudinalAccelSafetyTest): diff --git a/opendbc_repo/opendbc/safety/tests/test_chrysler.py b/opendbc_repo/opendbc/safety/tests/test_chrysler.py index daa585ab..8a3f1c32 100755 --- a/opendbc_repo/opendbc/safety/tests/test_chrysler.py +++ b/opendbc_repo/opendbc/safety/tests/test_chrysler.py @@ -71,6 +71,8 @@ class TestChryslerSafety(common.CarSafetyTest, common.MotorTorqueSteeringSafetyT self.assertFalse(self._tx(self._button_msg(cancel=True, resume=True))) self.assertFalse(self._tx(self._button_msg(cancel=False, resume=False))) + # FrogPilot variables + class TestChryslerRamDTSafety(TestChryslerSafety): TX_MSGS = [[0xB1, 2], [0xA6, 0], [0xFA, 0]] diff --git a/opendbc_repo/opendbc/safety/tests/test_ford.py b/opendbc_repo/opendbc/safety/tests/test_ford.py index f8cc80e0..88c90a1f 100755 --- a/opendbc_repo/opendbc/safety/tests/test_ford.py +++ b/opendbc_repo/opendbc/safety/tests/test_ford.py @@ -378,6 +378,8 @@ class TestFordSafetyBase(common.CarSafetyTest): for bus in (0, 2): self.assertEqual(enabled, self._tx(self._acc_button_msg(Buttons.CANCEL, bus))) + # FrogPilot variables + class TestFordCANFDStockSafety(TestFordSafetyBase): STEER_MESSAGE = MSG_LateralMotionControl2 diff --git a/opendbc_repo/opendbc/safety/tests/test_gm.py b/opendbc_repo/opendbc/safety/tests/test_gm.py index 007ec734..e3955191 100755 --- a/opendbc_repo/opendbc/safety/tests/test_gm.py +++ b/opendbc_repo/opendbc/safety/tests/test_gm.py @@ -132,6 +132,8 @@ class TestGmSafetyBase(common.CarSafetyTest, common.DriverTorqueSteeringSafetyTe values = {"ACCButtons": buttons} return self.packer.make_can_msg_safety("ASCMSteeringButton", self.BUTTONS_BUS, values) + # FrogPilot variables + class TestGmEVSafetyBase(TestGmSafetyBase): EXTRA_SAFETY_PARAM = GMSafetyFlags.EV diff --git a/opendbc_repo/opendbc/safety/tests/test_honda.py b/opendbc_repo/opendbc/safety/tests/test_honda.py index 44220095..38714741 100755 --- a/opendbc_repo/opendbc/safety/tests/test_honda.py +++ b/opendbc_repo/opendbc/safety/tests/test_honda.py @@ -237,6 +237,8 @@ class HondaBase(common.CarSafetyTest): self.assertTrue(self._tx(self._send_steer_msg(0x0000))) self.assertFalse(self._tx(self._send_steer_msg(0x1000))) + # FrogPilot variables + # ********************* Honda Nidec ********************** @@ -598,5 +600,6 @@ class TestHondaBoschCANFDAltBrakeSafety(HondaPcmEnableBase, TestHondaBoschCANFDS self.safety.init_tests() + if __name__ == "__main__": unittest.main() diff --git a/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py b/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py index dd992f33..f638f2b0 100755 --- a/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py +++ b/opendbc_repo/opendbc/safety/tests/test_hyundai_canfd.py @@ -81,6 +81,8 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.CarSafetyTest, common.Drive } return self.packer.make_can_msg_safety("CRUISE_BUTTONS", bus, values) + # FrogPilot variables + class TestHyundaiCanfdLFASteeringBase(TestHyundaiCanfdBase): @@ -150,6 +152,8 @@ class TestHyundaiCanfdLFASteeringAltButtonsBase(TestHyundaiCanfdLFASteeringBase) self.assertFalse(self._tx(self._acc_cancel_msg(True, accel=1))) self.assertFalse(self._tx(self._acc_cancel_msg(False))) + # FrogPilot variables + @parameterized_class(ALL_GAS_EV_HYBRID_COMBOS) class TestHyundaiCanfdLFASteeringAltButtons(TestHyundaiCanfdLFASteeringAltButtonsBase): @@ -284,5 +288,6 @@ class TestHyundaiCanfdLFASteeringLongAltButtons(TestHyundaiCanfdLFASteeringLongB pass + if __name__ == "__main__": unittest.main() diff --git a/opendbc_repo/opendbc/safety/tests/test_mazda.py b/opendbc_repo/opendbc/safety/tests/test_mazda.py index 607edcbf..5d228826 100755 --- a/opendbc_repo/opendbc/safety/tests/test_mazda.py +++ b/opendbc_repo/opendbc/safety/tests/test_mazda.py @@ -80,6 +80,8 @@ class TestMazdaSafety(common.CarSafetyTest, common.DriverTorqueSteeringSafetyTes self.assertTrue(self._tx(self._button_msg(cancel=True))) self.assertTrue(self._tx(self._button_msg(resume=True))) + # FrogPilot variables + if __name__ == "__main__": unittest.main() diff --git a/opendbc_repo/opendbc/safety/tests/test_nissan.py b/opendbc_repo/opendbc/safety/tests/test_nissan.py index 8f034cd7..7090695b 100755 --- a/opendbc_repo/opendbc/safety/tests/test_nissan.py +++ b/opendbc_repo/opendbc/safety/tests/test_nissan.py @@ -79,6 +79,8 @@ class TestNissanSafety(common.CarSafetyTest, common.AngleSteeringSafetyTest): tx = self._tx(self._acc_button_cmd(**args)) self.assertEqual(tx, should_tx) + # FrogPilot variables + class TestNissanSafetyAltEpsBus(TestNissanSafety): """Altima uses different buses""" @@ -113,6 +115,8 @@ class TestNissanLeafSafety(TestNissanSafety): def test_acc_buttons(self): pass + # FrogPilot variables + if __name__ == "__main__": unittest.main() diff --git a/opendbc_repo/opendbc/safety/tests/test_subaru.py b/opendbc_repo/opendbc/safety/tests/test_subaru.py index a0ad42d8..c7c3c8d6 100755 --- a/opendbc_repo/opendbc/safety/tests/test_subaru.py +++ b/opendbc_repo/opendbc/safety/tests/test_subaru.py @@ -111,6 +111,8 @@ class TestSubaruSafetyBase(common.CarSafetyTest): values = {"Cruise_Activated": enable} return self.packer.make_can_msg_safety("CruiseControl", self.ALT_MAIN_BUS, values) + # FrogPilot variables + class TestSubaruStockLongitudinalSafetyBase(TestSubaruSafetyBase): def _cancel_msg(self, cancel, cruise_throttle=0): diff --git a/opendbc_repo/opendbc/safety/tests/test_subaru_preglobal.py b/opendbc_repo/opendbc/safety/tests/test_subaru_preglobal.py index fb96de24..5d470187 100755 --- a/opendbc_repo/opendbc/safety/tests/test_subaru_preglobal.py +++ b/opendbc_repo/opendbc/safety/tests/test_subaru_preglobal.py @@ -59,6 +59,8 @@ class TestSubaruPreglobalSafety(common.CarSafetyTest, common.DriverTorqueSteerin values = {"Cruise_Activated": enable} return self.packer.make_can_msg_safety("CruiseControl", 0, values) + # FrogPilot variables + class TestSubaruPreglobalReversedDriverTorqueSafety(TestSubaruPreglobalSafety): FLAGS = SubaruSafetyFlags.PREGLOBAL_REVERSED_DRIVER_TORQUE diff --git a/opendbc_repo/opendbc/safety/tests/test_tesla.py b/opendbc_repo/opendbc/safety/tests/test_tesla.py index 52390f81..07df31d8 100755 --- a/opendbc_repo/opendbc/safety/tests/test_tesla.py +++ b/opendbc_repo/opendbc/safety/tests/test_tesla.py @@ -353,6 +353,8 @@ class TestTeslaSafetyBase(common.CarSafetyTest, common.AngleSteeringSafetyTest, # Recover self.assertTrue(self._tx(self._angle_cmd_msg(0, True))) + # FrogPilot variables + class TestTeslaStockSafety(TestTeslaSafetyBase): diff --git a/opendbc_repo/opendbc/safety/tests/test_toyota.py b/opendbc_repo/opendbc/safety/tests/test_toyota.py index dc94ddbe..3bdff15c 100755 --- a/opendbc_repo/opendbc/safety/tests/test_toyota.py +++ b/opendbc_repo/opendbc/safety/tests/test_toyota.py @@ -122,6 +122,8 @@ class TestToyotaSafetyBase(common.CarSafetyTest, common.LongitudinalAccelSafetyT self.assertFalse(self._rx(msg)) self.assertFalse(self.safety.get_controls_allowed()) + # FrogPilot variables + class TestToyotaSafetyTorque(TestToyotaSafetyBase, common.MotorTorqueSteeringSafetyTest, common.SteerRequestCutSafetyTest): @@ -394,5 +396,6 @@ class TestToyotaSecOcSafety(TestToyotaSecOcSafetyBase): self.assertEqual(should_tx, self._tx(self._accel_msg_343(accel, cancel_req=1))) + if __name__ == "__main__": unittest.main() diff --git a/opendbc_repo/opendbc/safety/tests/test_volkswagen_mqb.py b/opendbc_repo/opendbc/safety/tests/test_volkswagen_mqb.py index 47f77a8d..5418f913 100755 --- a/opendbc_repo/opendbc/safety/tests/test_volkswagen_mqb.py +++ b/opendbc_repo/opendbc/safety/tests/test_volkswagen_mqb.py @@ -126,6 +126,8 @@ class TestVolkswagenMqbSafetyBase(common.CarSafetyTest, common.DriverTorqueSteer self.assertEqual(0, self.safety.get_torque_driver_max()) self.assertEqual(0, self.safety.get_torque_driver_min()) + # FrogPilot variables + class TestVolkswagenMqbStockSafety(TestVolkswagenMqbSafetyBase): TX_MSGS = [[MSG_HCA_01, 0], [MSG_LDW_02, 0], [MSG_LH_EPS_03, 2], [MSG_GRA_ACC_01, 0], [MSG_GRA_ACC_01, 2]] diff --git a/opendbc_repo/opendbc/safety/tests/test_volkswagen_pq.py b/opendbc_repo/opendbc/safety/tests/test_volkswagen_pq.py index 72f0bfa6..bb099573 100755 --- a/opendbc_repo/opendbc/safety/tests/test_volkswagen_pq.py +++ b/opendbc_repo/opendbc/safety/tests/test_volkswagen_pq.py @@ -108,6 +108,8 @@ class TestVolkswagenPqSafetyBase(common.CarSafetyTest, common.DriverTorqueSteeri self.assertEqual(0, self.safety.get_torque_driver_max()) self.assertEqual(0, self.safety.get_torque_driver_min()) + # FrogPilot variables + class TestVolkswagenPqStockSafety(TestVolkswagenPqSafetyBase): # Transmit of GRA_Neu is allowed on bus 0 and 2 to keep compatibility with gateway and camera integration diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py old mode 100755 new mode 100644 index 42cdc1ff..772627be --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -63,6 +63,8 @@ class Car: RI: RadarInterfaceBase CP: car.CarParams + # FrogPilot variables + def __init__(self, CI=None, RI=None) -> None: self.can_sock = messaging.sub_sock('can', timeout=20) self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'onroadEvents']) @@ -105,6 +107,8 @@ class Car: # continue onto next fingerprinting step in pandad self.params.put_bool("FirmwareQueryDone", True) + + # FrogPilot variables else: self.CI, self.CP = CI, CI.CP self.RI = RI @@ -162,6 +166,8 @@ class Car: # OPGM variables self.resume_prev_button = False + # FrogPilot variables + def state_update(self) -> tuple[car.CarState, structs.RadarDataT | None]: """carState update loop, driven by can""" @@ -202,6 +208,8 @@ class Car: elif any(be.type in (ButtonType.decelCruise, ButtonType.setCruise) for be in CS.buttonEvents): self.resume_prev_button = False + # FrogPilot variables + return CS, RD def state_publish(self, CS: car.CarState, RD: structs.RadarDataT | None): @@ -234,6 +242,8 @@ class Car: tracks_msg.liveTracks = RD self.pm.send('liveTracks', tracks_msg) + # FrogPilot variables + def controls_update(self, CS: car.CarState, CC: car.CarControl): """control update loop, driven by carControl""" @@ -265,6 +275,9 @@ class Car: self.initialized_prev = initialized self.CS_prev = CS + # FrogPilot variables + self.CI.CS.CC = self.sm['carControl'] + def params_thread(self, evt): while not evt.is_set(): self.is_metric = self.params.get_bool("IsMetric") diff --git a/selfdrive/car/cruise.py b/selfdrive/car/cruise.py index 644337b9..187e13b8 100644 --- a/selfdrive/car/cruise.py +++ b/selfdrive/car/cruise.py @@ -109,6 +109,8 @@ class VCruiseHelper: else: self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type] + # FrogPilot variables + # If set is pressed while overriding, clip cruise speed to minimum of vEgo if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise): self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 1599c7c6..2f1e20f3 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -58,6 +58,8 @@ class Controls: elif self.CP.lateralTuning.which() == 'torque': self.LaC = LatControlTorque(self.CP, self.CI, DT_CTRL) + # FrogPilot variables + def update(self): self.sm.update(15) if self.sm.updated["liveCalibration"]: @@ -66,6 +68,8 @@ class Controls: device_pose = Pose.from_live_pose(self.sm['livePose']) self.calibrated_pose = self.pose_calibrator.build_calibrated_pose(device_pose) + # FrogPilot variables + def state_control(self): CS = self.sm['carState'] diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index ee4567f1..a77ab526 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -29,6 +29,8 @@ DESIRES = { }, } +# FrogPilot variables + class DesireHelper: def __init__(self): @@ -40,6 +42,8 @@ class DesireHelper: self.prev_one_blinker = False self.desire = log.Desire.none + # FrogPilot variables + @staticmethod def get_lane_change_direction(CS): return LaneChangeDirection.left if CS.leftBlinker else LaneChangeDirection.right @@ -72,12 +76,18 @@ class DesireHelper: blindspot_detected = ((carstate.leftBlindspot and self.lane_change_direction == LaneChangeDirection.left) or (carstate.rightBlindspot and self.lane_change_direction == LaneChangeDirection.right)) + # FrogPilot variables + if not one_blinker or below_lane_change_speed: self.lane_change_state = LaneChangeState.off self.lane_change_direction = LaneChangeDirection.none elif torque_applied and not blindspot_detected: self.lane_change_state = LaneChangeState.laneChangeStarting + # FrogPilot variables + + # FrogPilot variables + # LaneChangeState.laneChangeStarting elif self.lane_change_state == LaneChangeState.laneChangeStarting: # fade out over .5s @@ -117,3 +127,5 @@ class DesireHelper: self.keep_pulse_timer = 0.0 elif self.desire in (log.Desire.keepLeft, log.Desire.keepRight): self.desire = log.Desire.none + + # FrogPilot variables diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 34fc85f8..ad2f25e4 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -87,6 +87,9 @@ class LongitudinalPlanner: throttle_prob = model_msg.meta.disengagePredictions.gasPressProbs[1] else: throttle_prob = 1.0 + + # FrogPilot variables + return x, v, a, j, throttle_prob def update(self, sm): diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index bec7eede..6e5d5b33 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -22,6 +22,8 @@ def main(): sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState'], poll='modelV2') + # FrogPilot variables + while True: sm.update() if sm.updated['modelV2']: @@ -35,6 +37,8 @@ def main(): msg.driverAssistance.rightLaneDeparture = ldw.right pm.send('driverAssistance', msg) + # FrogPilot variables + if __name__ == "__main__": main() diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index 98fce1cb..f90fd54f 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -58,6 +58,8 @@ class Track: self.K_K = kalman_params.K self.kf = KF1D([[v_lead], [0.0]], self.K_A, self.K_C, self.K_K) + # FrogPilot variables + def update(self, d_rel: float, y_rel: float, v_rel: float, v_lead: float, measured: float): # relative values, copy self.dRel = d_rel # LONG_DIST @@ -109,6 +111,8 @@ class Track: ret = f"x: {self.dRel:4.1f} y: {self.yRel:4.1f} v: {self.vRel:4.1f} a: {self.aLeadK:4.1f}" return ret + # FrogPilot variables + def laplacian_pdf(x: float, mu: float, b: float): b = max(b, 1e-4) @@ -116,6 +120,8 @@ def laplacian_pdf(x: float, mu: float, b: float): def match_vision_to_track(v_ego: float, lead: capnp._DynamicStructReader, tracks: dict[int, Track]): + # FrogPilot variables + offset_vision_dist = lead.x[0] - RADAR_TO_CAMERA def prob(c): @@ -179,9 +185,14 @@ def get_lead(v_ego: float, ready: bool, tracks: dict[int, Track], lead_msg: capn if (not lead_dict['status']) or (closest_track.dRel < lead_dict['dRel']): lead_dict = closest_track.get_RadarState() + # FrogPilot variables + return lead_dict +# FrogPilot variables + + class RadarD: def __init__(self, delay: float = 0.0): self.current_time = 0.0 @@ -198,6 +209,8 @@ class RadarD: self.ready = False + # FrogPilot variables + def update(self, sm: messaging.SubMaster, rr: car.RadarData): self.ready = sm.seen['modelV2'] self.current_time = 1e-9*max(sm.logMonoTime.values()) @@ -242,6 +255,8 @@ class RadarD: self.radar_state.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, low_speed_override=True) self.radar_state.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, low_speed_override=False) + # FrogPilot variables + def publish(self, pm: messaging.PubMaster): assert self.radar_state is not None @@ -266,6 +281,8 @@ def main() -> None: RD = RadarD(CP.radarDelay) + # FrogPilot variables + while 1: sm.update() diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py index d7834f7f..7a53bce8 100755 --- a/selfdrive/locationd/lagd.py +++ b/selfdrive/locationd/lagd.py @@ -374,6 +374,8 @@ def main(): lag, valid_blocks = initial_lag_params lag_learner.reset(lag, valid_blocks) + # FrogPilot variables + while True: sm.update() if sm.all_checks(): @@ -392,3 +394,5 @@ def main(): if sm.frame % 1200 == 0: # cache every 60 seconds params.put_nonblocking("LiveDelay", lag_msg_dat) + + # FrogPilot variables diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index a5a7228e..41473bc5 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -283,6 +283,8 @@ def main(): steer_ratio, stiffness_factor, angle_offset_deg, pInitial = retrieve_initial_vehicle_params(params, CP, REPLAY, DEBUG) learner = VehicleParamsLearner(CP, steer_ratio, stiffness_factor, np.radians(angle_offset_deg), pInitial) + # FrogPilot variables + while True: sm.update() if sm.all_checks(): @@ -300,6 +302,8 @@ def main(): pm.send('liveParameters', msg_dat) + # FrogPilot variables + if __name__ == "__main__": main() diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 3f9b846e..86f44f57 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -251,6 +251,8 @@ def main(demo=False): params = Params() estimator = TorqueEstimator(messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)) + # FrogPilot variables + while True: sm.update() if sm.all_checks(): @@ -268,6 +270,8 @@ def main(demo=False): msg = estimator.get_msg(valid=sm.all_checks(), with_points=True) params.put_nonblocking("LiveTorqueParameters", msg.to_bytes()) + # FrogPilot variables + if __name__ == "__main__": import argparse diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 006eeef6..195c51da 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -225,6 +225,8 @@ class ModelState: def main(demo=False): cloudlog.warning("modeld init") + # FrogPilot variables + if not USBGPU: # USB GPU currently saturates a core so can't do this yet, # also need to move the aux USB interrupts for good timings @@ -294,6 +296,8 @@ def main(demo=False): DH = DesireHelper() + # FrogPilot variables + while True: # Keep receiving frames until we are at least 1 frame ahead of previous extra frame while meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000: @@ -397,8 +401,12 @@ def main(demo=False): pm.send('modelV2', modelv2_send) pm.send('drivingModelData', drivingdata_send) pm.send('cameraOdometry', posenet_send) + + # FrogPilot variables last_vipc_frame_id = meta_main.frame_id + # FrogPilot variables + if __name__ == "__main__": try: diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 1ac2c2dc..08e30955 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -15,6 +15,8 @@ def dmonitoringd_thread(): DM = DriverMonitoring(rhd_saved=params.get_bool("IsRhdDetected"), always_on=params.get_bool("AlwaysOnDM")) demo_mode=False + # FrogPilot variables + # 20Hz <- dmonitoringmodeld while True: sm.update() @@ -27,6 +29,7 @@ def dmonitoringd_thread(): DM.run_step(sm, demo=demo_mode) elif valid: DM.run_step(sm, demo=demo_mode) + # FrogPilot variables # publish dat = DM.get_state_packet(valid=valid) diff --git a/selfdrive/pandad/panda_safety.cc b/selfdrive/pandad/panda_safety.cc index b0895034..594b9426 100644 --- a/selfdrive/pandad/panda_safety.cc +++ b/selfdrive/pandad/panda_safety.cc @@ -65,6 +65,8 @@ void PandaSafety::setSafetyMode(const std::string ¶ms_string) { auto safety_configs = car_params.getSafetyConfigs(); uint16_t alternative_experience = car_params.getAlternativeExperience(); + // FrogPilot variables + for (int i = 0; i < pandas_.size(); ++i) { // Default to SILENT safety model if not specified cereal::CarParams::SafetyModel safety_model = cereal::CarParams::SafetyModel::SILENT; @@ -74,6 +76,8 @@ void PandaSafety::setSafetyMode(const std::string ¶ms_string) { safety_param = safety_configs[i].getSafetyParam(); } + // FrogPilot variables + LOGW("Panda %d: setting safety model: %d, param: %d, alternative experience: %d", i, (int)safety_model, safety_param, alternative_experience); pandas_[i]->set_alternative_experience(alternative_experience); pandas_[i]->set_safety_model(safety_model, safety_param); diff --git a/selfdrive/pandad/pandad.cc b/selfdrive/pandad/pandad.cc index 2fd4a4de..fe5a1b6d 100644 --- a/selfdrive/pandad/pandad.cc +++ b/selfdrive/pandad/pandad.cc @@ -43,6 +43,8 @@ ExitHandler do_exit; +// FrogPilot variables + bool check_all_connected(const std::vector &pandas) { for (const auto& panda : pandas) { if (!panda->connected()) { @@ -107,6 +109,8 @@ void can_send_thread(std::vector pandas, bool fake_send) { } else { LOGE("sendcan too old to send: %" PRIu64 ", %" PRIu64, nanos_since_boot(), event.getLogMonoTime()); } + + // FrogPilot variables } } @@ -421,9 +425,9 @@ void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control) } if (ir_pwr != prev_ir_pwr || sm.frame % 100 == 0) { - int16_t ir_panda = util::map_val(ir_pwr, 0, 100, 0, MAX_IR_PANDA_VAL); + int16_t ir_panda = util::map_val(ir_pwr, 0, 100, 0, MAX_IR_PANDA_VAL); panda->set_ir_pwr(ir_panda); - Hardware::set_ir_power(ir_pwr); + Hardware::set_ir_power(ir_pwr); prev_ir_pwr = ir_pwr; } } diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 1acfa296..e9b91fd8 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -9,6 +9,7 @@ from cereal import log, car import cereal.messaging as messaging from openpilot.common.constants import CV from openpilot.common.git import get_short_branch +from openpilot.common.params import Params from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER @@ -21,6 +22,8 @@ VisualAlert = car.CarControl.HUDControl.VisualAlert AudibleAlert = car.CarControl.HUDControl.AudibleAlert EventName = log.OnroadEvent.EventName +# FrogPilot variables + # Alert priorities class Priority(IntEnum): @@ -49,6 +52,8 @@ class ET: # get event name from enum EVENT_NAME = {v: k for k, v in EventName.schema.enumerants.items()} +# FrogPilot variables + class Events: def __init__(self): @@ -389,6 +394,9 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin return NormalPermanentAlert("Invalid LKAS setting", text) +# FrogPilot variables + + EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { # ********** events with no alerts ********** @@ -1020,6 +1028,10 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, } +# FrogPilot variables +FROGPILOT_EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { +} + if HARDWARE.get_device_type() == 'mici': EVENTS.update({ diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 997c7e37..2221c77b 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -39,6 +39,8 @@ EventName = log.OnroadEvent.EventName ButtonType = car.CarState.ButtonEvent.Type SafetyModel = car.CarParams.SafetyModel +# FrogPilot variables + IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) @@ -139,6 +141,8 @@ class SelfdriveD: elif self.CP.passive: self.events.add(EventName.dashcamMode, static=True) + # FrogPilot variables + def update_events(self, CS): """Compute onroadEvents from carState""" @@ -401,6 +405,8 @@ class SelfdriveD: self.params.put_nonblocking('LongitudinalPersonality', self.personality) self.events.add(EventName.personalityChanged) + # FrogPilot variables + def data_sample(self): _car_state = messaging.recv_one(self.car_state_sock) CS = _car_state.carState if _car_state else self.CS_prev @@ -461,6 +467,8 @@ class SelfdriveD: self.AM.add_many(self.sm.frame, alerts) self.AM.process_alerts(self.sm.frame, clear_event_types) + # FrogPilot variables + def publish_selfdriveState(self, CS): # selfdriveState ss_msg = messaging.new_message('selfdriveState') @@ -491,6 +499,8 @@ class SelfdriveD: self.pm.send('onroadEvents', ce_send) self.events_prev = self.events.names.copy() + # FrogPilot variables + def step(self): CS = self.data_sample() self.update_events(CS) @@ -502,6 +512,8 @@ class SelfdriveD: self.CS_prev = CS + # FrogPilot variables + def params_thread(self, evt): while not evt.is_set(): self.is_metric = self.params.get_bool("IsMetric") diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index ba9fa8b7..d89d991a 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -4,6 +4,8 @@ Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformat base_libs = [common, messaging, visionipc, transformations, 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] +# FrogPilot variables + if arch == 'larch64': base_libs.append('EGL') @@ -21,6 +23,10 @@ widgets_src = ["qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/prime_state.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] +# FrogPilot variables +frogpilot_widgets_src = ["../../frogpilot/ui/qt/widgets/frogpilot_controls.cc"] +widgets_src += frogpilot_widgets_src + widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) Export('widgets') qt_libs = [widgets, qt_util] + base_libs @@ -32,6 +38,11 @@ qt_src = ["main.cc", "ui.cc", "qt/sidebar.cc", "qt/body.cc", "qt/onroad/onroad_home.cc", "qt/onroad/annotated_camera.cc", "qt/onroad/model.cc", "qt/onroad/buttons.cc", "qt/onroad/alerts.cc", "qt/onroad/driver_monitoring.cc", "qt/onroad/hud.cc"] +# FrogPilot variables +frogpilot_src = ["../../frogpilot/ui/frogpilot_ui.cc", "../../frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc", + "../../frogpilot/ui/qt/onroad/frogpilot_buttons.cc", "../../frogpilot/ui/qt/onroad/frogpilot_onroad.cc"] +qt_src += frogpilot_src + # build translation files with open(File("translations/languages.json").abspath) as f: languages = json.loads(f.read()) diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 8b1fdf47..7d7f9147 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -42,13 +42,15 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { QObject::connect(uiState(), &UIState::uiUpdate, this, &HomeWindow::updateState); QObject::connect(uiState(), &UIState::offroadTransition, this, &HomeWindow::offroadTransition); QObject::connect(uiState(), &UIState::offroadTransition, sidebar, &Sidebar::offroadTransition); + + // FrogPilot variables } void HomeWindow::showSidebar(bool show) { sidebar->setVisible(show); } -void HomeWindow::updateState(const UIState &s) { +void HomeWindow::updateState(const UIState &s, const FrogPilotUIState &fs) { const SubMaster &sm = *(s.sm); // switch to the generic robot UI @@ -56,13 +58,22 @@ void HomeWindow::updateState(const UIState &s) { body->setEnabled(true); slayout->setCurrentWidget(body); } + + // FrogPilot variables + const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; } void HomeWindow::offroadTransition(bool offroad) { + // FrogPilot variables + FrogPilotUIState &fs = *frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + body->setEnabled(false); sidebar->setVisible(offroad); if (offroad) { slayout->setCurrentWidget(home); + + // FrogPilot variables } else { slayout->setCurrentWidget(onroad); } @@ -76,12 +87,16 @@ void HomeWindow::showDriverView(bool show) { slayout->setCurrentWidget(home); } sidebar->setVisible(show == false); + + // FrogPilot variables } void HomeWindow::mousePressEvent(QMouseEvent* e) { // Handle sidebar collapsing if ((onroad->isVisible() || body->isVisible()) && (!sidebar->isVisible() || e->x() > sidebar->width())) { sidebar->setVisible(!sidebar->isVisible()); + + // FrogPilot variables } } @@ -109,6 +124,8 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { header_layout->setContentsMargins(0, 0, 0, 0); header_layout->setSpacing(16); + // FrogPilot variables + update_notif = new QPushButton(tr("UPDATE")); update_notif->setVisible(false); update_notif->setStyleSheet("background-color: #364DEF;"); @@ -240,4 +257,8 @@ void OffroadHome::refresh() { if (alerts) { alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT"))); } + + // FrogPilot variables + FrogPilotUIState &fs = *frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; } diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index a19b70ac..4e5068d8 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -39,6 +39,8 @@ private: OffroadAlert* alerts_widget; QPushButton* alert_notif; QPushButton* update_notif; + + // FrogPilot variables }; class HomeWindow : public QWidget { @@ -68,6 +70,8 @@ private: DriverViewWindow *driver_view; QStackedLayout *slayout; + // FrogPilot variables + private slots: - void updateState(const UIState &s); + void updateState(const UIState &s, const FrogPilotUIState &fs); }; diff --git a/selfdrive/ui/qt/network/wifi_manager.h b/selfdrive/ui/qt/network/wifi_manager.h index cab932a3..76baec3b 100644 --- a/selfdrive/ui/qt/network/wifi_manager.h +++ b/selfdrive/ui/qt/network/wifi_manager.h @@ -69,6 +69,8 @@ public: void changeTetheringPassword(const QString &newPassword); QString getTetheringPassword(); + // FrogPilot variables + private: QString adapter; // Path to network manager wifi-device QTimer timer; diff --git a/selfdrive/ui/qt/offroad/developer_panel.cc b/selfdrive/ui/qt/offroad/developer_panel.cc index a095228d..9370d7cf 100644 --- a/selfdrive/ui/qt/offroad/developer_panel.cc +++ b/selfdrive/ui/qt/offroad/developer_panel.cc @@ -45,6 +45,8 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) { // Toggles should be not available to change in onroad state QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanel::updateToggles); + + // FrogPilot variables } void DeveloperPanel::updateToggles(bool _offroad) { @@ -59,6 +61,8 @@ void DeveloperPanel::updateToggles(bool _offroad) { if (btn != experimentalLongitudinalToggle) { btn->setEnabled(_offroad); } + + // FrogPilot variables } // longManeuverToggle and experimentalLongitudinalToggle should not be toggleable if the car does not have longitudinal control @@ -81,6 +85,8 @@ void DeveloperPanel::updateToggles(bool _offroad) { experimentalLongitudinalToggle->setVisible(CP.getAlphaLongitudinalAvailable() && !is_release); longManeuverToggle->setEnabled(hasLongitudinalControl(CP) && _offroad); + + // FrogPilot variables } else { longManeuverToggle->setEnabled(false); experimentalLongitudinalToggle->setVisible(false); @@ -88,8 +94,12 @@ void DeveloperPanel::updateToggles(bool _offroad) { experimentalLongitudinalToggle->refresh(); offroad = _offroad; + + // FrogPilot variables } void DeveloperPanel::showEvent(QShowEvent *event) { updateToggles(offroad); + + // FrogPilot variables } diff --git a/selfdrive/ui/qt/offroad/developer_panel.h b/selfdrive/ui/qt/offroad/developer_panel.h index c73421b1..e707cffd 100644 --- a/selfdrive/ui/qt/offroad/developer_panel.h +++ b/selfdrive/ui/qt/offroad/developer_panel.h @@ -8,6 +8,8 @@ public: explicit DeveloperPanel(SettingsWindow *parent); void showEvent(QShowEvent *event) override; +// FrogPilot variables + private: Params params; ParamControl* adbToggle; @@ -17,6 +19,8 @@ private: bool is_release; bool offroad = false; + // FrogPilot variables + private slots: void updateToggles(bool _offroad); }; diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 96734d69..e676456f 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -119,6 +119,8 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { // Toggles with confirmation dialogs toggles["ExperimentalMode"]->setActiveIcon("../assets/icons/experimental.svg"); toggles["ExperimentalMode"]->setConfirmation(true, true); + + // FrogPilot variables } void TogglesPanel::updateState(const UIState &s) { @@ -201,6 +203,10 @@ void TogglesPanel::updateToggles() { } else { experimental_mode_toggle->setDescription(e2e_description); } + + // FrogPilot variables + FrogPilotUIState &fs = *frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; } DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { @@ -406,6 +412,10 @@ void SettingsWindow::showEvent(QShowEvent *event) { setCurrentPanel(0); } +// FrogPilot variables +void SettingsWindow::hideEvent(QHideEvent *event) { +} + void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { if (!param.isEmpty()) { // Check if param ends with "Panel" to determine if it's a panel name @@ -454,7 +464,10 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { close_btn->setFixedSize(200, 200); sidebar_layout->addSpacing(45); sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter); - QObject::connect(close_btn, &QPushButton::clicked, this, &SettingsWindow::closeSettings); + QObject::connect(close_btn, &QPushButton::clicked, [this]() { + // FrogPilot variables + closeSettings(); + }); // setup panels DevicePanel *device = new DevicePanel(this); @@ -468,6 +481,8 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { auto networking = new Networking(this); QObject::connect(uiState()->prime_state, &PrimeState::changed, networking, &Networking::setPrimeType); + // FrogPilot variables + QList> panels = { {tr("Device"), device}, {tr("Network"), networking}, @@ -508,6 +523,8 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { panel_widget->addWidget(panel_frame); QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() { + // FrogPilot variables + btn->setChecked(true); panel_widget->setCurrentWidget(w); }); diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index d52cf16b..c5b7a5aa 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -25,6 +25,9 @@ public: protected: void showEvent(QShowEvent *event) override; + // FrogPilot variables + void hideEvent(QHideEvent *event) override; + signals: void closeSettings(); void reviewTrainingGuide(); @@ -32,11 +35,16 @@ signals: void expandToggleDescription(const QString ¶m); void scrollToToggle(const QString ¶m); + // FrogPilot variables + private: QPushButton *sidebar_alert_widget; QWidget *sidebar_widget; QButtonGroup *nav_btns; QStackedWidget *panel_widget; + + // FrogPilot variables + Params params; }; class DevicePanel : public ListWidget { @@ -65,6 +73,9 @@ public: explicit TogglesPanel(SettingsWindow *parent); void showEvent(QShowEvent *event) override; +signals: + // FrogPilot variables + public slots: void expandToggleDescription(const QString ¶m); void scrollToToggle(const QString ¶m); diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 9bc3fad3..346948f9 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -68,6 +68,8 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { params.put("UpdaterTargetBranch", selection.toStdString()); targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch"))); checkForUpdates(); + + // FrogPilot variables } }); if (!params.getBool("IsTestedBranch")) { @@ -101,9 +103,17 @@ void SoftwarePanel::showEvent(QShowEvent *event) { installBtn->setEnabled(true); updateLabels(); + + // FrogPilot variables + FrogPilotUIState &fs = *frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; } void SoftwarePanel::updateLabels() { + // FrogPilot variables + FrogPilotUIState &fs = *frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + // add these back in case the files got removed fs_watch->addParam("LastUpdateTime"); fs_watch->addParam("UpdateFailedCount"); @@ -111,6 +121,7 @@ void SoftwarePanel::updateLabels() { fs_watch->addParam("UpdateAvailable"); if (!isVisible()) { + // FrogPilot variables return; } @@ -124,6 +135,8 @@ void SoftwarePanel::updateLabels() { if (updater_state != "idle") { downloadBtn->setEnabled(false); downloadBtn->setValue(updater_state); + + // FrogPilot variables } else { if (failed) { downloadBtn->setText(tr("CHECK")); @@ -141,6 +154,8 @@ void SoftwarePanel::updateLabels() { downloadBtn->setValue(tr("up to date, last checked %1").arg(lastUpdate)); } downloadBtn->setEnabled(true); + + // FrogPilot variables } targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch"))); diff --git a/selfdrive/ui/qt/onroad/alerts.cc b/selfdrive/ui/qt/onroad/alerts.cc index 0236e20f..ee0e9008 100644 --- a/selfdrive/ui/qt/onroad/alerts.cc +++ b/selfdrive/ui/qt/onroad/alerts.cc @@ -5,27 +5,36 @@ #include "selfdrive/ui/qt/util.h" -void OnroadAlerts::updateState(const UIState &s) { +void OnroadAlerts::updateState(const UIState &s, const FrogPilotUIState &fs) { Alert a = getAlert(*(s.sm), s.scene.started_frame); if (!alert.equal(a)) { alert = a; update(); } + + // FrogPilot variables } void OnroadAlerts::clear() { alert = {}; update(); + + // FrogPilot variables + alertHeight = 0; } OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, uint64_t started_frame) { const cereal::SelfdriveState::Reader &ss = sm["selfdriveState"].getSelfdriveState(); const uint64_t selfdrive_frame = sm.rcv_frame("selfdriveState"); + // FrogPilot variables + Alert a = {}; if (selfdrive_frame >= started_frame) { // Don't get old alert. a = {ss.getAlertText1().cStr(), ss.getAlertText2().cStr(), ss.getAlertType().cStr(), ss.getAlertSize(), ss.getAlertStatus()}; + + // FrogPilot variables } if (!sm.updated("selfdriveState") && (sm.frame - started_frame) > 5 * UI_FREQ) { @@ -56,6 +65,8 @@ OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, uint64_t started void OnroadAlerts::paintEvent(QPaintEvent *event) { if (alert.size == cereal::SelfdriveState::AlertSize::NONE) { + // FrogPilot variables + alertHeight = 0; return; } static std::map alert_heights = { @@ -63,7 +74,9 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { {cereal::SelfdriveState::AlertSize::MID, 420}, {cereal::SelfdriveState::AlertSize::FULL, height()}, }; - int h = alert_heights[alert.size]; + // FrogPilot variables + alertHeight = alert_heights[alert.size]; + int h = alertHeight; int margin = 40; int radius = 30; @@ -71,6 +84,8 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { margin = 0; radius = 0; } + // FrogPilot variables + alertHeight -= margin; QRect r = QRect(0 + margin, height() - h + margin, width() - margin*2, h - margin*2); QPainter p(this); diff --git a/selfdrive/ui/qt/onroad/alerts.h b/selfdrive/ui/qt/onroad/alerts.h index de38d8ff..e1837634 100644 --- a/selfdrive/ui/qt/onroad/alerts.h +++ b/selfdrive/ui/qt/onroad/alerts.h @@ -9,9 +9,12 @@ class OnroadAlerts : public QWidget { public: OnroadAlerts(QWidget *parent = 0) : QWidget(parent) {} - void updateState(const UIState &s); + void updateState(const UIState &s, const FrogPilotUIState &fs); void clear(); + // FrogPilot variables + int alertHeight; + protected: struct Alert { QString text1; @@ -36,4 +39,6 @@ protected: QColor bg; Alert alert = {}; + + // FrogPilot variables }; diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index f504ad69..d179d740 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -19,12 +19,21 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget *par experimental_btn = new ExperimentalButton(this); main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight); + + // FrogPilot variables } -void AnnotatedCameraWidget::updateState(const UIState &s) { +void AnnotatedCameraWidget::updateState(const UIState &s, const FrogPilotUIState &fs) { // update engageability/experimental mode button - experimental_btn->updateState(s); + experimental_btn->updateState(s, fs); dmon.updateState(s); + + // FrogPilot variables + const SubMaster &sm = *(s.sm); + + const cereal::CarState::Reader &carState = sm["carState"].getCarState(); + + frogpilot_nvg->experimentalButtonPosition = QPoint(experimental_btn->x(), experimental_btn->y()); } void AnnotatedCameraWidget::initializeGL() { @@ -129,11 +138,22 @@ void AnnotatedCameraWidget::paintGL() { painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); + // FrogPilot variables + dmon.frogpilot_nvg = frogpilot_nvg; + hud.frogpilot_nvg = frogpilot_nvg; + model.frogpilot_nvg = frogpilot_nvg; + + experimental_btn->frogpilot_scene = frogpilot_scene; + model.frogpilot_scene = frogpilot_scene; + model.draw(painter, rect()); dmon.draw(painter, rect()); hud.updateState(*s); hud.draw(painter, rect()); + // FrogPilot variables + frogpilot_nvg->paintFrogPilotWidgets(painter, *s); + double cur_draw_t = millis_since_boot(); double dt = cur_draw_t - prev_draw_t; double fps = fps_filter.update(1. / dt * 1000); diff --git a/selfdrive/ui/qt/onroad/annotated_camera.h b/selfdrive/ui/qt/onroad/annotated_camera.h index d205579f..8953f4cc 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.h +++ b/selfdrive/ui/qt/onroad/annotated_camera.h @@ -8,12 +8,19 @@ #include "selfdrive/ui/qt/onroad/model.h" #include "selfdrive/ui/qt/widgets/cameraview.h" +#include "frogpilot/ui/qt/onroad/frogpilot_buttons.h" + class AnnotatedCameraWidget : public CameraWidget { Q_OBJECT public: explicit AnnotatedCameraWidget(VisionStreamType type, QWidget* parent = 0); - void updateState(const UIState &s); + void updateState(const UIState &s, const FrogPilotUIState &fs); + + // FrogPilot variables + FrogPilotAnnotatedCameraWidget *frogpilot_nvg; + + FrogPilotUIScene frogpilot_scene; private: QVBoxLayout *main_layout; @@ -26,6 +33,8 @@ private: int skip_frame_count = 0; bool wide_cam_requested = false; + // FrogPilot variables + protected: void paintGL() override; void initializeGL() override; diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc index 32e58c9d..45901634 100644 --- a/selfdrive/ui/qt/onroad/buttons.cc +++ b/selfdrive/ui/qt/onroad/buttons.cc @@ -22,17 +22,20 @@ ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(fals engage_img = loadPixmap("../assets/icons/chffr_wheel.png", {img_size, img_size}); experimental_img = loadPixmap("../assets/icons/experimental.svg", {img_size, img_size}); QObject::connect(this, &QPushButton::clicked, this, &ExperimentalButton::changeMode); + + // FrogPilot variables } void ExperimentalButton::changeMode() { const auto cp = (*uiState()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp) && params.getBool("ExperimentalModeConfirmed"); if (can_change) { + // FrogPilot variables params.putBool("ExperimentalMode", !experimental_mode); } } -void ExperimentalButton::updateState(const UIState &s) { +void ExperimentalButton::updateState(const UIState &s, const FrogPilotUIState &fs) { const auto cs = (*s.sm)["selfdriveState"].getSelfdriveState(); bool eng = cs.getEngageable() || cs.getEnabled(); if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) { @@ -40,6 +43,9 @@ void ExperimentalButton::updateState(const UIState &s) { experimental_mode = cs.getExperimentalMode(); update(); } + + // FrogPilot variables + const cereal::CarState::Reader &carState = (*s.sm)["carState"].getCarState(); } void ExperimentalButton::paintEvent(QPaintEvent *event) { @@ -47,3 +53,7 @@ void ExperimentalButton::paintEvent(QPaintEvent *event) { QPixmap img = experimental_mode ? experimental_img : engage_img; drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, QColor(0, 0, 0, 166), (isDown() || !engageable) ? 0.6 : 1.0); } + +// FrogPilot variables +void ExperimentalButton::showEvent(QShowEvent *event) { +} diff --git a/selfdrive/ui/qt/onroad/buttons.h b/selfdrive/ui/qt/onroad/buttons.h index 9c91bc3c..ec31c937 100644 --- a/selfdrive/ui/qt/onroad/buttons.h +++ b/selfdrive/ui/qt/onroad/buttons.h @@ -12,7 +12,10 @@ class ExperimentalButton : public QPushButton { public: explicit ExperimentalButton(QWidget *parent = 0); - void updateState(const UIState &s); + void updateState(const UIState &s, const FrogPilotUIState &fs); + + // FrogPilot variables + FrogPilotUIScene frogpilot_scene; private: void paintEvent(QPaintEvent *event) override; @@ -23,6 +26,9 @@ private: QPixmap experimental_img; bool experimental_mode; bool engageable; + + // FrogPilot variables + void showEvent(QShowEvent *event) override; }; void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity); diff --git a/selfdrive/ui/qt/onroad/driver_monitoring.cc b/selfdrive/ui/qt/onroad/driver_monitoring.cc index 49f2c950..9cc4c66e 100644 --- a/selfdrive/ui/qt/onroad/driver_monitoring.cc +++ b/selfdrive/ui/qt/onroad/driver_monitoring.cc @@ -73,6 +73,8 @@ void DriverMonitorRenderer::draw(QPainter &painter, const QRect &surface_rect) { float y = surface_rect.height() - offset; float opacity = is_active ? 0.65f : 0.2f; + // FrogPilot variables + drawIcon(painter, QPoint(x, y), dm_img, QColor(0, 0, 0, 70), opacity); QPointF keypoints[std::size(DEFAULT_FACE_KPTS_3D)]; @@ -104,4 +106,11 @@ void DriverMonitorRenderer::draw(QPainter &painter, const QRect &surface_rect) { painter.drawArc(QRectF(x - arc_l / 2, std::min(y + delta_y, y), arc_l, std::abs(delta_y)), (driver_pose_sins[0] > 0 ? 0 : 180) * 16, 180 * 16); painter.restore(); + + // FrogPilot variables + if (frogpilot_nvg) { + frogpilot_nvg->dmIconPosition.setX(x); + frogpilot_nvg->dmIconPosition.setY(y); + frogpilot_nvg->rightHandDM = is_rhd; + } } diff --git a/selfdrive/ui/qt/onroad/driver_monitoring.h b/selfdrive/ui/qt/onroad/driver_monitoring.h index 47db151c..6a27fabc 100644 --- a/selfdrive/ui/qt/onroad/driver_monitoring.h +++ b/selfdrive/ui/qt/onroad/driver_monitoring.h @@ -4,12 +4,17 @@ #include #include "selfdrive/ui/ui.h" +#include "frogpilot/ui/qt/onroad/frogpilot_annotated_camera.h" + class DriverMonitorRenderer { public: DriverMonitorRenderer(); void updateState(const UIState &s); void draw(QPainter &painter, const QRect &surface_rect); + // FrogPilot variables + FrogPilotAnnotatedCameraWidget *frogpilot_nvg; + private: float driver_pose_vals[3] = {}; float driver_pose_diff[3] = {}; diff --git a/selfdrive/ui/qt/onroad/hud.cc b/selfdrive/ui/qt/onroad/hud.cc index 4cfa3d0e..0ac34ef9 100644 --- a/selfdrive/ui/qt/onroad/hud.cc +++ b/selfdrive/ui/qt/onroad/hud.cc @@ -60,6 +60,9 @@ void HudRenderer::drawSetSpeed(QPainter &p, const QRect &surface_rect) { // Draw outer box + border to contain set speed const QSize default_size = {172, 204}; QSize set_speed_size = is_metric ? QSize(200, 204) : default_size; + + // FrogPilot variables + QRect set_speed_rect(QPoint(60 + (default_size.width() - set_speed_size.width()) / 2, 45), set_speed_size); // Draw set speed box @@ -91,6 +94,12 @@ void HudRenderer::drawSetSpeed(QPainter &p, const QRect &surface_rect) { p.setFont(InterFont(90, QFont::Bold)); p.setPen(set_speed_color); p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr); + + // FrogPilot variables + frogpilot_nvg->defaultSize = default_size; + frogpilot_nvg->isCruiseSet = is_cruise_set; + frogpilot_nvg->setSpeedRect = set_speed_rect; + frogpilot_nvg->speed = speed; } void HudRenderer::drawCurrentSpeed(QPainter &p, const QRect &surface_rect) { diff --git a/selfdrive/ui/qt/onroad/hud.h b/selfdrive/ui/qt/onroad/hud.h index b2ac379d..4457f24f 100644 --- a/selfdrive/ui/qt/onroad/hud.h +++ b/selfdrive/ui/qt/onroad/hud.h @@ -3,6 +3,8 @@ #include #include "selfdrive/ui/ui.h" +#include "frogpilot/ui/qt/onroad/frogpilot_annotated_camera.h" + class HudRenderer : public QObject { Q_OBJECT @@ -11,6 +13,9 @@ public: void updateState(const UIState &s); void draw(QPainter &p, const QRect &surface_rect); + // FrogPilot variables + FrogPilotAnnotatedCameraWidget *frogpilot_nvg; + private: void drawSetSpeed(QPainter &p, const QRect &surface_rect); void drawCurrentSpeed(QPainter &p, const QRect &surface_rect); diff --git a/selfdrive/ui/qt/onroad/model.cc b/selfdrive/ui/qt/onroad/model.cc index 52902abd..478da7a7 100644 --- a/selfdrive/ui/qt/onroad/model.cc +++ b/selfdrive/ui/qt/onroad/model.cc @@ -42,12 +42,18 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) { const auto &lead_two = radar_state.getLeadTwo(); if (lead_one.getStatus()) { drawLead(painter, lead_one, lead_vertices[0], surface_rect); - } + } else { + // FrogPilot variables if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) { drawLead(painter, lead_two, lead_vertices[1], surface_rect); } + + // FrogPilot variables + SubMaster &fpsm = *(frogpilotUIState()->sm); } + // FrogPilot variables + painter.restore(); } @@ -88,7 +94,12 @@ void ModelRenderer::update_model(const cereal::ModelDataV2::Reader &model, const max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); } max_idx = get_path_length_idx(model_position, max_distance); + // FrogPilot variables mapLineToPolygon(model_position, 0.9, path_offset_z, &track_vertices, max_idx, false); + + // FrogPilot variables + FrogPilotUIState *fs = frogpilotUIState(); + SubMaster &fpsm = *(fs->sm); } void ModelRenderer::drawLaneLines(QPainter &painter) { @@ -140,6 +151,8 @@ void ModelRenderer::drawPath(QPainter &painter, const cereal::ModelDataV2::Reade painter.setBrush(bg); painter.drawPolygon(track_vertices); + + // FrogPilot variables } void ModelRenderer::updatePathGradient(QLinearGradient &bg) { @@ -217,6 +230,8 @@ void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadDa QPointF chevron[] = {{x + (sz * 1.25), y + sz}, {x, y}, {x - (sz * 1.25), y + sz}}; painter.setBrush(QColor(201, 34, 49, fillAlpha)); painter.drawPolygon(chevron, std::size(chevron)); + + // FrogPilot variables } // Projects a point in car to space to the corresponding point in full frame image space. @@ -248,3 +263,5 @@ void ModelRenderer::mapLineToPolygon(const cereal::XYZTData::Reader &line, float } } } + +// FrogPilot variables diff --git a/selfdrive/ui/qt/onroad/model.h b/selfdrive/ui/qt/onroad/model.h index 79547e4b..ddad6a13 100644 --- a/selfdrive/ui/qt/onroad/model.h +++ b/selfdrive/ui/qt/onroad/model.h @@ -5,12 +5,19 @@ #include "selfdrive/ui/ui.h" +#include "frogpilot/ui/qt/onroad/frogpilot_annotated_camera.h" + class ModelRenderer { public: ModelRenderer() {} void setTransform(const Eigen::Matrix3f &transform) { car_space_transform = transform; } void draw(QPainter &painter, const QRect &surface_rect); + // FrogPilot variables + FrogPilotAnnotatedCameraWidget *frogpilot_nvg; + + FrogPilotUIScene frogpilot_scene; + private: bool mapToScreen(float in_x, float in_y, float in_z, QPointF *out); void mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off, @@ -36,4 +43,6 @@ private: QPointF lead_vertices[2] = {}; Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero(); QRectF clip_region; + + // FrogPilot variables }; diff --git a/selfdrive/ui/qt/onroad/onroad_home.cc b/selfdrive/ui/qt/onroad/onroad_home.cc index 080f9bd5..e9bcfb02 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.cc +++ b/selfdrive/ui/qt/onroad/onroad_home.cc @@ -37,15 +37,27 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); QObject::connect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState); QObject::connect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition); + + // FrogPilot variables + frogpilot_nvg = new FrogPilotAnnotatedCameraWidget(this); + frogpilot_onroad = new FrogPilotOnroadWindow(this); + frogpilot_onroad->setAttribute(Qt::WA_TransparentForMouseEvents, true); + + stacked_layout->addWidget(frogpilot_nvg); + stacked_layout->addWidget(frogpilot_onroad); + + frogpilot_onroad->raise(); + + nvg->frogpilot_nvg = frogpilot_nvg; } -void OnroadWindow::updateState(const UIState &s) { +void OnroadWindow::updateState(const UIState &s, const FrogPilotUIState &fs) { if (!s.scene.started) { return; } - alerts->updateState(s); - nvg->updateState(s); + alerts->updateState(s, fs); + nvg->updateState(s, fs); QColor bgColor = bg_colors[s.status]; if (bg != bgColor) { @@ -53,6 +65,24 @@ void OnroadWindow::updateState(const UIState &s) { bg = bgColor; update(); } + + // FrogPilot variables + const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + + frogpilot_nvg->alertHeight = alerts->alertHeight; + + frogpilot_onroad->bg = bg; + + nvg->frogpilot_nvg = frogpilot_nvg; + + nvg->frogpilot_scene = frogpilot_scene; + frogpilot_nvg->frogpilot_scene = frogpilot_scene; + frogpilot_onroad->frogpilot_scene = frogpilot_scene; + + frogpilot_onroad->setGeometry(rect()); + + frogpilot_nvg->updateState(s, fs); + frogpilot_onroad->updateState(s, fs); } void OnroadWindow::offroadTransition(bool offroad) { @@ -63,3 +93,15 @@ void OnroadWindow::paintEvent(QPaintEvent *event) { QPainter p(this); p.fillRect(rect(), QColor(bg.red(), bg.green(), bg.blue(), 255)); } + +// FrogPilot variables +void OnroadWindow::mousePressEvent(QMouseEvent* mouseEvent) { + frogpilot_nvg->mousePressEvent(mouseEvent); + + if (mouseEvent->isAccepted()) { + return; + } + + // propagation event to parent(HomeWindow) + QWidget::mousePressEvent(mouseEvent); +} diff --git a/selfdrive/ui/qt/onroad/onroad_home.h b/selfdrive/ui/qt/onroad/onroad_home.h index c321d2d4..484ceaad 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.h +++ b/selfdrive/ui/qt/onroad/onroad_home.h @@ -3,6 +3,8 @@ #include "selfdrive/ui/qt/onroad/alerts.h" #include "selfdrive/ui/qt/onroad/annotated_camera.h" +#include "frogpilot/ui/qt/onroad/frogpilot_onroad.h" + class OnroadWindow : public QWidget { Q_OBJECT @@ -16,7 +18,13 @@ private: QColor bg = bg_colors[STATUS_DISENGAGED]; QHBoxLayout* split; + // FrogPilot variables + void mousePressEvent(QMouseEvent* mouseEvent); + + FrogPilotAnnotatedCameraWidget *frogpilot_nvg; + FrogPilotOnroadWindow *frogpilot_onroad; + private slots: void offroadTransition(bool offroad); - void updateState(const UIState &s); + void updateState(const UIState &s, const FrogPilotUIState &fs); }; diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index e9c6a2f7..2d217d00 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -40,9 +40,15 @@ Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed( QObject::connect(uiState(), &UIState::uiUpdate, this, &Sidebar::updateState); pm = std::make_unique(std::vector{"bookmarkButton"}); + + // FrogPilot variables } void Sidebar::mousePressEvent(QMouseEvent *event) { + // FrogPilot variables + FrogPilotUIState *fs = frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs->frogpilot_scene; + if (onroad && home_btn.contains(event->pos())) { flag_pressed = true; update(); @@ -76,9 +82,14 @@ void Sidebar::offroadTransition(bool offroad) { update(); } -void Sidebar::updateState(const UIState &s) { +void Sidebar::updateState(const UIState &s, const FrogPilotUIState &fs) { if (!isVisible()) return; + // FrogPilot variables + const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + + const SubMaster &fpsm = *(fs.sm); + auto &sm = *(s.sm); networking = networking ? networking : window()->findChild(""); @@ -115,6 +126,8 @@ void Sidebar::updateState(const UIState &s) { setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); setProperty("recordingAudio", s.scene.recording_audio); + + // FrogPilot variables } void Sidebar::paintEvent(QPaintEvent *event) { @@ -139,6 +152,10 @@ void Sidebar::paintEvent(QPaintEvent *event) { } p.setOpacity(1.0); + // FrogPilot variables + FrogPilotUIState *fs = frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs->frogpilot_scene; + // network int x = 58; const QColor gray(0x54, 0x54, 0x54); @@ -163,3 +180,7 @@ void Sidebar::paintEvent(QPaintEvent *event) { drawMetric(p, panda_status.first, panda_status.second, 496); drawMetric(p, connect_status.first, connect_status.second, 654); } + +// FrogPilot variables +void Sidebar::showEvent(QShowEvent *event) { +} diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 6a2b7b69..24611786 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -20,6 +20,8 @@ class Sidebar : public QFrame { Q_PROPERTY(int netStrength MEMBER net_strength NOTIFY valueChanged); Q_PROPERTY(bool recordingAudio MEMBER recording_audio NOTIFY valueChanged); + // FrogPilot properties + public: explicit Sidebar(QWidget* parent = 0); @@ -29,7 +31,7 @@ signals: public slots: void offroadTransition(bool offroad); - void updateState(const UIState &s); + void updateState(const UIState &s, const FrogPilotUIState &fs); protected: void paintEvent(QPaintEvent *event) override; @@ -60,7 +62,14 @@ protected: QString net_type; int net_strength = 0; + // FrogPilot variables + private: std::unique_ptr pm; Networking *networking = nullptr; + + // FrogPilot variables + void showEvent(QShowEvent *event); + + Params params; }; diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 3342de53..4dc094a2 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -67,6 +67,8 @@ public slots: signals: void showDescriptionEvent(); + // FrogPilot variables + protected: AbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); void hideEvent(QHideEvent *e) override; @@ -132,6 +134,8 @@ public: toggle.update(); } + // FrogPilot variables + signals: void toggleFlipped(bool state); diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index 0cbf1493..cec7c418 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -141,6 +141,8 @@ InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &s QObject::connect(k, &Keyboard::emitEnter, this, &InputDialog::handleEnter); QObject::connect(k, &Keyboard::emitBackspace, this, [=]() { line->backspace(); + + // FrogPilot variables }); QObject::connect(k, &Keyboard::emitKey, this, [=](const QString &key) { line->insert(key.left(1)); @@ -154,6 +156,9 @@ QString InputDialog::getText(const QString &prompt, QWidget *parent, const QStri InputDialog d(prompt, parent, subtitle, secret); d.line->setText(defaultText); d.setMinLength(minLength); + + // FrogPilot variables + const int ret = d.exec(); return ret ? d.text() : QString(); } @@ -186,6 +191,8 @@ void InputDialog::setMinLength(int length) { minLength = length; } +// FrogPilot variables + // ConfirmationDialog ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, const QString &cancel_text, diff --git a/selfdrive/ui/qt/widgets/input.h b/selfdrive/ui/qt/widgets/input.h index 089e54e4..2cbbe21a 100644 --- a/selfdrive/ui/qt/widgets/input.h +++ b/selfdrive/ui/qt/widgets/input.h @@ -33,6 +33,8 @@ public: void setMinLength(int length); void show(); + // FrogPilot variables + private: int minLength; QLineEdit *line; @@ -42,6 +44,8 @@ private: QVBoxLayout *main_layout; QPushButton *eye_btn; + // FrogPilot variables + private slots: void handleEnter(); diff --git a/selfdrive/ui/qt/widgets/ssh_keys.cc b/selfdrive/ui/qt/widgets/ssh_keys.cc index 26743952..83213d59 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.cc +++ b/selfdrive/ui/qt/widgets/ssh_keys.cc @@ -19,6 +19,7 @@ SshControl::SshControl() : } else { params.remove("GithubUsername"); params.remove("GithubSshKeys"); + refresh(); } }); diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 6b579fcc..fd416b5b 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -79,6 +79,10 @@ void MainWindow::closeSettings() { } bool MainWindow::eventFilter(QObject *obj, QEvent *event) { + // FrogPilot variables + FrogPilotUIState &fs = *frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + bool ignore = false; switch (event->type()) { case QEvent::TouchBegin: diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 05b61e1f..87e907f8 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -22,4 +22,6 @@ private: HomeWindow *homeWindow; SettingsWindow *settingsWindow; OnboardingWindow *onboardingWindow; + + // FrogPilot variables }; diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 704a6a59..c5797333 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -30,6 +30,8 @@ if HARDWARE.get_device_type() in ("tici", "tizi"): AudibleAlert = car.CarControl.HUDControl.AudibleAlert +# FrogPilot variables + sound_list: dict[int, tuple[str, int | None, float]] = { # AudibleAlert, file name, play count (none for infinite) @@ -43,6 +45,8 @@ sound_list: dict[int, tuple[str, int | None, float]] = { AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME), AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME), + + # FrogPilot variables } if HARDWARE.get_device_type() in ("tici", "tizi"): sound_list.update({ @@ -72,6 +76,9 @@ class Soundd: self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) + # FrogPilot variables + self.update_frogpilot_sounds() + def load_sounds(self): self.loaded_sounds: dict[int, np.ndarray] = {} @@ -122,6 +129,9 @@ class Soundd: def get_audible_alert(self, sm): if sm.updated['selfdriveState']: new_alert = sm['selfdriveState'].alertSound.raw + + # FrogPilot variables + self.update_alert(new_alert) elif check_selfdrive_timeout_alert(sm): self.update_alert(AudibleAlert.warningImmediate) @@ -147,6 +157,8 @@ class Soundd: sm = messaging.SubMaster(['selfdriveState', 'soundPressure']) + # FrogPilot variables + with self.get_stream(sd) as stream: rk = Ratekeeper(20) @@ -164,6 +176,10 @@ class Soundd: assert stream.active + # FrogPilot variables + + def update_frogpilot_sounds(self): + def main(): s = Soundd() diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 4f8bd7dd..4109a3b6 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -18,7 +18,7 @@ static void update_sockets(UIState *s) { s->sm->update(0); } -static void update_state(UIState *s) { +static void update_state(UIState *s, FrogPilotUIState *fs) { SubMaster &sm = *(s->sm); UIScene &scene = s->scene; @@ -63,6 +63,20 @@ static void update_state(UIState *s) { auto params = Params(); scene.recording_audio = params.getBool("RecordAudio") && scene.started; + + // FrogPilot variables + FrogPilotUIScene &frogpilot_scene = fs->frogpilot_scene; + + if (sm.updated("carState")) { + const cereal::CarState::Reader &carState = sm["carState"].getCarState(); + frogpilot_scene.parked = carState.getGearShifter() == cereal::CarState::GearShifter::PARK; + frogpilot_scene.reverse = carState.getGearShifter() == cereal::CarState::GearShifter::REVERSE; + frogpilot_scene.standstill = carState.getStandstill() && !frogpilot_scene.reverse; + } + + if (scene.started) { + frogpilot_scene.started_timer += 1; + } } void ui_update_params(UIState *s) { @@ -70,15 +84,23 @@ void ui_update_params(UIState *s) { s->scene.is_metric = params.getBool("IsMetric"); } -void UIState::updateStatus() { +void UIState::updateStatus(FrogPilotUIState *fs) { + // FrogPilot variables + FrogPilotUIScene &frogpilot_scene = fs->frogpilot_scene; + if (scene.started && sm->updated("selfdriveState")) { auto ss = (*sm)["selfdriveState"].getSelfdriveState(); auto state = ss.getState(); + + // FrogPilot variables + if (state == cereal::SelfdriveState::OpenpilotState::PRE_ENABLED || state == cereal::SelfdriveState::OpenpilotState::OVERRIDING) { status = STATUS_OVERRIDE; } else { status = ss.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; } + + // FrogPilot variables } if (engaged() != engaged_prev) { @@ -94,6 +116,8 @@ void UIState::updateStatus() { } started_prev = scene.started; emit offroadTransition(!scene.started); + + // FrogPilot variables } } @@ -114,13 +138,19 @@ UIState::UIState(QObject *parent) : QObject(parent) { void UIState::update() { update_sockets(this); - update_state(this); - updateStatus(); + update_state(this, frogpilotUIState()); + updateStatus(frogpilotUIState()); if (sm->frame % UI_FREQ == 0) { watchdog_kick(nanos_since_boot()); } - emit uiUpdate(*this); + emit uiUpdate(*this, *frogpilotUIState()); + + // FrogPilot variables + FrogPilotUIState *fs = frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs->frogpilot_scene; + + fs->update(); } Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) { @@ -130,9 +160,9 @@ Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update); } -void Device::update(const UIState &s) { - updateBrightness(s); - updateWakefulness(s); +void Device::update(const UIState &s, const FrogPilotUIState &fs) { + updateBrightness(s, fs); + updateWakefulness(s, fs); } void Device::setAwake(bool on) { @@ -147,11 +177,16 @@ void Device::setAwake(bool on) { void Device::resetInteractiveTimeout(int timeout) { if (timeout == -1) { timeout = (ignition_on ? 10 : 30); + } else { + // FrogPilot variables } interactive_timeout = timeout * UI_FREQ; } -void Device::updateBrightness(const UIState &s) { +void Device::updateBrightness(const UIState &s, const FrogPilotUIState &fs) { + // FrogPilot variables + const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + float clipped_brightness = offroad_brightness; if (s.scene.started && s.scene.light_sensor >= 0) { clipped_brightness = s.scene.light_sensor; @@ -180,7 +215,10 @@ void Device::updateBrightness(const UIState &s) { } } -void Device::updateWakefulness(const UIState &s) { +void Device::updateWakefulness(const UIState &s, const FrogPilotUIState &fs) { + // FrogPilot variables + const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + bool ignition_just_turned_off = !s.scene.ignition && ignition_on; ignition_on = s.scene.ignition; diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index b3c482aa..b992d19a 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -15,6 +15,8 @@ #include "system/hardware/hw.h" #include "selfdrive/ui/qt/prime_state.h" +#include "frogpilot/ui/frogpilot_ui.h" + const int UI_BORDER_SIZE = 30; const int UI_HEADER_HEIGHT = 420; @@ -42,12 +44,18 @@ typedef enum UIStatus { STATUS_DISENGAGED, STATUS_OVERRIDE, STATUS_ENGAGED, + + // FrogPilot variables + STATUS_EXPERIMENTAL_MODE_ENABLED, } UIStatus; const QColor bg_colors [] = { [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), [STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1), + + // FrogPilot variables + [STATUS_EXPERIMENTAL_MODE_ENABLED] = QColor(0xda, 0x6f, 0x25, 0xf1), }; typedef struct UIScene { @@ -67,7 +75,7 @@ class UIState : public QObject { public: UIState(QObject* parent = 0); - void updateStatus(); + void updateStatus(FrogPilotUIState *fs); inline bool engaged() const { return scene.started && (*sm)["selfdriveState"].getSelfdriveState().getEnabled(); } @@ -79,7 +87,7 @@ public: PrimeState *prime_state; signals: - void uiUpdate(const UIState &s); + void uiUpdate(const UIState &s, const FrogPilotUIState &fs); void offroadTransition(bool offroad); void engagedChanged(bool engaged); @@ -115,8 +123,8 @@ private: FirstOrderFilter brightness_filter; QFuture brightness_future; - void updateBrightness(const UIState &s); - void updateWakefulness(const UIState &s); + void updateBrightness(const UIState &s, const FrogPilotUIState &fs); + void updateWakefulness(const UIState &s, const FrogPilotUIState &fs); void setAwake(bool on); signals: @@ -125,7 +133,7 @@ signals: public slots: void resetInteractiveTimeout(int timeout = -1); - void update(const UIState &s); + void update(const UIState &s, const FrogPilotUIState &fs); }; Device *device(); diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 8ddc4da2..313f9a59 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -212,6 +212,8 @@ def hardware_thread(end_event, hw_queue) -> None: fan_controller = None + # FrogPilot variables + while not end_event.is_set(): sm.update(PANDA_STATES_TIMEOUT) @@ -292,6 +294,8 @@ def hardware_thread(end_event, hw_queue) -> None: if fan_controller is not None: msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"]) + # FrogPilot variables + is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (time.monotonic() - off_ts > 60 * 5)) if is_offroad_for_5_min and offroad_comp_temp > OFFROAD_DANGER_TEMP: # if device is offroad and already hot without the extra onroad load, @@ -341,6 +345,8 @@ def hardware_thread(end_event, hw_queue) -> None: if started_ts is None: should_start = should_start and all(startup_conditions.values()) + # FrogPilot variables + if should_start != should_start_prev or (count == 0): params.put_bool("IsEngaged", False) engaged_prev = False @@ -410,6 +416,8 @@ def hardware_thread(end_event, hw_queue) -> None: msg.deviceState.thermalStatus = thermal_status pm.send("deviceState", msg) + # FrogPilot variables + # Log to statsd statlog.gauge("free_space_percent", msg.deviceState.freeSpacePercent) statlog.gauge("gpu_usage_percent", msg.deviceState.gpuUsagePercent) @@ -463,6 +471,8 @@ def hardware_thread(end_event, hw_queue) -> None: count += 1 should_start_prev = should_start + # FrogPilot variables + def main(): hw_queue = queue.Queue(maxsize=1) diff --git a/system/hardware/hw.h b/system/hardware/hw.h index d2083a59..f837a4fa 100644 --- a/system/hardware/hw.h +++ b/system/hardware/hw.h @@ -26,7 +26,14 @@ namespace Path { if (const char *env = getenv("LOG_ROOT")) { return env; } - return Hardware::PC() ? Path::comma_home() + "/media/0/realdata" : "/data/media/0/realdata"; + + if (Hardware::PC()) { + return Path::comma_home() + "/media/0/realdata"; + } + + // FrogPilot variables + + return "/data/media/0/realdata"; } inline std::string params() { diff --git a/system/loggerd/config.py b/system/loggerd/config.py index e1c47c76..ece38d0c 100644 --- a/system/loggerd/config.py +++ b/system/loggerd/config.py @@ -27,3 +27,6 @@ def get_available_bytes(default: int) -> int: available_bytes = default return available_bytes + + +# FrogPilot variables diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 5b6234e1..6f8fd9a5 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -248,6 +248,9 @@ def main(exit_event: threading.Event = None) -> None: uploader = Uploader(dongle_id, Paths.log_root()) backoff = 0.1 + + # FrogPilot variables + while not exit_event.is_set(): sm.update(0) offroad = params.get_bool("IsOffroad") @@ -268,6 +271,8 @@ def main(exit_event: threading.Event = None) -> None: if allow_sleep: time.sleep(backoff + random.uniform(0, backoff)) + # FrogPilot variables + if __name__ == "__main__": main() diff --git a/system/manager/manager.py b/system/manager/manager.py index 1d3c0cc7..5e701887 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -21,6 +21,8 @@ from openpilot.common.swaglog import cloudlog, add_file_handler from openpilot.system.version import get_build_metadata, terms_version, training_version from openpilot.system.hardware.hw import Paths +from openpilot.frogpilot.common.frogpilot_functions import frogpilot_boot_functions, install_frogpilot, uninstall_frogpilot + def manager_init() -> None: save_bootlog() @@ -38,6 +40,8 @@ def manager_init() -> None: if params.get_bool("RecordFrontLock"): params.put_bool("RecordFront", True) + # FrogPilot variables + # set unset params to their default value for k in params.all_keys(): default_value = params.get_default_value(k) @@ -93,6 +97,10 @@ def manager_init() -> None: for p in managed_processes.values(): p.prepare() + # FrogPilot variables + install_frogpilot() + frogpilot_boot_functions() + def manager_cleanup() -> None: # send signals to kill all procs @@ -129,6 +137,8 @@ def manager_thread() -> None: started_prev = False ignition_prev = False + # FrogPilot variables + while True: sm.update(1000) @@ -136,9 +146,13 @@ def manager_thread() -> None: if started and not started_prev: params.clear_all(ParamKeyFlag.CLEAR_ON_ONROAD_TRANSITION) + + # FrogPilot variables elif not started and started_prev: params.clear_all(ParamKeyFlag.CLEAR_ON_OFFROAD_TRANSITION) + # FrogPilot variables + ignition = any(ps.ignitionLine or ps.ignitionCan for ps in sm['pandaStates'] if ps.pandaType != log.PandaState.PandaType.unknown) if ignition and not ignition_prev: params.clear_all(ParamKeyFlag.CLEAR_ON_IGNITION_ON) @@ -181,6 +195,8 @@ def manager_thread() -> None: if shutdown: break + # FrogPilot variables + def main() -> None: manager_init() @@ -201,7 +217,7 @@ def main() -> None: params = Params() if params.get_bool("DoUninstall"): cloudlog.warning("uninstalling") - HARDWARE.uninstall() + uninstall_frogpilot() elif params.get_bool("DoReboot"): cloudlog.warning("reboot") HARDWARE.reboot() diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 5bb9ee25..54efc45b 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -4,7 +4,7 @@ import platform from cereal import car from openpilot.common.params import Params -from openpilot.system.hardware import PC, TICI +from openpilot.system.hardware import HARDWARE, PC, TICI from openpilot.system.manager.process import PythonProcess, NativeProcess, DaemonProcess WEBCAM = os.getenv("USE_WEBCAM") is not None @@ -61,6 +61,8 @@ def or_(*fns): def and_(*fns): return lambda *args: operator.and_(*(fn(*args) for fn in fns)) +# FrogPilot variables + procs = [ DaemonProcess("manage_athenad", "system.athena.manage_athenad", "AthenadPid"), @@ -80,7 +82,6 @@ procs = [ PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)), PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC), - NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), PythonProcess("soundd", "selfdrive.ui.soundd", driverview), PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad), NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False), @@ -115,4 +116,13 @@ procs = [ PythonProcess("joystick", "tools.joystick.joystick_control", and_(joystick, iscar)), ] +# FrogPilot variables +if HARDWARE.get_device_type() == "mici": + procs.append(PythonProcess("ui", "selfdrive.ui.ui", always_run)) +elif TICI: + procs.append(NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=5)), +procs += [ + PythonProcess("frogpilot_process", "frogpilot.frogpilot_process", always_run), +] + managed_processes = {p.name: p for p in procs} diff --git a/system/timed.py b/system/timed.py index b7131b04..69851738 100755 --- a/system/timed.py +++ b/system/timed.py @@ -24,6 +24,9 @@ def set_time(new_time): cloudlog.exception("timed.failed_setting_time") +# FrogPilot variables + + def main() -> NoReturn: """ timed has two responsibilities: @@ -38,6 +41,9 @@ def main() -> NoReturn: pm = messaging.PubMaster(['clocks']) sm = messaging.SubMaster([gps_location_service]) + + # FrogPilot variables + while True: sm.update(1000) @@ -56,6 +62,9 @@ def main() -> NoReturn: continue set_time(gps_time) + + # FrogPilot variables + time.sleep(10) if __name__ == "__main__": diff --git a/system/ui/spinner.py b/system/ui/spinner.py index 2a48b388..2d5e7037 100755 --- a/system/ui/spinner.py +++ b/system/ui/spinner.py @@ -27,6 +27,8 @@ FONT_SIZE = 96 LINE_HEIGHT = 104 DARKGRAY = (55, 55, 55, 255) +# FrogPilot variables + def clamp(value, min_value, max_value): return max(min(value, max_value), min_value) diff --git a/system/updated/updated.py b/system/updated/updated.py index a4a1f8f3..9b4b4198 100755 --- a/system/updated/updated.py +++ b/system/updated/updated.py @@ -410,6 +410,7 @@ class Updater: finalize_update() cloudlog.info("finalize success!") + # FrogPilot variables def main() -> None: params = Params() @@ -449,9 +450,14 @@ def main() -> None: # Run the update loop first_run = True + + # FrogPilot variables + while True: wait_helper.ready_event.clear() + # FrogPilot variables + # Attempt an update exception = None try: