diff --git a/RELEASES.md b/RELEASES.md
index ced7e3d4f..3ff80ca4d 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,10 +1,12 @@
-Version 0.9.9 (2025-04-30)
+Version 0.9.9 (2025-05-15)
========================
* New driving model
+ * New training architecture supervised by MLSIM
+* Steering actuator delay is now learned online
* Tesla Model 3 and Y support thanks to lukasloetkolben!
* Coming soon
- * New driving model supervised by MLSIM
- * An online learner for steering actuator delay
+ * New Honda models
+ * Bigger vision model
Version 0.9.8 (2025-02-28)
========================
diff --git a/common/model.h b/common/model.h
index 891178d19..551c0794b 100644
--- a/common/model.h
+++ b/common/model.h
@@ -1 +1 @@
-#define DEFAULT_MODEL "Tomb_Raider_5 (Default)"
+#define DEFAULT_MODEL "Tomb_Raider_6 (Default)"
diff --git a/common/tests/test_simple_kalman.py b/common/tests/test_simple_kalman.py
index f4a967e58..e44ac2cc5 100644
--- a/common/tests/test_simple_kalman.py
+++ b/common/tests/test_simple_kalman.py
@@ -24,6 +24,6 @@ class TestSimpleKalman:
self.kf.set_x([[1.0], [1.0]])
assert self.kf.x == [[1.0], [1.0]]
- def update_returns_state(self):
+ def test_update_returns_state(self):
x = self.kf.update(100)
- assert x == self.kf.x
+ assert x == [i[0] for i in self.kf.x]
diff --git a/docs/CARS.md b/docs/CARS.md
index 95af300c0..c22568ce2 100644
--- a/docs/CARS.md
+++ b/docs/CARS.md
@@ -80,7 +80,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts
- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts
- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[](##)|[](##)|Parts
- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
-|Honda|HR-V 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
+|Honda|HR-V 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts
- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[](##)|[](##)|Parts
- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[](##)|[](##)|Parts
- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here ||
diff --git a/docs/how-to/turn-the-speed-blue.md b/docs/how-to/turn-the-speed-blue.md
index e8b55ef81..64f4475df 100644
--- a/docs/how-to/turn-the-speed-blue.md
+++ b/docs/how-to/turn-the-speed-blue.md
@@ -20,7 +20,7 @@ source .venv/bin/activate
Then, compile openpilot:
```bash
-scons -j8
+scons -j$(nproc)
```
## 2. Run replay
@@ -38,61 +38,77 @@ The openpilot UI should launch and show a replay of the demo route.
If you have your own comma device, you can replace `--demo` with one of your own routes from comma connect.
+
## 3. Make the speed blue
-Search for “mph” with git grep in the `ui` folder.
+Now let’s update the speed display color in the UI.
+
+Search for the function responsible for rendering UI text:
```bash
-$ git grep "mph" selfdrive/ui/
-paint.cc: ui_draw_text(s, s->fb_w/2, 290, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, COLOR_WHITE_ALPHA(200), "sans-regular");
+git grep "drawText" selfdrive/ui/qt/onroad/hud.cc
```
-The line right above contains the actual speed. Unfortunately, COLOR_BLUE isn’t defined, but a git grep of COLOR_WHITE shows it’s nvgRGBA(255, 255, 255, 255). Personally, I like a lighter blue, so I went with #8080FF.
+You’ll find the relevant code inside `selfdrive/ui/qt/onroad/hud.cc`, in this function:
+
+```cpp
+void HudRenderer::drawText(QPainter &p, int x, int y, const QString &text, int alpha) {
+ QRect real_rect = p.fontMetrics().boundingRect(text);
+ real_rect.moveCenter({x, y - real_rect.height() / 2});
+
+ p.setPen(QColor(0xff, 0xff, 0xff, alpha)); // <- this sets the speed text color
+ p.drawText(real_rect.x(), real_rect.bottom(), text);
+}
+```
+
+Change the `QColor(...)` line to make it **blue** instead of white. A nice soft blue is `#8080FF`, which translates to:
+
+```diff
+- p.setPen(QColor(0xff, 0xff, 0xff, alpha));
++ p.setPen(QColor(0x80, 0x80, 0xFF, alpha));
+```
+
+This change will tint all speed-related UI text to blue with the same transparency (`alpha`).
+
+---
+
+## 4. Rebuild the UI
+
+After making changes, rebuild Openpilot so your new UI is compiled:
```bash
-$ git diff
-diff --git a/selfdrive/ui/paint.cc b/selfdrive/ui/paint.cc
-index 821d95115..cc996eaa1 100644
---- a/selfdrive/ui/paint.cc
-+++ b/selfdrive/ui/paint.cc
-@@ -175,8 +175,8 @@ static void ui_draw_vision_speed(UIState *s) {
- const float speed = std::max(0.0, (*s->sm)["carState"].getCarState().getVEgo() * (s->scene.is_metric ? 3.6 : 2.2369363));
- const std::string speed_str = std::to_string((int)std::nearbyint(speed));
- nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE);
-- ui_draw_text(s, s->fb_w/2, 210, speed_str.c_str(), 96 * 2.5, COLOR_WHITE, "sans-bold");
-- ui_draw_text(s, s->fb_w/2, 290, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, COLOR_WHITE_ALPHA(200), "sans-regular");
-+ ui_draw_text(s, s->fb_w/2, 210, speed_str.c_str(), 96 * 2.5, nvgRGBA(128, 128, 255, 255), "sans-bold");
-+ ui_draw_text(s, s->fb_w/2, 290, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, nvgRGBA(128, 128, 255, 200), "sans-regular");
- }
-
- static void ui_draw_vision_event(UIState *s) {
+scons -j$(nproc) && selfdrive/ui/ui
```
-
-
-## 4. Rebuild UI, and admire your work
-
-```
-scons -j8 && selfdrive/ui/ui
-```
-

+You should now see the speed displayed in a nice blue shade during the demo replay.
+
+---
+
## 5. Push your fork to GitHub
-Click fork on GitHub. Then, push with:
+Click **"Fork"** on the [Openpilot GitHub repo](https://github.com/commaai/openpilot). Then push with:
```bash
git remote rm origin
git remote add origin git@github.com:/openpilot.git
git add .
-git commit -m "Make the speed blue."
+git commit -m "Make the speed display blue"
git push --set-upstream origin master
```
-## 6. Run your fork on device in your car!
+---
-Uninstall openpilot from your device through the settings. Then, enter the URL for your very own installer:
+## 6. Run your fork on your comma device
+
+Uninstall Openpilot through the settings on your device.
+
+Then reinstall using your own GitHub-hosted fork:
```
installer.comma.ai//master
```
-## 7. Admire your work IRL
+---
+
+## 7. Admire your work IRL 🚗💨
+
+You’ve now successfully modified Openpilot’s UI and deployed it to your own car!

diff --git a/opendbc_repo b/opendbc_repo
index f55dcd5cf..63fbea047 160000
--- a/opendbc_repo
+++ b/opendbc_repo
@@ -1 +1 @@
-Subproject commit f55dcd5cf6565073976670cf9d0bd5f30e2280f0
+Subproject commit 63fbea047be715cb772701270e66eddda49433a4
diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py
index 3f99f1bbb..56eeac3bd 100644
--- a/selfdrive/controls/lib/drive_helpers.py
+++ b/selfdrive/controls/lib/drive_helpers.py
@@ -62,3 +62,13 @@ def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.
should_stop = (v_target < vEgoStopping and
v_target_1sec < vEgoStopping)
return a_target, should_stop
+
+def curv_from_psis(psi_target, psi_rate, vego, action_t):
+ vego = np.clip(vego, MIN_SPEED, np.inf)
+ curv_from_psi = psi_target / (vego * action_t)
+ return 2*curv_from_psi - psi_rate / vego
+
+def get_curvature_from_plan(yaws, yaw_rates, t_idxs, vego, action_t):
+ psi_target = np.interp(action_t, t_idxs, yaws)
+ psi_rate = yaw_rates[0]
+ return curv_from_psis(psi_target, psi_rate, vego, action_t)
diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py
index 980550643..c1b3dd52d 100755
--- a/selfdrive/modeld/modeld.py
+++ b/selfdrive/modeld/modeld.py
@@ -26,7 +26,7 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS
from openpilot.common.transformations.model import get_warp_matrix
from openpilot.system import sentry
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
-from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value
+from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan
from openpilot.selfdrive.modeld.parse_model_outputs import Parser
from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
@@ -54,8 +54,9 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.
ModelConstants.T_IDXS,
action_t=long_action_t)
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
-
- desired_curvature = model_output['desired_curvature'][0, 0]
+ desired_curvature = get_curvature_from_plan(plan[:, Plan.T_FROM_CURRENT_EULER][:, 2],
+ plan[:, Plan.ORIENTATION_RATE][:, 2],
+ ModelConstants.T_IDXS, v_ego, lat_action_t)
if v_ego > MIN_LAT_CONTROL_SPEED:
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS)
else:
diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx
index 2075792f4..5b9b68682 100644
--- a/selfdrive/modeld/models/driving_policy.onnx
+++ b/selfdrive/modeld/models/driving_policy.onnx
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b86d130c5db2d772da1b139c136ed86976f37137129a19a6b881fdf641bca198
-size 15578328
+oid sha256:8a478376723ba79e2393ca95e2d3e497571ba6fed113e5f13a36f0e4b4d4a7c5
+size 15588463
diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx
index 5e08b7847..0a7b4a803 100644
--- a/selfdrive/modeld/models/driving_vision.onnx
+++ b/selfdrive/modeld/models/driving_vision.onnx
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f222d2c528f1763828de01bb55e8979b1e4056e1dbb41350f521d2d2bb09d177
+oid sha256:dad289ae367cefcb862ef1d707fb4919d008f0eeaa1ebaf18df58d8de5a7d96e
size 46265585
diff --git a/sunnypilot/models/default_model.py b/sunnypilot/models/default_model.py
old mode 100644
new mode 100755
diff --git a/sunnypilot/models/tests/model_hash b/sunnypilot/models/tests/model_hash
index eab02971f..4504dca4b 100644
--- a/sunnypilot/models/tests/model_hash
+++ b/sunnypilot/models/tests/model_hash
@@ -1 +1 @@
-ee1b65ca7e29c7c98813af633474d190a4c86ac55d5d0d82f33672159744fb40
\ No newline at end of file
+82ead1a4ecffbde961382329a6cf71bc97a6b112fbfb1f175a51c28ee2472bec
\ No newline at end of file
diff --git a/tools/clip/run.py b/tools/clip/run.py
new file mode 100755
index 000000000..97baa880e
--- /dev/null
+++ b/tools/clip/run.py
@@ -0,0 +1,215 @@
+#!/usr/bin/env python3
+
+import atexit
+import logging
+import os
+import platform
+import shutil
+import time
+from argparse import ArgumentParser, ArgumentTypeError
+from collections.abc import Sequence
+from pathlib import Path
+from random import randint
+from subprocess import Popen, PIPE
+from typing import Literal
+
+from cereal.messaging import SubMaster
+from openpilot.common.basedir import BASEDIR
+from openpilot.common.prefix import OpenpilotPrefix
+from openpilot.tools.lib.route import SegmentRange, get_max_seg_number_cached
+
+DEFAULT_OUTPUT = 'output.mp4'
+DEMO_START = 90
+DEMO_END = 105
+DEMO_ROUTE = 'a2a0ccea32023010/2023-07-27--13-01-19'
+FRAMERATE = 20
+PIXEL_DEPTH = '24'
+RESOLUTION = '2160x1080'
+SECONDS_TO_WARM = 2
+PROC_WAIT_SECONDS = 5
+
+REPLAY = str(Path(BASEDIR, 'tools/replay/replay').resolve())
+UI = str(Path(BASEDIR, 'selfdrive/ui/ui').resolve())
+
+logger = logging.getLogger('clip.py')
+
+
+def check_for_failure(proc: Popen):
+ exit_code = proc.poll()
+ if exit_code is not None and exit_code != 0:
+ cmd = str(proc.args)
+ if isinstance(proc.args, str):
+ cmd = proc.args
+ elif isinstance(proc.args, Sequence):
+ cmd = str(proc.args[0])
+ msg = f'{cmd} failed, exit code {exit_code}'
+ logger.error(msg)
+ stdout, stderr = proc.communicate()
+ if stdout:
+ logger.error(stdout.decode())
+ if stderr:
+ logger.error(stderr.decode())
+ raise ChildProcessError(msg)
+
+
+def parse_args(parser: ArgumentParser):
+ args = parser.parse_args()
+ if args.demo:
+ args.route = DEMO_ROUTE
+ if args.start is None or args.end is None:
+ args.start = DEMO_START
+ args.end = DEMO_END
+ elif args.route.count('/') == 1:
+ if args.start is None or args.end is None:
+ parser.error('must provide both start and end if timing is not in the route ID')
+ elif args.route.count('/') == 3:
+ if args.start is not None or args.end is not None:
+ parser.error('don\'t provide timing when including it in the route ID')
+ parts = args.route.split('/')
+ args.route = '/'.join(parts[:2])
+ args.start = int(parts[2])
+ args.end = int(parts[3])
+ if args.end <= args.start:
+ parser.error(f'end ({args.end}) must be greater than start ({args.start})')
+ if args.start < SECONDS_TO_WARM:
+ parser.error(f'start must be greater than {SECONDS_TO_WARM}s to allow the UI time to warm up')
+
+ # if using local files, don't worry about length check right now so we skip the network call
+ # TODO: derive segment count from local FS
+ if not args.data_dir:
+ try:
+ num_segs = get_max_seg_number_cached(SegmentRange(args.route))
+ except Exception as e:
+ parser.error(f'failed to get route length: {e}')
+
+ # FIXME: length isn't exactly max segment seconds, simplify to replay exiting at end of data
+ length = round(num_segs * 60)
+ if args.start >= length:
+ parser.error(f'start ({args.start}s) cannot be after end of route ({length}s)')
+ if args.end > length:
+ parser.error(f'end ({args.end}s) cannot be after end of route ({length}s)')
+
+ return args
+
+
+def start_proc(args: list[str], env: dict[str, str]):
+ return Popen(args, env=env, stdout=PIPE, stderr=PIPE)
+
+
+def validate_env(parser: ArgumentParser):
+ if platform.system() not in ['Linux']:
+ parser.exit(1, f'clip.py: error: {platform.system()} is not a supported operating system\n')
+ for proc in ['Xvfb', 'ffmpeg']:
+ if shutil.which(proc) is None:
+ parser.exit(1, f'clip.py: error: missing {proc} command, is it installed?\n')
+ for proc in [REPLAY, UI]:
+ if shutil.which(proc) is None:
+ parser.exit(1, f'clip.py: error: missing {proc} command, did you build openpilot yet?\n')
+
+
+def validate_output_file(output_file: str):
+ if not output_file.endswith('.mp4'):
+ raise ArgumentTypeError('output must be an mp4')
+ return output_file
+
+
+def validate_route(route: str):
+ if route.count('/') not in (1, 3):
+ raise ArgumentTypeError(f'route must include or exclude timing, example: {DEMO_ROUTE}')
+ return route
+
+
+def wait_for_frames(procs: list[Popen]):
+ sm = SubMaster(['uiDebug'])
+ no_frames_drawn = True
+ while no_frames_drawn:
+ sm.update()
+ no_frames_drawn = sm['uiDebug'].drawTimeMillis == 0.
+ for proc in procs:
+ check_for_failure(proc)
+
+
+def clip(data_dir: str | None, quality: Literal['low', 'high'], prefix: str, route: str, output_filepath: str, start: int, end: int, target_size_mb: int):
+ logger.info(f'clipping route {route}, start={start} end={end} quality={quality} target_filesize={target_size_mb}MB')
+
+ begin_at = max(start - SECONDS_TO_WARM, 0)
+ duration = end - start
+ bit_rate_kbps = int(round(target_size_mb * 8 * 1024 * 1024 / duration / 1000))
+
+ # TODO: evaluate creating fn that inspects /tmp/.X11-unix and creates unused display to avoid possibility of collision
+ display = f':{randint(99, 999)}'
+
+ ffmpeg_cmd = [
+ 'ffmpeg', '-y', '-video_size', RESOLUTION, '-framerate', str(FRAMERATE), '-f', 'x11grab', '-draw_mouse', '0',
+ '-i', display, '-c:v', 'libx264', '-maxrate', f'{bit_rate_kbps}k', '-bufsize', f'{bit_rate_kbps*2}k', '-crf', '23',
+ '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-movflags', '+faststart', '-f', 'mp4', '-t', str(duration), output_filepath,
+ ]
+
+ replay_cmd = [REPLAY, '-c', '1', '-s', str(begin_at), '--prefix', prefix]
+ if data_dir:
+ replay_cmd.extend(['--data_dir', data_dir])
+ if quality == 'low':
+ replay_cmd.append('--qcam')
+ replay_cmd.append(route)
+
+ ui_cmd = [UI, '-platform', 'xcb']
+ xvfb_cmd = ['Xvfb', display, '-terminate', '-screen', '0', f'{RESOLUTION}x{PIXEL_DEPTH}']
+
+ with OpenpilotPrefix(prefix, shared_download_cache=True):
+ env = os.environ.copy()
+ env['DISPLAY'] = display
+
+ xvfb_proc = start_proc(xvfb_cmd, env)
+ atexit.register(lambda: xvfb_proc.terminate())
+ ui_proc = start_proc(ui_cmd, env)
+ atexit.register(lambda: ui_proc.terminate())
+ replay_proc = start_proc(replay_cmd, env)
+ atexit.register(lambda: replay_proc.terminate())
+ procs = [replay_proc, ui_proc, xvfb_proc]
+
+ logger.info('waiting for replay to begin (loading segments, may take a while)...')
+ wait_for_frames(procs)
+
+ logger.debug(f'letting UI warm up ({SECONDS_TO_WARM}s)...')
+ time.sleep(SECONDS_TO_WARM)
+ for proc in procs:
+ check_for_failure(proc)
+
+ ffmpeg_proc = start_proc(ffmpeg_cmd, env)
+ procs.append(ffmpeg_proc)
+ atexit.register(lambda: ffmpeg_proc.terminate())
+
+ logger.info(f'recording in progress ({duration}s)...')
+ ffmpeg_proc.wait(duration + PROC_WAIT_SECONDS)
+ for proc in procs:
+ check_for_failure(proc)
+ logger.info(f'recording complete: {Path(output_filepath).resolve()}')
+
+
+def main():
+ p = ArgumentParser(prog='clip.py', description='clip your openpilot route.', epilog='comma.ai')
+ validate_env(p)
+ route_group = p.add_mutually_exclusive_group(required=True)
+ route_group.add_argument('route', nargs='?', type=validate_route, help=f'The route (e.g. {DEMO_ROUTE} or {DEMO_ROUTE}/{DEMO_START}/{DEMO_END})')
+ route_group.add_argument('--demo', help='use the demo route', action='store_true')
+ p.add_argument('-d', '--data-dir', help='local directory where route data is stored')
+ p.add_argument('-e', '--end', help='stop clipping at seconds', type=int)
+ p.add_argument('-f', '--file-size', help='target file size (Discord/GitHub support max 10MB, default is 9MB)', type=float, default=9.)
+ p.add_argument('-o', '--output', help='output clip to (.mp4)', type=validate_output_file, default=DEFAULT_OUTPUT)
+ p.add_argument('-p', '--prefix', help='openpilot prefix', default=f'clip_{randint(100, 99999)}')
+ p.add_argument('-q', '--quality', help='quality of camera (low = qcam, high = hevc)', choices=['low', 'high'], default='high')
+ p.add_argument('-s', '--start', help='start clipping at seconds', type=int)
+ args = parse_args(p)
+ try:
+ clip(args.data_dir, args.quality, args.prefix, args.route, args.output, args.start, args.end, args.file_size)
+ except KeyboardInterrupt as e:
+ logger.exception('interrupted by user', exc_info=e)
+ except Exception as e:
+ logger.exception('encountered error', exc_info=e)
+ finally:
+ atexit._run_exitfuncs()
+
+
+if __name__ == '__main__':
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s %(name)s %(levelname)s\t%(message)s')
+ main()
diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh
index 03302364e..f33569704 100755
--- a/tools/install_ubuntu_dependencies.sh
+++ b/tools/install_ubuntu_dependencies.sh
@@ -63,7 +63,8 @@ function install_ubuntu_common_requirements() {
libqt5svg5-dev \
libqt5serialbus5-dev \
libqt5x11extras5-dev \
- libqt5opengl5-dev
+ libqt5opengl5-dev \
+ xvfb
}
# Install Ubuntu 24.04 LTS packages
diff --git a/tools/op.sh b/tools/op.sh
index b4508f7ef..8a8faaf23 100755
--- a/tools/op.sh
+++ b/tools/op.sh
@@ -328,6 +328,11 @@ function op_sim() {
op_run_command exec tools/sim/launch_openpilot.sh
}
+function op_clip() {
+ op_before_cmd
+ op_run_command tools/clip/run.py $@
+}
+
function op_switch() {
REMOTE="origin"
if [ "$#" -gt 1 ]; then
@@ -395,6 +400,7 @@ function op_default() {
echo -e " ${BOLD}juggle${NC} Run PlotJuggler"
echo -e " ${BOLD}replay${NC} Run Replay"
echo -e " ${BOLD}cabana${NC} Run Cabana"
+ echo -e " ${BOLD}clip${NC} Run clip (linux only)"
echo -e " ${BOLD}adb${NC} Run adb shell"
echo ""
echo -e "${BOLD}${UNDERLINE}Commands [Testing]:${NC}"
@@ -445,6 +451,7 @@ function _op() {
lint ) shift 1; op_lint "$@" ;;
test ) shift 1; op_test "$@" ;;
replay ) shift 1; op_replay "$@" ;;
+ clip ) shift 1; op_clip "$@" ;;
sim ) shift 1; op_sim "$@" ;;
install ) shift 1; op_install "$@" ;;
switch ) shift 1; op_switch "$@" ;;
diff --git a/tools/replay/README.md b/tools/replay/README.md
index f02090c58..719f7d2db 100644
--- a/tools/replay/README.md
+++ b/tools/replay/README.md
@@ -81,7 +81,7 @@ Arguments:
connect.comma.ai
```
-## Visualize the Replay in the Openpilot UI
+## Visualize the Replay in the openpilot UI
To visualize the replay within the openpilot UI, run the following commands:
```bash
diff --git a/tools/replay/consoleui.h b/tools/replay/consoleui.h
index 3d4abeb45..7731f8017 100644
--- a/tools/replay/consoleui.h
+++ b/tools/replay/consoleui.h
@@ -12,7 +12,7 @@ public:
ConsoleUI(Replay *replay);
~ConsoleUI();
int exec();
- inline static const std::array speed_array = {0.2f, 0.5f, 1.0f, 2.0f, 3.0f};
+ inline static const std::array speed_array = {0.2f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f};
private:
void initWindows();
diff --git a/uv.lock b/uv.lock
index a8a99e997..498101045 100644
--- a/uv.lock
+++ b/uv.lock
@@ -217,11 +217,11 @@ wheels = [
[[package]]
name = "certifi"
-version = "2025.1.31"
+version = "2025.4.26"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload_time = "2025-01-31T02:16:47.166Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload_time = "2025-04-26T02:12:29.51Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload_time = "2025-01-31T02:16:45.015Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload_time = "2025-04-26T02:12:27.662Z" },
]
[[package]]
@@ -1632,24 +1632,28 @@ wheels = [
[[package]]
name = "pyarrow"
-version = "19.0.1"
+version = "20.0.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7f/09/a9046344212690f0632b9c709f9bf18506522feb333c894d0de81d62341a/pyarrow-19.0.1.tar.gz", hash = "sha256:3bf266b485df66a400f282ac0b6d1b500b9d2ae73314a153dbe97d6d5cc8a99e", size = 1129437, upload_time = "2025-02-18T18:55:57.027Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/ee/a7810cb9f3d6e9238e61d312076a9859bf3668fd21c69744de9532383912/pyarrow-20.0.0.tar.gz", hash = "sha256:febc4a913592573c8d5805091a6c2b5064c8bd6e002131f01061797d91c783c1", size = 1125187, upload_time = "2025-04-27T12:34:23.264Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a0/55/f1a8d838ec07fe3ca53edbe76f782df7b9aafd4417080eebf0b42aab0c52/pyarrow-19.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:cc55d71898ea30dc95900297d191377caba257612f384207fe9f8293b5850f90", size = 30713987, upload_time = "2025-02-18T18:52:20.463Z" },
- { url = "https://files.pythonhosted.org/packages/13/12/428861540bb54c98a140ae858a11f71d041ef9e501e6b7eb965ca7909505/pyarrow-19.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:7a544ec12de66769612b2d6988c36adc96fb9767ecc8ee0a4d270b10b1c51e00", size = 32135613, upload_time = "2025-02-18T18:52:25.29Z" },
- { url = "https://files.pythonhosted.org/packages/2f/8a/23d7cc5ae2066c6c736bce1db8ea7bc9ac3ef97ac7e1c1667706c764d2d9/pyarrow-19.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0148bb4fc158bfbc3d6dfe5001d93ebeed253793fff4435167f6ce1dc4bddeae", size = 41149147, upload_time = "2025-02-18T18:52:30.975Z" },
- { url = "https://files.pythonhosted.org/packages/a2/7a/845d151bb81a892dfb368bf11db584cf8b216963ccce40a5cf50a2492a18/pyarrow-19.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f24faab6ed18f216a37870d8c5623f9c044566d75ec586ef884e13a02a9d62c5", size = 42178045, upload_time = "2025-02-18T18:52:36.859Z" },
- { url = "https://files.pythonhosted.org/packages/a7/31/e7282d79a70816132cf6cae7e378adfccce9ae10352d21c2fecf9d9756dd/pyarrow-19.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:4982f8e2b7afd6dae8608d70ba5bd91699077323f812a0448d8b7abdff6cb5d3", size = 40532998, upload_time = "2025-02-18T18:52:42.578Z" },
- { url = "https://files.pythonhosted.org/packages/b8/82/20f3c290d6e705e2ee9c1fa1d5a0869365ee477e1788073d8b548da8b64c/pyarrow-19.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:49a3aecb62c1be1d822f8bf629226d4a96418228a42f5b40835c1f10d42e4db6", size = 42084055, upload_time = "2025-02-18T18:52:48.749Z" },
- { url = "https://files.pythonhosted.org/packages/ff/77/e62aebd343238863f2c9f080ad2ef6ace25c919c6ab383436b5b81cbeef7/pyarrow-19.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:008a4009efdb4ea3d2e18f05cd31f9d43c388aad29c636112c2966605ba33466", size = 25283133, upload_time = "2025-02-18T18:52:54.549Z" },
- { url = "https://files.pythonhosted.org/packages/78/b4/94e828704b050e723f67d67c3535cf7076c7432cd4cf046e4bb3b96a9c9d/pyarrow-19.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:80b2ad2b193e7d19e81008a96e313fbd53157945c7be9ac65f44f8937a55427b", size = 30670749, upload_time = "2025-02-18T18:53:00.062Z" },
- { url = "https://files.pythonhosted.org/packages/7e/3b/4692965e04bb1df55e2c314c4296f1eb12b4f3052d4cf43d29e076aedf66/pyarrow-19.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:ee8dec072569f43835932a3b10c55973593abc00936c202707a4ad06af7cb294", size = 32128007, upload_time = "2025-02-18T18:53:06.581Z" },
- { url = "https://files.pythonhosted.org/packages/22/f7/2239af706252c6582a5635c35caa17cb4d401cd74a87821ef702e3888957/pyarrow-19.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d5d1ec7ec5324b98887bdc006f4d2ce534e10e60f7ad995e7875ffa0ff9cb14", size = 41144566, upload_time = "2025-02-18T18:53:11.958Z" },
- { url = "https://files.pythonhosted.org/packages/fb/e3/c9661b2b2849cfefddd9fd65b64e093594b231b472de08ff658f76c732b2/pyarrow-19.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ad4c0eb4e2a9aeb990af6c09e6fa0b195c8c0e7b272ecc8d4d2b6574809d34", size = 42202991, upload_time = "2025-02-18T18:53:17.678Z" },
- { url = "https://files.pythonhosted.org/packages/fe/4f/a2c0ed309167ef436674782dfee4a124570ba64299c551e38d3fdaf0a17b/pyarrow-19.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d383591f3dcbe545f6cc62daaef9c7cdfe0dff0fb9e1c8121101cabe9098cfa6", size = 40507986, upload_time = "2025-02-18T18:53:26.263Z" },
- { url = "https://files.pythonhosted.org/packages/27/2e/29bb28a7102a6f71026a9d70d1d61df926887e36ec797f2e6acfd2dd3867/pyarrow-19.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b4c4156a625f1e35d6c0b2132635a237708944eb41df5fbe7d50f20d20c17832", size = 42087026, upload_time = "2025-02-18T18:53:33.063Z" },
- { url = "https://files.pythonhosted.org/packages/16/33/2a67c0f783251106aeeee516f4806161e7b481f7d744d0d643d2f30230a5/pyarrow-19.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:5bd1618ae5e5476b7654c7b55a6364ae87686d4724538c24185bbb2952679960", size = 25250108, upload_time = "2025-02-18T18:53:38.462Z" },
+ { url = "https://files.pythonhosted.org/packages/47/a2/b7930824181ceadd0c63c1042d01fa4ef63eee233934826a7a2a9af6e463/pyarrow-20.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:24ca380585444cb2a31324c546a9a56abbe87e26069189e14bdba19c86c049f0", size = 30856035, upload_time = "2025-04-27T12:28:40.78Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/18/c765770227d7f5bdfa8a69f64b49194352325c66a5c3bb5e332dfd5867d9/pyarrow-20.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:95b330059ddfdc591a3225f2d272123be26c8fa76e8c9ee1a77aad507361cfdb", size = 32309552, upload_time = "2025-04-27T12:28:47.051Z" },
+ { url = "https://files.pythonhosted.org/packages/44/fb/dfb2dfdd3e488bb14f822d7335653092dde150cffc2da97de6e7500681f9/pyarrow-20.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f0fb1041267e9968c6d0d2ce3ff92e3928b243e2b6d11eeb84d9ac547308232", size = 41334704, upload_time = "2025-04-27T12:28:55.064Z" },
+ { url = "https://files.pythonhosted.org/packages/58/0d/08a95878d38808051a953e887332d4a76bc06c6ee04351918ee1155407eb/pyarrow-20.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8ff87cc837601532cc8242d2f7e09b4e02404de1b797aee747dd4ba4bd6313f", size = 42399836, upload_time = "2025-04-27T12:29:02.13Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/cd/efa271234dfe38f0271561086eedcad7bc0f2ddd1efba423916ff0883684/pyarrow-20.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7a3a5dcf54286e6141d5114522cf31dd67a9e7c9133d150799f30ee302a7a1ab", size = 40711789, upload_time = "2025-04-27T12:29:09.951Z" },
+ { url = "https://files.pythonhosted.org/packages/46/1f/7f02009bc7fc8955c391defee5348f510e589a020e4b40ca05edcb847854/pyarrow-20.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a6ad3e7758ecf559900261a4df985662df54fb7fdb55e8e3b3aa99b23d526b62", size = 42301124, upload_time = "2025-04-27T12:29:17.187Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/92/692c562be4504c262089e86757a9048739fe1acb4024f92d39615e7bab3f/pyarrow-20.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6bb830757103a6cb300a04610e08d9636f0cd223d32f388418ea893a3e655f1c", size = 42916060, upload_time = "2025-04-27T12:29:24.253Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/ec/9f5c7e7c828d8e0a3c7ef50ee62eca38a7de2fa6eb1b8fa43685c9414fef/pyarrow-20.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:96e37f0766ecb4514a899d9a3554fadda770fb57ddf42b63d80f14bc20aa7db3", size = 44547640, upload_time = "2025-04-27T12:29:32.782Z" },
+ { url = "https://files.pythonhosted.org/packages/54/96/46613131b4727f10fd2ffa6d0d6f02efcc09a0e7374eff3b5771548aa95b/pyarrow-20.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3346babb516f4b6fd790da99b98bed9708e3f02e734c84971faccb20736848dc", size = 25781491, upload_time = "2025-04-27T12:29:38.464Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/d6/0c10e0d54f6c13eb464ee9b67a68b8c71bcf2f67760ef5b6fbcddd2ab05f/pyarrow-20.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:75a51a5b0eef32727a247707d4755322cb970be7e935172b6a3a9f9ae98404ba", size = 30815067, upload_time = "2025-04-27T12:29:44.384Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/e2/04e9874abe4094a06fd8b0cbb0f1312d8dd7d707f144c2ec1e5e8f452ffa/pyarrow-20.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:211d5e84cecc640c7a3ab900f930aaff5cd2702177e0d562d426fb7c4f737781", size = 32297128, upload_time = "2025-04-27T12:29:52.038Z" },
+ { url = "https://files.pythonhosted.org/packages/31/fd/c565e5dcc906a3b471a83273039cb75cb79aad4a2d4a12f76cc5ae90a4b8/pyarrow-20.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ba3cf4182828be7a896cbd232aa8dd6a31bd1f9e32776cc3796c012855e1199", size = 41334890, upload_time = "2025-04-27T12:29:59.452Z" },
+ { url = "https://files.pythonhosted.org/packages/af/a9/3bdd799e2c9b20c1ea6dc6fa8e83f29480a97711cf806e823f808c2316ac/pyarrow-20.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c3a01f313ffe27ac4126f4c2e5ea0f36a5fc6ab51f8726cf41fee4b256680bd", size = 42421775, upload_time = "2025-04-27T12:30:06.875Z" },
+ { url = "https://files.pythonhosted.org/packages/10/f7/da98ccd86354c332f593218101ae56568d5dcedb460e342000bd89c49cc1/pyarrow-20.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a2791f69ad72addd33510fec7bb14ee06c2a448e06b649e264c094c5b5f7ce28", size = 40687231, upload_time = "2025-04-27T12:30:13.954Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/1b/2168d6050e52ff1e6cefc61d600723870bf569cbf41d13db939c8cf97a16/pyarrow-20.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4250e28a22302ce8692d3a0e8ec9d9dde54ec00d237cff4dfa9c1fbf79e472a8", size = 42295639, upload_time = "2025-04-27T12:30:21.949Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/66/2d976c0c7158fd25591c8ca55aee026e6d5745a021915a1835578707feb3/pyarrow-20.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:89e030dc58fc760e4010148e6ff164d2f44441490280ef1e97a542375e41058e", size = 42908549, upload_time = "2025-04-27T12:30:29.551Z" },
+ { url = "https://files.pythonhosted.org/packages/31/a9/dfb999c2fc6911201dcbf348247f9cc382a8990f9ab45c12eabfd7243a38/pyarrow-20.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6102b4864d77102dbbb72965618e204e550135a940c2534711d5ffa787df2a5a", size = 44557216, upload_time = "2025-04-27T12:30:36.977Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/8e/9adee63dfa3911be2382fb4d92e4b2e7d82610f9d9f668493bebaa2af50f/pyarrow-20.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:96d6a0a37d9c98be08f5ed6a10831d88d52cac7b13f5287f1e0f625a0de8062b", size = 25660496, upload_time = "2025-04-27T12:30:42.809Z" },
]
[[package]]
@@ -4810,11 +4814,11 @@ wheels = [
[[package]]
name = "setuptools"
-version = "79.0.1"
+version = "80.0.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/bb/71/b6365e6325b3290e14957b2c3a804a529968c77a049b2ed40c095f749707/setuptools-79.0.1.tar.gz", hash = "sha256:128ce7b8f33c3079fd1b067ecbb4051a66e8526e7b65f6cec075dfc650ddfa88", size = 1367909, upload_time = "2025-04-23T22:20:59.241Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/44/80/97e25f0f1e4067677806084b7382a6ff9979f3d15119375c475c288db9d7/setuptools-80.0.0.tar.gz", hash = "sha256:c40a5b3729d58dd749c0f08f1a07d134fb8a0a3d7f87dc33e7c5e1f762138650", size = 1354221, upload_time = "2025-04-27T17:21:10.806Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0d/6d/b4752b044bf94cb802d88a888dc7d288baaf77d7910b7dedda74b5ceea0c/setuptools-79.0.1-py3-none-any.whl", hash = "sha256:e147c0549f27767ba362f9da434eab9c5dc0045d5304feb602a0af001089fc51", size = 1256281, upload_time = "2025-04-23T22:20:56.768Z" },
+ { url = "https://files.pythonhosted.org/packages/23/63/5517029d6696ddf2bd378d46f63f479be001c31b462303170a1da57650cb/setuptools-80.0.0-py3-none-any.whl", hash = "sha256:a38f898dcd6e5380f4da4381a87ec90bd0a7eec23d204a5552e80ee3cab6bd27", size = 1240907, upload_time = "2025-04-27T17:21:09.175Z" },
]
[[package]]
@@ -4914,14 +4918,14 @@ sdist = { url = "https://files.pythonhosted.org/packages/c7/d9/401c0a7be089e0282
[[package]]
name = "sympy"
-version = "1.13.3"
+version = "1.14.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mpmath" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/11/8a/5a7fd6284fa8caac23a26c9ddf9c30485a48169344b4bd3b0f02fef1890f/sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9", size = 7533196, upload_time = "2024-09-18T21:54:25.591Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload_time = "2025-04-27T18:05:01.611Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/99/ff/c87e0622b1dadea79d2fb0b25ade9ed98954c9033722eb707053d310d4f3/sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73", size = 6189483, upload_time = "2024-09-18T21:54:23.097Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload_time = "2025-04-27T18:04:59.103Z" },
]
[[package]]