diff --git a/.github/workflows/jenkins-pr-trigger.yaml b/.github/workflows/jenkins-pr-trigger.yaml
new file mode 100644
index 0000000000..db1d524018
--- /dev/null
+++ b/.github/workflows/jenkins-pr-trigger.yaml
@@ -0,0 +1,45 @@
+name: jenkins scan
+
+on:
+ issue_comment:
+ types: [created, edited]
+
+jobs:
+ # TODO: gc old branches in a separate job in this workflow
+ scan-comments:
+ runs-on: ubuntu-latest
+ if: ${{ github.event.issue.pull_request }}
+ steps:
+ - name: Check for trigger phrase
+ id: check_comment
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const triggerPhrase = "trigger-jenkins";
+ const comment = context.payload.comment.body;
+ const commenter = context.payload.comment.user.login;
+
+ const { data: permissions } = await github.rest.repos.getCollaboratorPermissionLevel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ username: commenter
+ });
+
+ const hasWriteAccess = permissions.permission === 'write' || permissions.permission === 'admin';
+
+ return (hasWriteAccess && comment.includes(triggerPhrase));
+ result-encoding: json
+
+ - name: Checkout repository
+ if: steps.check_comment.outputs.result == 'true'
+ uses: actions/checkout@v4
+ with:
+ ref: refs/pull/${{ github.event.issue.number }}/head
+
+ - name: Push to tmp-jenkins branch
+ if: steps.check_comment.outputs.result == 'true'
+ run: |
+ git config --global user.name "github-actions[bot]"
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git checkout -b tmp-jenkins-${{ github.event.issue.number }}
+ GIT_LFS_SKIP_PUSH=1 git push -f origin tmp-jenkins-${{ github.event.issue.number }}
diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml
index 42c1dbb5eb..1154948ace 100644
--- a/.github/workflows/selfdrive_tests.yaml
+++ b/.github/workflows/selfdrive_tests.yaml
@@ -338,6 +338,7 @@ jobs:
})
create_ui_report:
+ # This job name needs to be the same as UI_JOB_NAME in ui_preview.yaml
name: Create UI Report
runs-on: ubuntu-latest
steps:
@@ -355,55 +356,5 @@ jobs:
- name: Upload Test Report
uses: actions/upload-artifact@v4
with:
- name: report-${{ inputs.run_number || '1' }}
- path: selfdrive/ui/tests/test_ui/report_${{ inputs.run_number || '1' }}
- - name: Get changes to selfdrive/ui
- if: ${{ github.event_name == 'pull_request' }}
- id: changed-files
- uses: tj-actions/changed-files@v44
- with:
- files: |
- selfdrive/ui/**
- - name: Checkout ci-artifacts
- if: ${{ github.event_name == 'pull_request' && steps.changed-files.outputs.any_changed == 'true' }}
- uses: actions/checkout@v4
- with:
- repository: commaai/ci-artifacts
- ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
- path: ${{ github.workspace }}/ci-artifacts
- ref: master
- - name: Push Screenshots
- if: ${{ github.event_name == 'pull_request' && steps.changed-files.outputs.any_changed == 'true' }}
- working-directory: ${{ github.workspace }}/ci-artifacts
- run: |
- git checkout -b openpilot/pr-${{ github.event.pull_request.number }}
- git config user.name "GitHub Actions Bot"
- git config user.email "<>"
- sudo mv ${{ github.workspace }}/selfdrive/ui/tests/test_ui/report_1/screenshots/* .
- git add .
- git commit -m "screenshots for PR #${{ github.event.pull_request.number }}"
- git push origin openpilot/pr-${{ github.event.pull_request.number }} --force
- - name: Comment Screenshots on PR
- if: ${{ github.event_name == 'pull_request' && steps.changed-files.outputs.any_changed == 'true' }}
- uses: thollander/actions-comment-pull-request@v2
- with:
- message: |
-
- ## UI Screenshots
-
-
-  |
-  |
-
-
-  |
-  |
-
-
-  |
-  |
-
-
- comment_tag: run_id_screenshots
- pr_number: ${{ github.event.pull_request.number }}
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ name: report-${{ github.event.number }}
+ path: selfdrive/ui/tests/test_ui/report_1/screenshots
diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml
index b64963fb60..9085556392 100644
--- a/.github/workflows/tools_tests.yaml
+++ b/.github/workflows/tools_tests.yaml
@@ -73,12 +73,6 @@ jobs:
scons-${{ runner.arch }}-ubuntu2004
- name: Building openpilot
run: uv run scons -u -j$(nproc)
- - name: Saving scons cache
- uses: actions/cache/save@v4
- if: github.ref == 'refs/heads/master'
- with:
- path: /tmp/scons_cache
- key: scons-${{ runner.arch }}-ubuntu2004-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
devcontainer:
name: devcontainer
diff --git a/.github/workflows/ui_preview.yaml b/.github/workflows/ui_preview.yaml
new file mode 100644
index 0000000000..bc19e23778
--- /dev/null
+++ b/.github/workflows/ui_preview.yaml
@@ -0,0 +1,91 @@
+name: "ui preview"
+on:
+ pull_request_target:
+ types: [assigned, opened, synchronize, reopened, edited]
+ branches:
+ - 'master'
+ paths:
+ - 'selfdrive/ui/**'
+
+env:
+ UI_JOB_NAME: "Create UI Report"
+
+jobs:
+ preview:
+ if: github.repository == 'commaai/openpilot'
+ name: preview
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ permissions:
+ contents: read
+ pull-requests: write
+ actions: read
+ steps:
+ - name: Waiting for ui test to start
+ run: sleep 30
+
+ - name: Wait for ui report
+ uses: lewagon/wait-on-check-action@v1.3.4
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ check-name: ${{ env.UI_JOB_NAME }}
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ allowed-conclusions: success
+ wait-interval: 20
+
+ - name: Get workflow run ID
+ id: get_run_id
+ run: |
+ echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?[0-9]+)") | .number')" >> $GITHUB_OUTPUT
+
+ - name: Checkout ci-artifacts
+ uses: actions/checkout@v4
+ with:
+ repository: commaai/ci-artifacts
+ ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
+ path: ${{ github.workspace }}/ci-artifacts
+ ref: master
+
+ - name: Download artifact
+ id: download-artifact
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ run_id: ${{ steps.get_run_id.outputs.run_id }}
+ search_artifacts: true
+ name: report-${{ github.event.number }}
+ path: ${{ github.workspace }}/ci-artifacts
+
+ - name: Push Screenshots
+ working-directory: ${{ github.workspace }}/ci-artifacts
+ run: |
+ git checkout -b openpilot/pr-${{ github.event.number }}
+ git config user.name "GitHub Actions Bot"
+ git config user.email "<>"
+ git add ${{ github.workspace }}/ci-artifacts/*
+ git commit -m "screenshots for PR #${{ github.event.number }}"
+ git push origin openpilot/pr-${{ github.event.number }} --force
+
+ - name: Comment Screenshots on PR
+ uses: thollander/actions-comment-pull-request@v2
+ with:
+ message: |
+
+ ## UI Screenshots
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+ comment_tag: run_id_screenshots
+ pr_number: ${{ github.event.number }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/README.md b/README.md
index db74ee6a1a..5afaf6e7d6 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ To start using openpilot in a car
To use openpilot in a car, you need four things:
1. **Supported Device:** a comma 3/3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x).
2. **Software:** The setup procedure for the comma 3/3X allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version.
-3. **Supported Car:** Ensure that you have one of [the 250+ supported cars](docs/CARS.md).
+3. **Supported Car:** Ensure that you have one of [the 275+ supported cars](docs/CARS.md).
4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3/3X to your car.
We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play.
diff --git a/SConstruct b/SConstruct
index 0c4dbee18e..1f83b1e43d 100644
--- a/SConstruct
+++ b/SConstruct
@@ -102,7 +102,6 @@ if arch == "larch64":
libpath = [
"/usr/local/lib",
- "/usr/lib",
"/system/vendor/lib64",
f"#third_party/acados/{arch}/lib",
]
diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py
index e4841c705f..efeb76c4a8 100644
--- a/selfdrive/controls/lib/longcontrol.py
+++ b/selfdrive/controls/lib/longcontrol.py
@@ -22,22 +22,26 @@ def long_control_state_trans(CP, active, long_control_state, v_ego,
long_control_state = LongCtrlState.off
else:
- if long_control_state in (LongCtrlState.off, LongCtrlState.pid):
- long_control_state = LongCtrlState.pid
- if stopping_condition:
+ if long_control_state == LongCtrlState.off:
+ if not starting_condition:
long_control_state = LongCtrlState.stopping
+ else:
+ if starting_condition and CP.startingState:
+ long_control_state = LongCtrlState.starting
+ else:
+ long_control_state = LongCtrlState.pid
+
elif long_control_state == LongCtrlState.stopping:
if starting_condition and CP.startingState:
long_control_state = LongCtrlState.starting
elif starting_condition:
long_control_state = LongCtrlState.pid
- elif long_control_state == LongCtrlState.starting:
+ elif long_control_state in [LongCtrlState.starting, LongCtrlState.pid]:
if stopping_condition:
long_control_state = LongCtrlState.stopping
elif started_condition:
long_control_state = LongCtrlState.pid
-
return long_control_state
class LongControl:
diff --git a/selfdrive/controls/tests/test_longcontrol.py b/selfdrive/controls/tests/test_longcontrol.py
new file mode 100644
index 0000000000..ab50810d89
--- /dev/null
+++ b/selfdrive/controls/tests/test_longcontrol.py
@@ -0,0 +1,56 @@
+from cereal import car
+from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans
+
+
+
+
+class TestLongControlStateTransition:
+
+ def test_stay_stopped(self):
+ CP = car.CarParams.new_message()
+ active = True
+ current_state = LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=True, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=False, brake_pressed=True, cruise_standstill=False)
+ assert next_state == LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=False, brake_pressed=False, cruise_standstill=True)
+ assert next_state == LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
+ should_stop=False, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.pid
+ active = False
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
+ should_stop=False, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.off
+
+def test_engage():
+ CP = car.CarParams.new_message()
+ active = True
+ current_state = LongCtrlState.off
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=True, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=False, brake_pressed=True, cruise_standstill=False)
+ assert next_state == LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=False, brake_pressed=False, cruise_standstill=True)
+ assert next_state == LongCtrlState.stopping
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=False, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.pid
+
+def test_starting():
+ CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5)
+ active = True
+ current_state = LongCtrlState.starting
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=0.1,
+ should_stop=False, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.starting
+ next_state = long_control_state_trans(CP, active, current_state, v_ego=1.0,
+ should_stop=False, brake_pressed=False, cruise_standstill=False)
+ assert next_state == LongCtrlState.pid
diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx
index e292bb0d2d..ecbe644e09 100644
--- a/selfdrive/modeld/models/commonmodel_pyx.pyx
+++ b/selfdrive/modeld/models/commonmodel_pyx.pyx
@@ -1,5 +1,5 @@
# distutils: language = c++
-# cython: c_string_encoding=ascii
+# cython: c_string_encoding=ascii, language_level=3
import numpy as np
cimport numpy as cnp
diff --git a/selfdrive/modeld/runners/runmodel_pyx.pyx b/selfdrive/modeld/runners/runmodel_pyx.pyx
index e1b201a6a9..12b8ec10ff 100644
--- a/selfdrive/modeld/runners/runmodel_pyx.pyx
+++ b/selfdrive/modeld/runners/runmodel_pyx.pyx
@@ -1,5 +1,5 @@
# distutils: language = c++
-# cython: c_string_encoding=ascii
+# cython: c_string_encoding=ascii, language_level=3
from libcpp.string cimport string
diff --git a/selfdrive/modeld/runners/snpemodel_pyx.pyx b/selfdrive/modeld/runners/snpemodel_pyx.pyx
index c3b2b7e9bd..f83b7c8cff 100644
--- a/selfdrive/modeld/runners/snpemodel_pyx.pyx
+++ b/selfdrive/modeld/runners/snpemodel_pyx.pyx
@@ -1,5 +1,5 @@
# distutils: language = c++
-# cython: c_string_encoding=ascii
+# cython: c_string_encoding=ascii, language_level=3
import os
from libcpp cimport bool
diff --git a/selfdrive/modeld/runners/thneedmodel_pyx.pyx b/selfdrive/modeld/runners/thneedmodel_pyx.pyx
index 53487afa1b..6f8fdd255f 100644
--- a/selfdrive/modeld/runners/thneedmodel_pyx.pyx
+++ b/selfdrive/modeld/runners/thneedmodel_pyx.pyx
@@ -1,5 +1,5 @@
# distutils: language = c++
-# cython: c_string_encoding=ascii
+# cython: c_string_encoding=ascii, language_level=3
from libcpp cimport bool
from libcpp.string cimport string
diff --git a/selfdrive/ui/qt/qt_window.cc b/selfdrive/ui/qt/qt_window.cc
index f71cea04e9..8d3d7cf72e 100644
--- a/selfdrive/ui/qt/qt_window.cc
+++ b/selfdrive/ui/qt/qt_window.cc
@@ -18,7 +18,9 @@ void setMainWindow(QWidget *w) {
wl_surface *s = reinterpret_cast(native->nativeResourceForWindow("surface", w->windowHandle()));
wl_surface_set_buffer_transform(s, WL_OUTPUT_TRANSFORM_270);
wl_surface_commit(s);
- w->showFullScreen();
+
+ w->setWindowState(Qt::WindowFullScreen);
+ w->setVisible(true);
// ensure we have a valid eglDisplay, otherwise the ui will silently fail
void *egl = native->nativeResourceForWindow("egldisplay", w->windowHandle());
diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc
index 47ea5ded4d..7c1f8e44e7 100644
--- a/system/camerad/cameras/camera_qcom2.cc
+++ b/system/camerad/cameras/camera_qcom2.cc
@@ -469,6 +469,16 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num
enabled = enabled_;
if (!enabled) return;
+ if (!openSensor()) {
+ return;
+ }
+
+ configISP();
+ configCSIPHY();
+ linkDevices();
+}
+
+bool CameraState::openSensor() {
sensor_fd = open_v4l_by_name_and_index("cam-sensor-driver", camera_num);
assert(sensor_fd >= 0);
LOGD("opened sensor for %d", camera_num);
@@ -493,7 +503,7 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num
!init_sensor_lambda(new OS04C10)) {
LOGE("** sensor %d FAILED bringup, disabling", camera_num);
enabled = false;
- return;
+ return false;
}
LOGD("-- Probing sensor %d success", camera_num);
@@ -512,7 +522,10 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num
LOG("-- Configuring sensor");
sensors_i2c(ci->init_reg_array.data(), ci->init_reg_array.size(), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, ci->data_word);
+ return true;
+}
+void CameraState::configISP() {
// NOTE: to be able to disable road and wide road, we still have to configure the sensor over i2c
// If you don't do this, the strobe GPIO is an output (even in reset it seems!)
if (!enabled) return;
@@ -570,6 +583,13 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num
isp_dev_handle = *isp_dev_handle_;
LOGD("acquire isp dev");
+ // config ISP
+ alloc_w_mmu_hdl(multi_cam_state->video0_fd, 984480, (uint32_t*)&buf0_handle, 0x20, CAM_MEM_FLAG_HW_READ_WRITE | CAM_MEM_FLAG_KMD_ACCESS |
+ CAM_MEM_FLAG_UMD_ACCESS | CAM_MEM_FLAG_CMD_BUF_TYPE, multi_cam_state->device_iommu, multi_cam_state->cdm_iommu);
+ config_isp(0, 0, 1, buf0_handle, 0);
+}
+
+void CameraState::configCSIPHY() {
csiphy_fd = open_v4l_by_name_and_index("cam-csiphy-driver", camera_num);
assert(csiphy_fd >= 0);
LOGD("opened csiphy for %d", camera_num);
@@ -580,11 +600,6 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num
csiphy_dev_handle = *csiphy_dev_handle_;
LOGD("acquire csiphy dev");
- // config ISP
- alloc_w_mmu_hdl(multi_cam_state->video0_fd, 984480, (uint32_t*)&buf0_handle, 0x20, CAM_MEM_FLAG_HW_READ_WRITE | CAM_MEM_FLAG_KMD_ACCESS |
- CAM_MEM_FLAG_UMD_ACCESS | CAM_MEM_FLAG_CMD_BUF_TYPE, multi_cam_state->device_iommu, multi_cam_state->cdm_iommu);
- config_isp(0, 0, 1, buf0_handle, 0);
-
// config csiphy
LOG("-- Config CSI PHY");
{
@@ -612,15 +627,16 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num
int ret_ = device_config(csiphy_fd, session_handle, csiphy_dev_handle, cam_packet_handle);
assert(ret_ == 0);
}
+}
- // link devices
+void CameraState::linkDevices() {
LOG("-- Link devices");
struct cam_req_mgr_link_info req_mgr_link_info = {0};
req_mgr_link_info.session_hdl = session_handle;
req_mgr_link_info.num_devices = 2;
req_mgr_link_info.dev_hdls[0] = isp_dev_handle;
req_mgr_link_info.dev_hdls[1] = sensor_dev_handle;
- ret = do_cam_control(multi_cam_state->video0_fd, CAM_REQ_MGR_LINK, &req_mgr_link_info, sizeof(req_mgr_link_info));
+ int ret = do_cam_control(multi_cam_state->video0_fd, CAM_REQ_MGR_LINK, &req_mgr_link_info, sizeof(req_mgr_link_info));
link_handle = req_mgr_link_info.link_hdl;
LOGD("link: %d session: 0x%X isp: 0x%X sensors: 0x%X link: 0x%X", ret, session_handle, isp_dev_handle, sensor_dev_handle, link_handle);
diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h
index 0b15c9c3f0..b702d1bb69 100644
--- a/system/camerad/cameras/camera_qcom2.h
+++ b/system/camerad/cameras/camera_qcom2.h
@@ -86,6 +86,11 @@ public:
void sensors_i2c(const struct i2c_random_wr_payload* dat, int len, int op_code, bool data_word);
private:
+ bool openSensor();
+ void configISP();
+ void configCSIPHY();
+ void linkDevices();
+
// for debugging
Params params;
};
diff --git a/tools/install_python_dependencies.sh b/tools/install_python_dependencies.sh
index 276de58db7..a90462888f 100755
--- a/tools/install_python_dependencies.sh
+++ b/tools/install_python_dependencies.sh
@@ -34,7 +34,7 @@ update_uv
# TODO: remove --no-cache once this is fixed: https://github.com/astral-sh/uv/issues/4378
echo "installing python packages..."
-uv --no-cache sync --all-extras
+uv --no-cache sync --frozen --all-extras
source .venv/bin/activate
echo "PYTHONPATH=${PWD}" > $ROOT/.env
diff --git a/tools/op.sh b/tools/op.sh
new file mode 100755
index 0000000000..111a06e81c
--- /dev/null
+++ b/tools/op.sh
@@ -0,0 +1,313 @@
+#!/bin/bash
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+UNDERLINE='\033[4m'
+BOLD='\033[1m'
+NC='\033[0m'
+
+function op_first_install() {
+ (set -e
+
+ echo "Installing op system-wide..."
+ RC_FILE="${HOME}/.$(basename ${SHELL})rc"
+ if [ "$(uname)" == "Darwin" ] && [ $SHELL == "/bin/bash" ]; then
+ RC_FILE="$HOME/.bash_profile"
+ fi
+ op_run_command printf "\nalias op='source "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/op.sh" \"\$@\"'\n" >> $RC_FILE
+ echo -e " ↳ [${GREEN}✔${NC}] op installed successfully. Open a new shell to use it.\n"
+
+ )
+}
+
+# be default, assume openpilot dir is in current directory
+OPENPILOT_ROOT=$(pwd)
+function op_check_openpilot_dir() {
+ while [[ "$OPENPILOT_ROOT" != '/' ]];
+ do
+ if find "$OPENPILOT_ROOT/launch_openpilot.sh" -maxdepth 1 -mindepth 1 &> /dev/null; then
+ return 0
+ fi
+ OPENPILOT_ROOT="$(readlink -f "$OPENPILOT_ROOT/"..)"
+ done
+
+ echo "openpilot directory not found! Make sure that you are inside openpilot"
+ echo "directory or specify one with the --dir option!"
+ return 1
+}
+
+function op_run_command() {
+ CMD="$@"
+ echo -e "${BOLD}Running:${NC} $CMD"
+ if [[ -z "$DRY" ]]; then
+ $CMD
+ fi
+}
+
+function op_check_git() {
+ (set -e
+
+ cd $OPENPILOT_ROOT
+
+ echo "Checking for git..."
+ if ! command -v "git" > /dev/null 2>&1; then
+ echo -e " ↳ [${RED}✗${NC}] git not found on your system!"
+ return 1
+ else
+ echo -e " ↳ [${GREEN}✔${NC}] git found on your system.\n"
+ fi
+
+ echo "Checking for git lfs files..."
+ if [[ $(file -b $(git lfs ls-files -n | grep "\.so" | head -n 1)) == "ASCII text" ]]; then
+ echo -e " ↳ [${RED}✗${NC}] git lfs files not found! Run git lfs pull"
+ return 1
+ else
+ echo -e " ↳ [${GREEN}✔${NC}] git lfs files found on your system.\n"
+ fi
+
+ echo "Checking for git submodules..."
+ if $(git submodule foreach --quiet --recursive 'return 1' 2&> /dev/null); then
+ echo -e " ↳ [${RED}✗${NC}] git submodules not found! Run 'git submodule update --init --recursive'"
+ return 1
+ else
+ echo -e " ↳ [${GREEN}✔${NC}] git submodules found on your system.\n"
+ fi
+
+ )
+}
+
+function op_check_os() {
+ (set -e
+
+ echo "Checking for compatible os version..."
+ if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+
+ if [ -f "/etc/os-release" ]; then
+ source /etc/os-release
+ case "$VERSION_CODENAME" in
+ "jammy" | "kinetic" | "noble" | "focal")
+ echo -e " ↳ [${GREEN}✔${NC}] Ubuntu $VERSION_CODENAME detected.\n"
+ ;;
+ * )
+ echo -e " ↳ [${RED}✗${NC}] Incompatible Ubuntu version $VERSION_CODENAME detected!"
+ return 1
+ ;;
+ esac
+ else
+ echo -e " ↳ [${RED}✗${NC}] No /etc/os-release on your system. Make sure you're running on Ubuntu, or similar!"
+ return 1
+ fi
+
+ elif [[ "$OSTYPE" == "darwin"* ]]; then
+ echo -e " ↳ [${GREEN}✔${NC}] macos detected.\n"
+ else
+ echo -e " ↳ [${RED}✗${NC}] OS type $OSTYPE not supported!"
+ return 1
+ fi
+
+ )
+}
+
+function op_check_python() {
+ (set -e
+
+ echo "Checking for compatible python version..."
+ export REQUIRED_PYTHON_VERSION=$(grep "requires-python" pyproject.toml | cut -d= -f3- | tr -d '"' | tr -d ' ')
+ if ! command -v "python3" > /dev/null 2>&1; then
+ echo -e " ↳ [${RED}✗${NC}] python3 not found on your system. You need python version at least $REQUIRED_PYTHON_VERSION to continue!"
+ return 1
+ else
+ if $(python3 -c "import sys; quit(not sys.version_info >= tuple(map(int, \"$REQUIRED_PYTHON_VERSION\".split('.'))))"); then
+ echo -e " ↳ [${GREEN}✔${NC}] $(python3 --version) detected.\n"
+ else
+ echo -e " ↳ [${RED}✗${NC}] You need python version at least $REQUIRED_PYTHON_VERSION to continue!"
+ return 1
+ fi
+ fi
+
+ )
+}
+
+# this must be run in the same shell as the user calling "op"
+function op_venv() {
+ op_check_openpilot_dir || return 1
+ op_run_command source $OPENPILOT_ROOT/.venv/bin/activate || (echo -e "\nCan't activate venv. Have you ran 'op install' ?" && return 1)
+}
+
+function op_check() {
+ (set -e
+
+ op_check_openpilot_dir
+ cd $OPENPILOT_ROOT
+ op_check_git
+ op_check_os
+ op_check_python
+
+ )
+}
+
+function op_run() {
+ (set -e
+
+ op_venv
+ cd $OPENPILOT_ROOT
+
+ op_run_command $OPENPILOT_ROOT/launch_openpilot.sh
+
+ )
+}
+
+function op_install() {
+ (set -e
+
+ op_check_openpilot_dir
+ cd $OPENPILOT_ROOT
+
+ op_check_os
+ op_check_python
+
+ if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+ $OPENPILOT_ROOT/tools/ubuntu_setup.sh
+ elif [[ "$OSTYPE" == "darwin"* ]]; then
+ $OPENPILOT_ROOT/tools/mac_setup.sh
+ fi
+
+ git submodule update --init --recursive
+ git lfs pull
+
+ )
+}
+
+function op_build() {
+ (set -e
+
+ op_venv
+ cd $OPENPILOT_ROOT
+
+ op_run_command scons $@
+
+ )
+}
+
+function op_juggle() {
+ (set -e
+
+ op_venv
+ cd $OPENPILOT_ROOT
+
+ op_run_command $OPENPILOT_ROOT/tools/plotjuggler/juggle.py $@
+
+ )
+}
+
+function op_linter() {
+ (set -e
+
+ op_venv
+ cd $OPENPILOT_ROOT
+
+ op_run_command pre-commit run --all $@
+
+ )
+}
+
+function op_replay() {
+ (set -e
+ op_check_openpilot_dir
+ cd $OPENPILOT_ROOT
+
+ op_run_command $OPENPILOT_ROOT/tools/replay/replay $@
+
+ )
+}
+
+function op_default() {
+ echo "An openpilot helper"
+ echo ""
+ echo -e "${BOLD}${UNDERLINE}Description:${NC}"
+ echo " op is your entry point for all things related to openpilot development."
+ echo " op is only a wrapper for scripts, tools and commands already existing."
+ echo " op will always show you what it will run on your system."
+ echo ""
+ echo " op will try to find your openpilot directory in the following order:"
+ echo " 1: use the directory specified with the --dir option"
+ echo " 2: use the current working directory"
+ echo " 3: go up the file tree non-recursively"
+ echo ""
+ echo -e "${BOLD}${UNDERLINE}Usage:${NC} op [OPTIONS] "
+ echo ""
+ echo -e "${BOLD}${UNDERLINE}Commands:${NC}"
+ echo -e " ${BOLD}venv${NC} Activate the virtual environment"
+ echo -e " ${BOLD}check${NC} Check system requirements (git, os, python) to start using openpilot"
+ echo -e " ${BOLD}install${NC} Install requirements to use openpilot"
+ echo -e " ${BOLD}build${NC} Build openpilot"
+ echo -e " ${BOLD}run${NC} Run openpilot"
+ echo -e " ${BOLD}juggle${NC} Run Plotjuggler"
+ echo -e " ${BOLD}replay${NC} Run replay"
+ echo -e " ${BOLD}linter${NC} Run all the pre-commit checks"
+ echo -e " ${BOLD}help${NC} Show this message"
+ echo -e " ${BOLD}--install${NC} Install this tool system wide"
+ echo ""
+ echo -e "${BOLD}${UNDERLINE}Options:${NC}"
+ echo -e " ${BOLD}-d, --dir${NC}"
+ echo " Specify the openpilot directory you want to use"
+ echo -e " ${BOLD}--dry${NC}"
+ echo " Don't actually run anything, just print what would be"
+ echo ""
+ echo -e "${BOLD}${UNDERLINE}Examples:${NC}"
+ echo " op --dir /tmp/openpilot check"
+ echo " Run the check command on openpilot located in /tmp/openpilot"
+ echo ""
+ echo " op juggle --install"
+ echo " Install plotjuggler in the openpilot located in your current"
+ echo " working directory"
+ echo ""
+ echo " op --dir /tmp/openpilot build -j4"
+ echo " Run the build command on openpilot located in /tmp/openpilot"
+ echo " on 4 cores"
+}
+
+
+function _op() {
+ # parse Options
+ case $1 in
+ -d | --dir ) shift 1; OPENPILOT_ROOT="$1"; shift 1 ;;
+ --dry ) shift 1; DRY="1" ;;
+ esac
+
+ # parse Commands
+ case $1 in
+ venv ) shift 1; op_venv "$@" ;;
+ check ) shift 1; op_check "$@" ;;
+ install ) shift 1; op_install "$@" ;;
+ build ) shift 1; op_build "$@" ;;
+ run ) shift 1; op_run "$@" ;;
+ juggle ) shift 1; op_juggle "$@" ;;
+ linter ) shift 1; op_linter "$@" ;;
+ replay ) shift 1; op_replay "$@" ;;
+ --install ) shift 1; op_first_install "$@" ;;
+ * ) op_default "$@" ;;
+ esac
+}
+
+_op $@
+
+# remove from env
+unset -f _op
+unset -f op_check
+unset -f op_install
+unset -f op_build
+unset -f op_run
+unset -f op_juggle
+unset -f op_venv
+unset -f op_check_openpilot_dir
+unset -f op_check_git
+unset -f op_check_python
+unset -f op_check_os
+unset -f op_first_install
+unset -f op_default
+unset -f op_run_command
+unset -f op_linter
+unset -f op_replay
+unset DRY
+unset OPENPILOT_ROOT
diff --git a/tools/rerun/run.py b/tools/rerun/run.py
index 421785a2d5..6d9554038c 100755
--- a/tools/rerun/run.py
+++ b/tools/rerun/run.py
@@ -18,6 +18,12 @@ RR_TIMELINE_NAME = "Timeline"
RR_WIN = "rerun_test"
+"""
+Relevant upstream Rerun issues:
+- large time series: https://github.com/rerun-io/rerun/issues/5967
+- loading videos directly: https://github.com/rerun-io/rerun/issues/6532
+"""
+
class Rerunner:
def __init__(self, route, segment_range, camera_config, enabled_services):
self.enabled_services = [s.lower() for s in enabled_services]