diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 78b957a468..5c5f8d78d7 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -301,6 +301,7 @@ jobs: timeout-minutes: 30 run: | ${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ + chmod -R 777 /tmp/comma_download_cache && \ coverage xml" - name: Print diff id: print-diff diff --git a/common/params.cc b/common/params.cc index e8ab42c0b0..63baa30315 100644 --- a/common/params.cc +++ b/common/params.cc @@ -158,6 +158,7 @@ std::unordered_map keys = { {"LongitudinalPersonality", PERSISTENT}, {"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"NavDestinationWaypoints", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, + {"NavPastDestinations", PERSISTENT}, {"NavSettingLeftSide", PERSISTENT}, {"NavSettingTime24h", PERSISTENT}, {"NavdRender", PERSISTENT}, diff --git a/docs/CARS.md b/docs/CARS.md index 9ded53f201..ae977f16e3 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -268,7 +268,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| ### Footnotes -1Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
+1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
2By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
3Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
4Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
diff --git a/docs/docker/Dockerfile b/docs/docker/Dockerfile index e079fe6223..240f828912 100644 --- a/docs/docker/Dockerfile +++ b/docs/docker/Dockerfile @@ -2,7 +2,7 @@ FROM ghcr.io/commaai/openpilot-base:latest ENV PYTHONUNBUFFERED 1 -ENV OPENPILOT_PATH /home/batman/openpilot/ +ENV OPENPILOT_PATH /tmp/openpilot ENV PYTHONPATH ${OPENPILOT_PATH}:${PYTHONPATH} ENV POETRY_VIRUALENVS_CREATE false @@ -15,8 +15,8 @@ COPY ./openpilot ${OPENPILOT_PATH}/openpilot COPY ./body ${OPENPILOT_PATH}/body COPY ./third_party ${OPENPILOT_PATH}/third_party COPY ./site_scons ${OPENPILOT_PATH}/site_scons -COPY ./laika ${OPENPILOT_PATH}/laika COPY ./laika_repo ${OPENPILOT_PATH}/laika_repo +RUN ln -s ${OPENPILOT_PATH}/laika_repo ${OPENPILOT_PATH}/laika COPY ./rednose ${OPENPILOT_PATH}/rednose COPY ./rednose_repo ${OPENPILOT_PATH}/rednose_repo COPY ./tools ${OPENPILOT_PATH}/tools @@ -29,7 +29,7 @@ COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive COPY ./system ${OPENPILOT_PATH}/system COPY ./*.md ${OPENPILOT_PATH}/ -RUN --mount=type=bind,source=.ci_cache/scons_cache,target=/tmp/scons_cache,rw scons -j$(nproc) +RUN --mount=type=bind,source=.ci_cache/scons_cache,target=/tmp/scons_cache,rw scons -j$(nproc) --cache-readonly RUN apt update && apt install doxygen -y COPY ./docs ${OPENPILOT_PATH}/docs @@ -38,5 +38,5 @@ WORKDIR ${OPENPILOT_PATH}/docs RUN make html FROM nginx:1.21 -COPY --from=0 /home/batman/openpilot/build/docs/html /usr/share/nginx/html +COPY --from=0 /tmp/openpilot/build/docs/html /usr/share/nginx/html COPY ./docs/docker/nginx.conf /etc/nginx/conf.d/default.conf diff --git a/poetry.lock b/poetry.lock index c5d7b0e28f..b021e0011e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2383,14 +2383,7 @@ files = [ ] [package.dependencies] -numpy = [ - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, -] +numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} [[package]] name = "packaging" @@ -2438,10 +2431,7 @@ files = [ ] [package.dependencies] -numpy = [ - {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, - {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, -] +numpy = {version = ">=1.23.2", markers = "python_version >= \"3.11\""} python-dateutil = ">=2.8.2" pytz = ">=2020.1" tzdata = ">=2022.1" @@ -3243,6 +3233,33 @@ files = [ attrs = ">=19.2.0" pytest = ">=7.0" +[[package]] +name = "pytest-timeout" +version = "2.1.0" +description = "pytest plugin to abort hanging tests" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-timeout-2.1.0.tar.gz", hash = "sha256:c07ca07404c612f8abbe22294b23c368e2e5104b521c1790195561f37e1ac3d9"}, + {file = "pytest_timeout-2.1.0-py3-none-any.whl", hash = "sha256:f6f50101443ce70ad325ceb4473c4255e9d74e3c7cd0ef827309dfa4c0d975c6"}, +] + +[package.dependencies] +pytest = ">=5.0.0" + +[[package]] +name = "pytest-timeouts" +version = "1.2.1" +description = "Linux-only Pytest plugin to control durations of various test case execution phases" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pytest-timeouts-1.2.1.tar.gz", hash = "sha256:390351afc7ecb422ea0ec38081e0acd91cad416b383944a9a3358087de50c2fb"}, +] + +[package.dependencies] +pytest = ">=3.1" + [[package]] name = "pytest-xdist" version = "3.3.1" @@ -4322,4 +4339,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "7e2bfde2719e7d7bb4b1627b0657e9ab4a9f4e1637d8b8ae6d5c80c7861e2052" +content-hash = "799e03aa1f3098c94a383f17b56275cd4140179bc457ff837b793fbcf7eb4b1e" diff --git a/pyproject.toml b/pyproject.toml index e350f070c4..b7952d3fa8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,8 @@ [tool.pytest.ini_options] minversion = "6.0" -addopts = "--ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=laika_repo/" +addopts = "--ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=laika_repo/ -Werror --strict-config --strict-markers" python_files = "test_*.py" -timeout = "30" # you get this long by default +#timeout = "30" # you get this long by default [tool.mypy] python_version = "3.11" @@ -124,6 +124,8 @@ pytest = "*" pytest-cov = "*" pytest-subtests = "*" pytest-xdist = "*" +pytest-timeout = "*" +pytest-timeouts = "*" scipy = "*" sphinx = "*" sphinx-rtd-theme = "*" diff --git a/release/files_common b/release/files_common index b375c18dce..54aece8530 100644 --- a/release/files_common +++ b/release/files_common @@ -318,6 +318,8 @@ selfdrive/ui/tests/test_translations.py selfdrive/ui/qt/*.cc selfdrive/ui/qt/*.h +selfdrive/ui/qt/network/*.cc +selfdrive/ui/qt/network/*.h selfdrive/ui/qt/offroad/*.cc selfdrive/ui/qt/offroad/*.h selfdrive/ui/qt/offroad/*.qml diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py index 3319ba31c8..60f9494708 100644 --- a/selfdrive/car/docs_definitions.py +++ b/selfdrive/car/docs_definitions.py @@ -85,6 +85,7 @@ class CarHarness(EnumBase): bosch_a = BaseCarHarness("Honda Bosch A connector") bosch_b = BaseCarHarness("Honda Bosch B connector") toyota_a = BaseCarHarness("Toyota A connector") + toyota_b = BaseCarHarness("Toyota B connector") subaru_a = BaseCarHarness("Subaru A connector") subaru_b = BaseCarHarness("Subaru B connector") subaru_c = BaseCarHarness("Subaru C connector") @@ -110,6 +111,7 @@ class CarHarness(EnumBase): hyundai_o = BaseCarHarness("Hyundai O connector") hyundai_p = BaseCarHarness("Hyundai P connector") hyundai_q = BaseCarHarness("Hyundai Q connector") + hyundai_r = BaseCarHarness("Hyundai R connector") custom = BaseCarHarness("Developer connector") obd_ii = BaseCarHarness("OBD-II connector", parts=[Cable.long_obdc_cable, Cable.long_obdc_cable], has_connector=False) gm = BaseCarHarness("GM connector") @@ -174,8 +176,8 @@ CarFootnote = namedtuple("CarFootnote", ["text", "column", "docs_only", "shop_fo class CommonFootnote(Enum): EXP_LONG_AVAIL = CarFootnote( - "Experimental openpilot longitudinal control is available behind a toggle; " + - "the toggle is only available in non-release branches such as `devel` or `master-ci`. ", + "openpilot Longitudinal Control (Alpha) is available behind a toggle; " + + "the toggle is only available in non-release branches such as `devel` or `master-ci`.", Column.LONGITUDINAL, docs_only=True) EXP_LONG_DSU = CarFootnote( "By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. " + diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 796f85f1e7..017ce66793 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -24,7 +24,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "qt/ "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", "qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", - "qt/request_repeater.cc", "qt/qt_window.cc", "qt/offroad/networking.cc", "qt/offroad/wifiManager.cc"] + "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] qt_env['CPPDEFINES'] = [] if maps: diff --git a/selfdrive/ui/qt/maps/map_settings.cc b/selfdrive/ui/qt/maps/map_settings.cc index a4386ded72..4d655be36c 100644 --- a/selfdrive/ui/qt/maps/map_settings.cc +++ b/selfdrive/ui/qt/maps/map_settings.cc @@ -9,6 +9,17 @@ #include "selfdrive/ui/qt/request_repeater.h" #include "selfdrive/ui/qt/widgets/scrollview.h" +static void swap(QJsonValueRef v1, QJsonValueRef v2) { std::swap(v1, v2); } + +static bool locationEqual(const QJsonValue &v1, const QJsonValue &v2) { + return v1["latitude"] == v2["latitude"] && v1["longitude"] == v2["longitude"]; +} + +static qint64 convertTimestampToEpoch(const QString ×tamp) { + QDateTime dt = QDateTime::fromString(timestamp, Qt::ISODate); + return dt.isValid() ? dt.toSecsSinceEpoch() : 0; +} + MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) { setContentsMargins(0, 0, 0, 0); setAttribute(Qt::WA_NoMousePropagation); @@ -61,11 +72,8 @@ MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) { frame->addSpacing(32); current_widget = new DestinationWidget(this); - QObject::connect(current_widget, &DestinationWidget::actionClicked, [=]() { - if (current_destination.empty()) return; - params.remove("NavDestination"); - updateCurrentRoute(); - }); + QObject::connect(current_widget, &DestinationWidget::actionClicked, + []() { NavManager::instance()->setCurrentDestination({}); }); frame->addWidget(current_widget); frame->addSpacing(32); @@ -84,40 +92,16 @@ MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) { frame->addWidget(destinations_scroller); setStyleSheet("MapSettings { background-color: #333333; }"); - - QObject::connect(NavigationRequest::instance(), &NavigationRequest::locationsUpdated, this, &MapSettings::updateLocations); - QObject::connect(NavigationRequest::instance(), &NavigationRequest::nextDestinationUpdated, this, &MapSettings::updateCurrentRoute); - - current_locations = NavigationRequest::instance()->currentLocations(); + QObject::connect(NavManager::instance(), &NavManager::updated, this, &MapSettings::refresh); } void MapSettings::showEvent(QShowEvent *event) { - updateCurrentRoute(); -} - -void MapSettings::updateCurrentRoute() { - auto dest = QString::fromStdString(params.get("NavDestination")); - if (dest.size()) { - QJsonDocument doc = QJsonDocument::fromJson(dest.trimmed().toUtf8()); - if (doc.isNull()) { - qWarning() << "JSON Parse failed on NavDestination" << dest; - return; - } - current_destination = doc.object(); - current_widget->set(current_destination, true); - } else { - current_destination = {}; - current_widget->unset("", true); - } - if (isVisible()) refresh(); -} - -void MapSettings::updateLocations(const QJsonArray &locations) { - current_locations = locations; refresh(); } void MapSettings::refresh() { + if (!isVisible()) return; + setUpdatesEnabled(false); auto get_w = [this](int i) { @@ -129,11 +113,17 @@ void MapSettings::refresh() { return w; }; + const auto current_dest = NavManager::instance()->currentDestination(); + if (!current_dest.isEmpty()) { + current_widget->set(current_dest, true); + } else { + current_widget->unset("", true); + } home_widget->unset(NAV_FAVORITE_LABEL_HOME); work_widget->unset(NAV_FAVORITE_LABEL_WORK); int n = 0; - for (auto location : current_locations) { + for (auto location : NavManager::instance()->currentLocations()) { DestinationWidget *w = nullptr; auto dest = location.toObject(); if (dest["save_type"].toString() == NAV_TYPE_FAVORITE) { @@ -143,7 +133,7 @@ void MapSettings::refresh() { } w = w ? w : get_w(n++); w->set(dest, false); - w->setVisible(dest != current_destination); + w->setVisible(!locationEqual(dest, current_dest)); } for (; n < widgets.size(); ++n) widgets[n]->setVisible(false); @@ -151,9 +141,7 @@ void MapSettings::refresh() { } void MapSettings::navigateTo(const QJsonObject &place) { - QJsonDocument doc(place); - params.put("NavDestination", doc.toJson().toStdString()); - updateCurrentRoute(); + NavManager::instance()->setCurrentDestination(place); emit closeSettings(); } @@ -279,24 +267,26 @@ void DestinationWidget::unset(const QString &label, bool current) { setVisible(true); } -// singleton NavigationRequest +// singleton NavManager -NavigationRequest *NavigationRequest::instance() { - static NavigationRequest *request = new NavigationRequest(qApp); +NavManager *NavManager::instance() { + static NavManager *request = new NavManager(qApp); return request; } -NavigationRequest::NavigationRequest(QObject *parent) : QObject(parent) { +NavManager::NavManager(QObject *parent) : QObject(parent) { + locations = QJsonDocument::fromJson(params.get("NavPastDestinations").c_str()).array(); + current_dest = QJsonDocument::fromJson(params.get("NavDestination").c_str()).object(); if (auto dongle_id = getDongleId()) { { // Fetch favorite and recent locations QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/locations"; RequestRepeater *repeater = new RequestRepeater(this, url, "ApiCache_NavDestinations", 30, true); - QObject::connect(repeater, &RequestRepeater::requestDone, this, &NavigationRequest::parseLocationsResponse); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &NavManager::parseLocationsResponse); } { auto param_watcher = new ParamWatcher(this); - QObject::connect(param_watcher, &ParamWatcher::paramChanged, this, &NavigationRequest::nextDestinationUpdated); + QObject::connect(param_watcher, &ParamWatcher::paramChanged, this, &NavManager::updated); // Destination set while offline QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/next"; @@ -316,14 +306,14 @@ NavigationRequest::NavigationRequest(QObject *parent) : QObject(parent) { // athena can set destination at any time param_watcher->addParam("NavDestination"); + current_dest = QJsonDocument::fromJson(params.get("NavDestination").c_str()).object(); + emit updated(); }); } } } -static void swap(QJsonValueRef v1, QJsonValueRef v2) { std::swap(v1, v2); } - -void NavigationRequest::parseLocationsResponse(const QString &response, bool success) { +void NavManager::parseLocationsResponse(const QString &response, bool success) { if (!success || response == prev_response) return; prev_response = response; @@ -333,13 +323,63 @@ void NavigationRequest::parseLocationsResponse(const QString &response, bool suc return; } - // Sort: alphabetical FAVORITES, and then most recent (as returned by API). + // set last activity time. + auto remote_locations = doc.array(); + for (QJsonValueRef loc : remote_locations) { + auto obj = loc.toObject(); + auto serverTime = convertTimestampToEpoch(obj["modified"].toString()); + obj.insert("time", qMax(serverTime, getLastActivity(obj))); + loc = obj; + } + + locations = remote_locations; + sortLocations(); + emit updated(); +} + +void NavManager::sortLocations() { + // Sort: alphabetical FAVORITES, and then most recent. // We don't need to care about the ordering of HOME and WORK. DestinationWidget always displays them at the top. - locations = doc.array(); std::stable_sort(locations.begin(), locations.end(), [](const QJsonValue &a, const QJsonValue &b) { - bool has_favorite = a["save_type"] == NAV_TYPE_FAVORITE || b["save_type"] == NAV_TYPE_FAVORITE; - return has_favorite && (std::tuple(a["save_type"].toString(), a["place_name"].toString()) < - std::tuple(b["save_type"].toString(), b["place_name"].toString())); + if (a["save_type"] == NAV_TYPE_FAVORITE || b["save_type"] == NAV_TYPE_FAVORITE) { + return (std::tuple(a["save_type"].toString(), a["place_name"].toString()) < + std::tuple(b["save_type"].toString(), b["place_name"].toString())); + } else { + return a["time"].toVariant().toLongLong() > b["time"].toVariant().toLongLong(); + } + }); + + write_param_future = std::async(std::launch::async, [destinations = QJsonArray(locations)]() { + Params().put("NavPastDestinations", QJsonDocument(destinations).toJson().toStdString()); }); - emit locationsUpdated(locations); +} + +qint64 NavManager::getLastActivity(const QJsonObject &loc) const { + qint64 last_activity = 0; + auto it = std::find_if(locations.begin(), locations.end(), + [&loc](const QJsonValue &l) { return locationEqual(loc, l); }); + if (it != locations.end()) { + auto tm = it->toObject().value("time"); + if (!tm.isUndefined() && !tm.isNull()) { + last_activity = tm.toVariant().toLongLong(); + } + } + return last_activity; +} + +void NavManager::setCurrentDestination(const QJsonObject &loc) { + current_dest = loc; + if (!current_dest.isEmpty()) { + current_dest["time"] = QDateTime::currentSecsSinceEpoch(); + auto it = std::find_if(locations.begin(), locations.end(), + [&loc](const QJsonValue &l) { return locationEqual(loc, l); }); + if (it != locations.end()) { + *it = current_dest; + sortLocations(); + } + params.put("NavDestination", QJsonDocument(current_dest).toJson().toStdString()); + } else { + params.remove("NavDestination"); + } + emit updated(); } diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h index bee0061932..0e151df4ad 100644 --- a/selfdrive/ui/qt/maps/map_settings.h +++ b/selfdrive/ui/qt/maps/map_settings.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -22,42 +23,41 @@ const QString NAV_FAVORITE_LABEL_WORK = "work"; class DestinationWidget; -class NavigationRequest : public QObject { +class NavManager : public QObject { Q_OBJECT public: - static NavigationRequest *instance(); + static NavManager *instance(); QJsonArray currentLocations() const { return locations; } + QJsonObject currentDestination() const { return current_dest; } + void setCurrentDestination(const QJsonObject &loc); + qint64 getLastActivity(const QJsonObject &loc) const; signals: - void locationsUpdated(const QJsonArray &locations); - void nextDestinationUpdated(); + void updated(); private: - NavigationRequest(QObject *parent); + NavManager(QObject *parent); void parseLocationsResponse(const QString &response, bool success); + void sortLocations(); Params params; QString prev_response; QJsonArray locations; + QJsonObject current_dest; + std::future write_param_future; }; class MapSettings : public QFrame { Q_OBJECT public: explicit MapSettings(bool closeable = false, QWidget *parent = nullptr); - void navigateTo(const QJsonObject &place); - void updateLocations(const QJsonArray &locations); - void updateCurrentRoute(); private: void showEvent(QShowEvent *event) override; void refresh(); - Params params; - QJsonArray current_locations; - QJsonObject current_destination; QVBoxLayout *destinations_layout; DestinationWidget *current_widget; DestinationWidget *home_widget; diff --git a/selfdrive/ui/qt/offroad/networking.cc b/selfdrive/ui/qt/network/networking.cc similarity index 99% rename from selfdrive/ui/qt/offroad/networking.cc rename to selfdrive/ui/qt/network/networking.cc index 420bc5a2cf..98ecc90fe3 100644 --- a/selfdrive/ui/qt/offroad/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/networking.h" +#include "selfdrive/ui/qt/network/networking.h" #include diff --git a/selfdrive/ui/qt/offroad/networking.h b/selfdrive/ui/qt/network/networking.h similarity index 97% rename from selfdrive/ui/qt/offroad/networking.h rename to selfdrive/ui/qt/network/networking.h index 81ffb3cf2d..4ff7380f42 100644 --- a/selfdrive/ui/qt/offroad/networking.h +++ b/selfdrive/ui/qt/network/networking.h @@ -2,7 +2,7 @@ #include -#include "selfdrive/ui/qt/offroad/wifiManager.h" +#include "selfdrive/ui/qt/network/wifi_manager.h" #include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/ssh_keys.h" #include "selfdrive/ui/qt/widgets/toggle.h" diff --git a/selfdrive/ui/qt/offroad/networkmanager.h b/selfdrive/ui/qt/network/networkmanager.h similarity index 100% rename from selfdrive/ui/qt/offroad/networkmanager.h rename to selfdrive/ui/qt/network/networkmanager.h diff --git a/selfdrive/ui/qt/offroad/wifiManager.cc b/selfdrive/ui/qt/network/wifi_manager.cc similarity index 99% rename from selfdrive/ui/qt/offroad/wifiManager.cc rename to selfdrive/ui/qt/network/wifi_manager.cc index 858de15bfe..03c6896f7a 100644 --- a/selfdrive/ui/qt/offroad/wifiManager.cc +++ b/selfdrive/ui/qt/network/wifi_manager.cc @@ -1,4 +1,4 @@ -#include "selfdrive/ui/qt/offroad/wifiManager.h" +#include "selfdrive/ui/qt/network/wifi_manager.h" #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/widgets/prime.h" diff --git a/selfdrive/ui/qt/offroad/wifiManager.h b/selfdrive/ui/qt/network/wifi_manager.h similarity index 98% rename from selfdrive/ui/qt/offroad/wifiManager.h rename to selfdrive/ui/qt/network/wifi_manager.h index c444b2abc1..7debffa452 100644 --- a/selfdrive/ui/qt/offroad/wifiManager.h +++ b/selfdrive/ui/qt/network/wifi_manager.h @@ -4,7 +4,7 @@ #include #include -#include "selfdrive/ui/qt/offroad/networkmanager.h" +#include "selfdrive/ui/qt/network/networkmanager.h" enum class SecurityType { OPEN, diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index ec4b567ca6..693a0253b4 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -35,7 +35,7 @@ void DriverViewWindow::mouseReleaseEvent(QMouseEvent* e) { closeView(); } -DriverViewScene::DriverViewScene(QWidget* parent) : sm({"driverStateV2"}), QWidget(parent) { +DriverViewScene::DriverViewScene(QWidget* parent) : QWidget(parent) { face_img = loadPixmap("../assets/img_driver_face_static.png", {FACE_IMG_SIZE, FACE_IMG_SIZE}); } @@ -51,7 +51,6 @@ void DriverViewScene::hideEvent(QHideEvent* event) { void DriverViewScene::frameUpdated() { frame_updated = true; - sm.update(0); update(); } @@ -67,6 +66,7 @@ void DriverViewScene::paintEvent(QPaintEvent* event) { return; } + const auto &sm = *(uiState()->sm); cereal::DriverStateV2::Reader driver_state = sm["driverStateV2"].getDriverStateV2(); cereal::DriverStateV2::DriverData::Reader driver_data; diff --git a/selfdrive/ui/qt/offroad/driverview.h b/selfdrive/ui/qt/offroad/driverview.h index a6ea2cbee7..8bfc7a4b7b 100644 --- a/selfdrive/ui/qt/offroad/driverview.h +++ b/selfdrive/ui/qt/offroad/driverview.h @@ -20,7 +20,6 @@ protected: private: Params params; - SubMaster sm; QPixmap face_img; bool is_rhd = false; bool frame_updated = false; diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 8b0f9c1f36..0af027ca90 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -8,7 +8,7 @@ #include -#include "selfdrive/ui/qt/offroad/networking.h" +#include "selfdrive/ui/qt/network/networking.h" #include "common/params.h" #include "common/watchdog.h" diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index 269874f115..3b3666b19a 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -15,7 +15,7 @@ #include "system/hardware/hw.h" #include "selfdrive/ui/qt/api.h" #include "selfdrive/ui/qt/qt_window.h" -#include "selfdrive/ui/qt/offroad/networking.h" +#include "selfdrive/ui/qt/network/networking.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/input.h" diff --git a/selfdrive/ui/qt/setup/updater.cc b/selfdrive/ui/qt/setup/updater.cc index ae5f26c77e..ed47590aa3 100644 --- a/selfdrive/ui/qt/setup/updater.cc +++ b/selfdrive/ui/qt/setup/updater.cc @@ -6,7 +6,7 @@ #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/setup/updater.h" -#include "selfdrive/ui/qt/offroad/networking.h" +#include "selfdrive/ui/qt/network/networking.h" Updater::Updater(const QString &updater_path, const QString &manifest_path, QWidget *parent) : updater(updater_path), manifest(manifest_path), QStackedWidget(parent) { diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index 49fbdff222..52d0c5cd6e 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -9,7 +9,7 @@ #include "selfdrive/ui/qt/widgets/scrollview.h" -QDialogBase::QDialogBase(QWidget *parent) : QDialog(parent) { +DialogBase::DialogBase(QWidget *parent) : QDialog(parent) { Q_ASSERT(parent != nullptr); parent->installEventFilter(this); @@ -19,7 +19,7 @@ QDialogBase::QDialogBase(QWidget *parent) : QDialog(parent) { color: white; font-family: Inter; } - QDialogBase { + DialogBase { background-color: black; } QPushButton { @@ -36,19 +36,19 @@ QDialogBase::QDialogBase(QWidget *parent) : QDialog(parent) { )"); } -bool QDialogBase::eventFilter(QObject *o, QEvent *e) { +bool DialogBase::eventFilter(QObject *o, QEvent *e) { if (o == parent() && e->type() == QEvent::Hide) { reject(); } return QDialog::eventFilter(o, e); } -int QDialogBase::exec() { +int DialogBase::exec() { setMainWindow(this); return QDialog::exec(); } -InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &subtitle, bool secret) : QDialogBase(parent) { +InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &subtitle, bool secret) : DialogBase(parent) { main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(50, 55, 50, 50); main_layout->setSpacing(0); @@ -188,7 +188,7 @@ void InputDialog::setMinLength(int length) { // ConfirmationDialog ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, const QString &cancel_text, - const bool rich, QWidget *parent) : QDialogBase(parent) { + const bool rich, QWidget *parent) : DialogBase(parent) { QFrame *container = new QFrame(this); container->setStyleSheet(R"( QFrame { background-color: #1B1B1B; color: #C9C9C9; } @@ -245,7 +245,7 @@ bool ConfirmationDialog::rich(const QString &prompt_text, QWidget *parent) { // MultiOptionDialog -MultiOptionDialog::MultiOptionDialog(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent) : QDialogBase(parent) { +MultiOptionDialog::MultiOptionDialog(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent) : DialogBase(parent) { QFrame *container = new QFrame(this); container->setStyleSheet(R"( QFrame { background-color: #1B1B1B; } diff --git a/selfdrive/ui/qt/widgets/input.h b/selfdrive/ui/qt/widgets/input.h index e6c0fba86d..917ea21b57 100644 --- a/selfdrive/ui/qt/widgets/input.h +++ b/selfdrive/ui/qt/widgets/input.h @@ -10,18 +10,18 @@ #include "selfdrive/ui/qt/widgets/keyboard.h" -class QDialogBase : public QDialog { +class DialogBase : public QDialog { Q_OBJECT protected: - QDialogBase(QWidget *parent); + DialogBase(QWidget *parent); bool eventFilter(QObject *o, QEvent *e) override; public slots: int exec() override; }; -class InputDialog : public QDialogBase { +class InputDialog : public DialogBase { Q_OBJECT public: @@ -50,7 +50,7 @@ signals: void emitText(const QString &text); }; -class ConfirmationDialog : public QDialogBase { +class ConfirmationDialog : public DialogBase { Q_OBJECT public: @@ -61,7 +61,7 @@ public: static bool rich(const QString &prompt_text, QWidget *parent); }; -class MultiOptionDialog : public QDialogBase { +class MultiOptionDialog : public DialogBase { Q_OBJECT public: diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc index 57d9d87a64..324d6cf6ae 100644 --- a/selfdrive/ui/qt/widgets/prime.cc +++ b/selfdrive/ui/qt/widgets/prime.cc @@ -68,7 +68,7 @@ void PairingQRWidget::paintEvent(QPaintEvent *e) { } -PairingPopup::PairingPopup(QWidget *parent) : QDialogBase(parent) { +PairingPopup::PairingPopup(QWidget *parent) : DialogBase(parent) { QHBoxLayout *hlayout = new QHBoxLayout(this); hlayout->setContentsMargins(0, 0, 0, 0); hlayout->setSpacing(0); diff --git a/selfdrive/ui/qt/widgets/prime.h b/selfdrive/ui/qt/widgets/prime.h index 227a0f31c1..63341c4cea 100644 --- a/selfdrive/ui/qt/widgets/prime.h +++ b/selfdrive/ui/qt/widgets/prime.h @@ -28,7 +28,7 @@ private slots: // pairing popup widget -class PairingPopup : public QDialogBase { +class PairingPopup : public DialogBase { Q_OBJECT public: diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 5908db1b2e..79c4ae5596 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -517,7 +517,7 @@ PrimeAdWidget Upgrade Now - Mettre à niveau maintenant + Mettre à niveau Become a comma prime member at connect.comma.ai @@ -860,7 +860,7 @@ Cela peut prendre jusqu'à une minute. SoftwarePanel Updates are only downloaded while the car is off. - Les mises à jour sont téléchargées uniquement lorsque la voiture est éteinte. + Les MàJ sont téléchargées uniquement si la voiture est éteinte. Current Version @@ -1174,7 +1174,7 @@ Cela peut prendre jusqu'à une minute. WiFiPromptWidget Setup Wi-Fi - Configurer le Wi-Fi + Configurer Wi-Fi Connect to Wi-Fi to upload driving data and help improve openpilot diff --git a/system/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py index bf21d8d7a9..6e2f86d6ca 100755 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -14,7 +14,7 @@ from openpilot.system.loggerd.uploader import uploader_fn, UPLOAD_ATTR_NAME, UPL from openpilot.system.loggerd.tests.loggerd_tests_common import UploaderTestCase -class TestLogHandler(logging.Handler): +class FakeLogHandler(logging.Handler): def __init__(self): logging.Handler.__init__(self) self.reset() @@ -33,7 +33,7 @@ class TestLogHandler(logging.Handler): except Exception: pass -log_handler = TestLogHandler() +log_handler = FakeLogHandler() cloudlog.addHandler(log_handler) diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 4e9e0173d7..6a3d11cda1 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -281,7 +281,8 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, co case Column::NAME: { const auto msg = dbc()->msg(id); match = re.match(msg ? msg->name : UNTITLED).hasMatch(); - match |= msg && std::any_of(msg->sigs.cbegin(), msg->sigs.cend(), [&re](const auto &s) { return re.match(s->name).hasMatch(); }); + match = match || (msg && std::any_of(msg->sigs.cbegin(), msg->sigs.cend(), + [&re](const auto &s) { return re.match(s->name).hasMatch(); })); break; } case Column::SOURCE: @@ -289,7 +290,7 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, co break; case Column::ADDRESS: { match = re.match(QString::number(id.address, 16)).hasMatch(); - match |= parseRange(txt, id.address, 16); + match = match || parseRange(txt, id.address, 16); break; } case Column::FREQ: @@ -301,8 +302,8 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, co break; case Column::DATA: { match = QString(data.dat.toHex()).contains(txt, Qt::CaseInsensitive); - match |= re.match(QString(data.dat.toHex())).hasMatch(); - match |= re.match(QString(data.dat.toHex(' '))).hasMatch(); + match = match || re.match(QString(data.dat.toHex())).hasMatch(); + match = match || re.match(QString(data.dat.toHex(' '))).hasMatch(); break; } } diff --git a/tools/plotjuggler/test_plotjuggler.py b/tools/plotjuggler/test_plotjuggler.py index e29e33f921..b002331cd7 100755 --- a/tools/plotjuggler/test_plotjuggler.py +++ b/tools/plotjuggler/test_plotjuggler.py @@ -18,19 +18,18 @@ class TestPlotJuggler(unittest.TestCase): install() pj = os.path.join(PJ_DIR, "juggle.py") - p = subprocess.Popen(f'QT_QPA_PLATFORM=offscreen {pj} --demo None 1 --qlog', - stderr=subprocess.PIPE, shell=True, start_new_session=True) - - # Wait for "Done reading Rlog data" signal from the plugin - output = "\n" - with Timeout(180, error_msg=output): - while output.splitlines()[-1] != "Done reading Rlog data": - output += p.stderr.readline().decode("utf-8") - - # ensure plotjuggler didn't crash after exiting the plugin - time.sleep(15) - self.assertEqual(p.poll(), None) - os.killpg(os.getpgid(p.pid), signal.SIGTERM) + with subprocess.Popen(f'QT_QPA_PLATFORM=offscreen {pj} --demo None 1 --qlog', + stderr=subprocess.PIPE, shell=True, start_new_session=True) as p: + # Wait for "Done reading Rlog data" signal from the plugin + output = "\n" + with Timeout(180, error_msg=output): + while output.splitlines()[-1] != "Done reading Rlog data": + output += p.stderr.readline().decode("utf-8") + + # ensure plotjuggler didn't crash after exiting the plugin + time.sleep(15) + self.assertEqual(p.poll(), None) + os.killpg(os.getpgid(p.pid), signal.SIGTERM) # TODO: also test that layouts successfully load def test_layouts(self): diff --git a/tools/sim/Dockerfile.sim b/tools/sim/Dockerfile.sim index 8de5266841..800e9dac74 100644 --- a/tools/sim/Dockerfile.sim +++ b/tools/sim/Dockerfile.sim @@ -9,26 +9,29 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN cd $HOME && \ curl -O https://raw.githubusercontent.com/commaai/eon-neos-builder/master/devices/eon/home/.tmux.conf -ENV PYTHONPATH $HOME/openpilot:${PYTHONPATH} -RUN mkdir -p $HOME/openpilot - -COPY SConstruct $HOME/openpilot/ - -COPY ./openpilot $HOME/openpilot/openpilot -COPY ./body $HOME/openpilot/body -COPY ./third_party $HOME/openpilot/third_party -COPY ./site_scons $HOME/openpilot/site_scons -COPY ./rednose $HOME/openpilot/rednose -COPY ./laika $HOME/openpilot/laika -COPY ./common $HOME/openpilot/common -COPY ./opendbc $HOME/openpilot/opendbc -COPY ./cereal $HOME/openpilot/cereal -COPY ./panda $HOME/openpilot/panda -COPY ./selfdrive $HOME/openpilot/selfdrive -COPY ./system $HOME/openpilot/system -COPY ./tools $HOME/openpilot/tools - -WORKDIR $HOME/openpilot -RUN --mount=type=bind,source=.ci_cache/scons_cache,target=/tmp/scons_cache,rw scons -j$(nproc) +ENV OPENPILOT_PATH /tmp/openpilot +ENV PYTHONPATH ${OPENPILOT_PATH}:${PYTHONPATH} + +RUN mkdir -p ${OPENPILOT_PATH} +WORKDIR ${OPENPILOT_PATH} + +COPY SConstruct ${OPENPILOT_PATH} + +COPY ./openpilot ${OPENPILOT_PATH}/openpilot +COPY ./body ${OPENPILOT_PATH}/body +COPY ./third_party ${OPENPILOT_PATH}/third_party +COPY ./site_scons ${OPENPILOT_PATH}/site_scons +COPY ./rednose ${OPENPILOT_PATH}/rednose +COPY ./laika_repo ${OPENPILOT_PATH}/laika_repo +RUN ln -s ${OPENPILOT_PATH}/laika_repo ${OPENPILOT_PATH}/laika +COPY ./common ${OPENPILOT_PATH}/common +COPY ./opendbc ${OPENPILOT_PATH}/opendbc +COPY ./cereal ${OPENPILOT_PATH}/cereal +COPY ./panda ${OPENPILOT_PATH}/panda +COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive +COPY ./system ${OPENPILOT_PATH}/system +COPY ./tools ${OPENPILOT_PATH}/tools + +RUN --mount=type=bind,source=.ci_cache/scons_cache,target=/tmp/scons_cache,rw scons -j$(nproc) --cache-readonly RUN python -c "from openpilot.selfdrive.test.helpers import set_params_enabled; set_params_enabled()"