diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index af67bdbe5d..53a06f6f31 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,11 +1,12 @@ FROM ghcr.io/commaai/openpilot-base:latest -RUN apt update && apt install -y vim net-tools usbutils htop ripgrep tmux wget mesa-utils xvfb libxtst6 libxv1 libglu1-mesa libegl1-mesa gdb bash-completion -RUN pip install ipython jupyter jupyterlab +RUN apt update && apt install -y vim net-tools usbutils htop ripgrep tmux wget mesa-utils xvfb libxtst6 libxv1 libglu1-mesa gdb bash-completion +RUN python3 -m ensurepip --upgrade +RUN pip3 install ipython jupyter jupyterlab RUN cd /tmp && \ ARCH=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) && \ - curl -L -o virtualgl.deb "https://downloads.sourceforge.net/project/virtualgl/3.1/virtualgl_3.1_$ARCH.deb" && \ + curl -L -o virtualgl.deb "https://github.com/VirtualGL/virtualgl/releases/download/3.1.1/virtualgl_3.1.1_$ARCH.deb" && \ dpkg -i virtualgl.deb RUN usermod -aG video batman diff --git a/.github/workflows/ci_weekly_report.yaml b/.github/workflows/ci_weekly_report.yaml new file mode 100644 index 0000000000..c5c98b46b5 --- /dev/null +++ b/.github/workflows/ci_weekly_report.yaml @@ -0,0 +1,123 @@ +name: weekly CI test report +on: + schedule: + - cron: '37 9 * * 1' # 9:37AM UTC -> 2:37AM PST every monday + workflow_dispatch: + inputs: + ci_runs: + description: 'The amount of runs to trigger in CI test report' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + setup: + if: github.repository == 'commaai/openpilot' + runs-on: ubuntu-latest + outputs: + ci_runs: ${{ steps.ci_runs_setup.outputs.value }} + steps: + - id: ci_runs_setup + run: | + CI_RUNS=${{ inputs.ci_runs || '50' }} + mylist="value=[" + + for i in $(seq 1 $CI_RUNS); + do + if [ $i != $CI_RUNS ]; then + mylist+="\"$i\", " + else + mylist+="\"$i\"]" + fi + done + + echo "$mylist" >> $GITHUB_OUTPUT + echo "Number of CI runs for report: $CI_RUNS" + ci_matrix_run: + needs: [ setup ] + strategy: + fail-fast: false + matrix: + value: ${{fromJSON(needs.setup.outputs.ci_runs)}} + uses: commaai/openpilot/.github/workflows/ci_weekly_run.yaml@master + with: + run_number: ${{ matrix.value }} + + report: + needs: [ci_matrix_run] + runs-on: ubuntu-latest + if: always() + steps: + - name: Get job results + uses: actions/github-script@v7 + id: get-job-results + with: + script: | + const jobs = await github + .paginate("GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt}/jobs", { + owner: "commaai", + repo: "${{ github.event.repository.name }}", + run_id: "${{ github.run_id }}", + attempt: "${{ github.run_attempt }}", + }) + var report = {} + jobs.slice(1, jobs.length-1).forEach(job => { + const jobName = job.name.split('/')[2].trim(); + report[jobName] = report[jobName] || { successes: [], failures: [], cancelled: [] }; + switch (job.conclusion) { + case "success": + report[jobName].successes.push(job.html_url); break; + case "failure": + report[jobName].failures.push(job.html_url); break; + case "cancelled": + report[jobName].cancelled.push(job.html_url); break; + } + }); + return JSON.stringify(report); + + - name: Add job results to summary + env: + JOB_RESULTS: ${{ fromJSON(steps.get-job-results.outputs.result) }} + run: | + echo $JOB_RESULTS > job_results.json + generate_html_table() { + echo "
| Job | " + echo "Succeeded ✅ | " + echo "Failed ❌ | " + echo "Cancelled (timed out) ⏰ | " + echo "
|---|---|---|---|
| \($job) | ", + "",
+ " ",
+ " ",
+ " (\(.[$job].successes | length))", + " \(.[$job].successes[])", + " | ",
+ " ",
+ " ",
+ " ",
+ " (\(.[$job].failures | length))", + " \(.[$job].failures[])", + " | ",
+ " ",
+ " ",
+ " ",
+ " (\(.[$job].cancelled | length))", + " \(.[$job].cancelled[])", + " | ",
+ "
![]() |
+ ![]() |
+
![]() |
+ ![]() |
+
![]() |
+ ![]() |
+
{release_notes}
\n" + assert self.params.get("UpdaterNewReleaseNotes", encoding="utf-8") == f"{release_notes}\n" super()._test_finalized_update(branch, version, agnos_version, release_notes) def send_check_for_updates_signal(self, updated: ManagerProcess): diff --git a/system/updated/updated.py b/system/updated/updated.py index 8453e37a32..e753fd76fb 100755 --- a/system/updated/updated.py +++ b/system/updated/updated.py @@ -60,7 +60,7 @@ class WaitTimeHelper: self.ready_event.wait(timeout=t) def write_time_to_param(params, param) -> None: - t = datetime.datetime.utcnow() + t = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) params.put(param, t.isoformat().encode('utf8')) def read_time_from_param(params, param) -> datetime.datetime | None: @@ -279,7 +279,7 @@ class Updater: if len(self.branches): self.params.put("UpdaterAvailableBranches", ','.join(self.branches.keys())) - last_update = datetime.datetime.utcnow() + last_update = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) if update_success: write_time_to_param(self.params, "LastUpdateTime") else: @@ -323,7 +323,7 @@ class Updater: for alert in ("Offroad_UpdateFailed", "Offroad_ConnectivityNeeded", "Offroad_ConnectivityNeededPrompt"): set_offroad_alert(alert, False) - now = datetime.datetime.utcnow() + now = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) dt = now - last_update build_metadata = get_build_metadata() if failed_count > 15 and exception is not None and self.has_internet: @@ -429,7 +429,7 @@ def main() -> None: cloudlog.event("update installed") if not params.get("InstallDate"): - t = datetime.datetime.utcnow().isoformat() + t = datetime.datetime.now(datetime.UTC).replace(tzinfo=None).isoformat() params.put("InstallDate", t.encode('utf8')) updater = Updater() @@ -469,7 +469,7 @@ def main() -> None: # download update last_fetch = read_time_from_param(params, "UpdaterLastFetchTime") - timed_out = last_fetch is None or (datetime.datetime.utcnow() - last_fetch > datetime.timedelta(days=3)) + timed_out = last_fetch is None or (datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - last_fetch > datetime.timedelta(days=3)) user_requested_fetch = wait_helper.user_request == UserRequest.FETCH if params.get_bool("NetworkMetered") and not timed_out and not user_requested_fetch: cloudlog.info("skipping fetch, connection metered") diff --git a/system/webrtc/tests/test_stream_session.py b/system/webrtc/tests/test_stream_session.py index e89e42e440..46d55ecd90 100644 --- a/system/webrtc/tests/test_stream_session.py +++ b/system/webrtc/tests/test_stream_session.py @@ -3,6 +3,7 @@ import json # for aiortc and its dependencies import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) +warnings.filterwarnings("ignore", category=RuntimeWarning) # TODO: remove this when google-crc32c publish a python3.12 wheel from aiortc import RTCDataChannel from aiortc.mediastreams import VIDEO_CLOCK_RATE, VIDEO_TIME_BASE diff --git a/system/webrtc/tests/test_webrtcd.py b/system/webrtc/tests/test_webrtcd.py index d4b659a3aa..4fa6d8953f 100644 --- a/system/webrtc/tests/test_webrtcd.py +++ b/system/webrtc/tests/test_webrtcd.py @@ -4,6 +4,7 @@ import json # for aiortc and its dependencies import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) +warnings.filterwarnings("ignore", category=RuntimeWarning) # TODO: remove this when google-crc32c publish a python3.12 wheel from openpilot.system.webrtc.webrtcd import get_stream diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py index afd346857f..79c5b4888f 100755 --- a/system/webrtc/webrtcd.py +++ b/system/webrtc/webrtcd.py @@ -11,6 +11,7 @@ from typing import Any, TYPE_CHECKING # aiortc and its dependencies have lots of internal warnings :( import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) +warnings.filterwarnings("ignore", category=RuntimeWarning) # TODO: remove this when google-crc32c publish a python3.12 wheel import capnp from aiohttp import web diff --git a/third_party/maplibre-native-qt/x86_64/lib/libQMapLibre.so.3.0.0 b/third_party/maplibre-native-qt/x86_64/lib/libQMapLibre.so.3.0.0 index f2fcf107bc..e49d321c7c 100755 --- a/third_party/maplibre-native-qt/x86_64/lib/libQMapLibre.so.3.0.0 +++ b/third_party/maplibre-native-qt/x86_64/lib/libQMapLibre.so.3.0.0 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b718e4ea770105893dc41f64be36f806586a9471e2bb9d18d5ad97e906434548 -size 11000296 +oid sha256:d62e192aa7806302ed79866d9e3c51efc4bc315a6fdfe0e58e448dac5b279098 +size 11549728 diff --git a/tools/README.md b/tools/README.md index 361a27deda..521a12ca92 100644 --- a/tools/README.md +++ b/tools/README.md @@ -2,11 +2,11 @@ ## System Requirements -openpilot is developed and tested on **Ubuntu 20.04**, which is the primary development target aside from the [supported embedded hardware](https://github.com/commaai/openpilot#running-on-a-dedicated-device-in-a-car). +openpilot is developed and tested on **Ubuntu 24.04**, which is the primary development target aside from the [supported embedded hardware](https://github.com/commaai/openpilot#running-on-a-dedicated-device-in-a-car). Running natively on any other system is not recommended and will require modifications. On Windows you can use WSL, and on macOS or incompatible Linux systems, it is recommended to use the dev containers. -## Native setup on Ubuntu 20.04 +## Native setup on Ubuntu 24.04 **1. Clone openpilot** @@ -26,16 +26,23 @@ git clone --recurse-submodules https://github.com/commaai/openpilot.git ``` bash cd openpilot -git lfs pull tools/ubuntu_setup.sh ``` -Activate a shell with the Python dependencies installed: +**3. Git LFS** + ``` bash -poetry shell +git lfs pull ``` -**3. Build openpilot** +**4. Activate a python shell** + +Activate a shell with the Python dependencies installed: +``` bash +source .venv/bin/activate +``` + +**5. Build openpilot** ``` bash scons -u -j$(nproc) @@ -55,7 +62,7 @@ GUI apps like `ui` or `cabana` can also run inside the container by leveraging X [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/about) should provide a similar experience to native Ubuntu. [WSL 2](https://docs.microsoft.com/en-us/windows/wsl/compare-versions) specifically has been reported by several users to be a seamless experience. -Follow [these instructions](https://docs.microsoft.com/en-us/windows/wsl/install) to setup the WSL and install the `Ubuntu-20.04` distribution. Once your Ubuntu WSL environment is setup, follow the Linux setup instructions to finish setting up your environment. See [these instructions](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) for running GUI apps. +Follow [these instructions](https://docs.microsoft.com/en-us/windows/wsl/install) to setup the WSL and install the `Ubuntu-24.04` distribution. Once your Ubuntu WSL environment is setup, follow the Linux setup instructions to finish setting up your environment. See [these instructions](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) for running GUI apps. **NOTE**: If you are running WSL and any GUIs are failing (segfaulting or other strange issues) even after following the steps above, you may need to enable software rendering with `LIBGL_ALWAYS_SOFTWARE=1`, e.g. `LIBGL_ALWAYS_SOFTWARE=1 selfdrive/ui/ui`. diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index fcf171a858..28d4e068ee 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -277,7 +277,7 @@ void ChartView::updateSeriesPoints() { } ((QScatterSeries *)s.series)->setMarkerSize(size); } else { - s.series->setPointsVisible(pixels_per_point > 20); + s.series->setPointsVisible(num_points == 1 || pixels_per_point > 20); } } } @@ -421,13 +421,6 @@ qreal ChartView::niceNumber(qreal x, bool ceiling) { return q * z; } -void ChartView::leaveEvent(QEvent *event) { - if (tip_label->isVisible()) { - charts_widget->showValueTip(-1); - } - QChartView::leaveEvent(event); -} - QPixmap getBlankShadowPixmap(const QPixmap &px, int radius) { QGraphicsDropShadowEffect *e = new QGraphicsDropShadowEffect; e->setColor(QColor(40, 40, 40, 245)); @@ -513,7 +506,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { // no rubber dragged, seek to mouse position can->seekTo(min); } else if (rubber->width() > 10 && (max - min) > MIN_ZOOM_SECONDS) { - charts_widget->zoom_undo_stack->push(new ZoomCommand(charts_widget, {min, max})); + charts_widget->zoom_undo_stack->push(new ZoomCommand({min, max})); } else { viewport()->update(); } @@ -546,7 +539,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) { bool is_zooming = rubber && rubber->isVisible(); clearTrackPoints(); - if (!is_zooming && plot_area.contains(ev->pos())) { + if (!is_zooming && plot_area.contains(ev->pos()) && isActiveWindow()) { const double sec = chart()->mapToValue(ev->pos()).x(); charts_widget->showValueTip(sec); } else if (tip_label->isVisible()) { diff --git a/tools/cabana/chart/chart.h b/tools/cabana/chart/chart.h index d690a14d1c..1bfec6355a 100644 --- a/tools/cabana/chart/chart.h +++ b/tools/cabana/chart/chart.h @@ -76,7 +76,6 @@ private: void dragLeaveEvent(QDragLeaveEvent *event) override { drawDropIndicator(false); } void dragMoveEvent(QDragMoveEvent *event) override; void dropEvent(QDropEvent *event) override; - void leaveEvent(QEvent *event) override; void resizeEvent(QResizeEvent *event) override; QSize sizeHint() const override; void updateAxisY(); diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index a7bdd74646..9822eaa307 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -103,6 +103,8 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); QObject::connect(can, &AbstractStream::eventsMerged, this, &ChartsWidget::eventsMerged); QObject::connect(can, &AbstractStream::msgsReceived, this, &ChartsWidget::updateState); + QObject::connect(can, &AbstractStream::seeking, this, &ChartsWidget::updateState); + QObject::connect(can, &AbstractStream::timeRangeChanged, this, &ChartsWidget::timeRangeChanged); QObject::connect(range_slider, &QSlider::valueChanged, this, &ChartsWidget::setMaxChartRange); QObject::connect(new_plot_btn, &QToolButton::clicked, this, &ChartsWidget::newChart); QObject::connect(remove_all_btn, &QToolButton::clicked, this, &ChartsWidget::removeAll); @@ -114,13 +116,12 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) { if (index != -1) updateLayout(true); }); - QObject::connect(dock_btn, &QToolButton::clicked, [this]() { - emit dock(!docking); - docking = !docking; - updateToolBar(); - }); + QObject::connect(dock_btn, &QToolButton::clicked, this, &ChartsWidget::toggleChartsDocking); + setIsDocked(true); newTab(); + qApp->installEventFilter(this); + setWhatsThis(tr(R"( Chart view