From 9e0d89968dd5a4485ee536a0b9f1cd6e7cee26e4 Mon Sep 17 00:00:00 2001 From: MVL Date: Thu, 6 Aug 2026 19:18:37 -0700 Subject: [PATCH 1/8] Honda Clarity: brake hold fix (#1247) * Nidec Hybrid brake hold fix * intent fix * bump opendbc --------- Co-authored-by: Jason Wen --- opendbc_repo | 2 +- openpilot/selfdrive/car/car_specific.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 4c64e8a95b..063414f63f 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 4c64e8a95b4eadca6a9e631d06a854f52bb9ebfa +Subproject commit 063414f63f14f6fe8a662bac6ca372019ccda418 diff --git a/openpilot/selfdrive/car/car_specific.py b/openpilot/selfdrive/car/car_specific.py index 244a8e3b07..a7fdbd0b44 100644 --- a/openpilot/selfdrive/car/car_specific.py +++ b/openpilot/selfdrive/car/car_specific.py @@ -56,6 +56,9 @@ class CarSpecificEvents: if self.CP.minEnableSpeed > 0 and CS.vEgo < 0.001: events.add(EventName.manualRestart) + if CS.brakeHoldActive and CS.blockPcmEnable: # set by Nidec Hybrid which cannot resume from brakehold + events.add(EventName.belowEngageSpeed) + elif self.CP.brand == 'toyota': # TODO: when we check for unexpected disengagement, check gear not S1, S2, S3 if self.CP.openpilotLongitudinalControl: From 6f2a1d573c0bcc21c0ed94033163f5d766b043b8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 8 Aug 2026 01:27:33 -0400 Subject: [PATCH 2/8] ci: sunnypilot CI test routes (#1899) --- openpilot/selfdrive/car/tests/test_models.py | 4 +- openpilot/sunnypilot/tools/lib/__init__.py | 0 .../tools/lib/sunnypilot_car_segments.py | 21 ++++ .../sunnypilot/tools/upload_ci_routes.py | 68 ++++++++++ openpilot/tools/lib/logreader.py | 4 +- pyproject.toml | 1 + uv.lock | 117 +++++++++++++++++- 7 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 openpilot/sunnypilot/tools/lib/__init__.py create mode 100644 openpilot/sunnypilot/tools/lib/sunnypilot_car_segments.py create mode 100755 openpilot/sunnypilot/tools/upload_ci_routes.py diff --git a/openpilot/selfdrive/car/tests/test_models.py b/openpilot/selfdrive/car/tests/test_models.py index b98890838f..3dc7dd8a7b 100644 --- a/openpilot/selfdrive/car/tests/test_models.py +++ b/openpilot/selfdrive/car/tests/test_models.py @@ -25,6 +25,8 @@ from openpilot.tools.lib.logreader import LogReader, LogsUnavailable, openpilotc from openpilot.tools.lib.file_sources import Source from openpilot.tools.lib.route import SegmentName +from openpilot.sunnypilot.tools.lib.sunnypilot_car_segments import sunnypilot_car_segments_source + SafetyModel = car.CarParams.SafetyModel SteerControlType = structs.CarParams.SteerControlType @@ -132,7 +134,7 @@ class TestCarModelBase(unittest.TestCase): segment_range = f"{cls.test_route.route}/{seg}" try: - sources: list[Source] = [internal_source] if len(INTERNAL_SEG_LIST) else [openpilotci_source, comma_api_source] + sources: list[Source] = [internal_source] if len(INTERNAL_SEG_LIST) else [openpilotci_source, comma_api_source, sunnypilot_car_segments_source] lr = LogReader(segment_range, sources=sources, sort_by_time=True) return cls.get_testing_data_from_logreader(lr) except (LogsUnavailable, AssertionError): diff --git a/openpilot/sunnypilot/tools/lib/__init__.py b/openpilot/sunnypilot/tools/lib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpilot/sunnypilot/tools/lib/sunnypilot_car_segments.py b/openpilot/sunnypilot/tools/lib/sunnypilot_car_segments.py new file mode 100644 index 0000000000..a3a0e576cd --- /dev/null +++ b/openpilot/sunnypilot/tools/lib/sunnypilot_car_segments.py @@ -0,0 +1,21 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" + +import os + +SUNNYPILOT_CAR_SEGMENTS_REPO = os.environ.get("SUNNYPILOT_CAR_SEGMENTS_REPO", + "https://huggingface.co/datasets/sunnypilot/sunnypilotCarSegments") +SUNNYPILOT_CAR_SEGMENTS_BRANCH = os.environ.get("SUNNYPILOT_CAR_SEGMENTS_BRANCH", "main") + + +def get_url(route, segment, file="rlog.zst"): + return f"{SUNNYPILOT_CAR_SEGMENTS_REPO}/resolve/{SUNNYPILOT_CAR_SEGMENTS_BRANCH}/segments/{route.replace('|', '/')}/{segment}/{file}" + + +def sunnypilot_car_segments_source(sr, seg_idxs, fns, /): + from openpilot.tools.lib.file_sources import eval_source + return eval_source({seg: [get_url(sr.route_name, seg, fn) for fn in fns] for seg in seg_idxs}) diff --git a/openpilot/sunnypilot/tools/upload_ci_routes.py b/openpilot/sunnypilot/tools/upload_ci_routes.py new file mode 100755 index 0000000000..3f511a6a72 --- /dev/null +++ b/openpilot/sunnypilot/tools/upload_ci_routes.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" + +import argparse +import os +import tempfile + +import requests +from huggingface_hub import HfApi +from tqdm import tqdm + +from openpilot.tools.lib.route import Route + +REPO_ID = os.environ.get("SUNNYPILOT_CAR_SEGMENTS_REPO_ID", "sunnypilot/sunnypilotCarSegments") + + +def upload_route(route_name: str, dry_run: bool = False) -> None: + route = Route(route_name) + log_paths = route.log_paths() + valid_segments = [(i, url) for i, url in enumerate(log_paths) if url is not None] + + print(f"Route: {route_name}") + print(f"Segments: {len(valid_segments)}/{len(log_paths)}") + + if not valid_segments: + print("No segments found.") + return + + api = HfApi() + + with tempfile.TemporaryDirectory() as tmpdir: + for seg_idx, url in tqdm(valid_segments, desc="Uploading"): + filename = url.split("?")[0].rsplit("/", 1)[-1] + local_path = os.path.join(tmpdir, f"{seg_idx}_{filename}") + resp = requests.get(url, stream=True) + resp.raise_for_status() + with open(local_path, "wb") as f: + for chunk in resp.iter_content(chunk_size=8192): + f.write(chunk) + + repo_path = f"segments/{route_name.replace('|', '/')}/{seg_idx}/{filename}" + + if dry_run: + size_mb = os.path.getsize(local_path) / 1024 / 1024 + print(f" [{seg_idx}] {size_mb:.1f} MB -> {repo_path}") + else: + api.upload_file( + path_or_fileobj=local_path, + path_in_repo=repo_path, + repo_id=REPO_ID, + repo_type="dataset", + ) + + print("Done.") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Upload route rlogs to sunnypilot HuggingFace dataset") + parser.add_argument("route", help="Route ID (e.g. 5beb9b58bd12b691/0000010a--a51155e496)") + parser.add_argument("--dry-run", action="store_true", help="Download and show sizes without uploading") + args = parser.parse_args() + + upload_route(args.route, dry_run=args.dry_run) diff --git a/openpilot/tools/lib/logreader.py b/openpilot/tools/lib/logreader.py index 805e411b53..fbfb28dbe0 100755 --- a/openpilot/tools/lib/logreader.py +++ b/openpilot/tools/lib/logreader.py @@ -22,6 +22,8 @@ from openpilot.tools.lib.file_sources import comma_api_source, internal_source, from openpilot.tools.lib.route import SegmentRange, FileName from openpilot.tools.lib.log_time_series import msgs_to_time_series +from openpilot.sunnypilot.tools.lib.sunnypilot_car_segments import sunnypilot_car_segments_source + LogMessage = type[capnp._DynamicStructReader] LogIterable = Iterable[LogMessage] RawLogIterable = Iterable[bytes] @@ -246,7 +248,7 @@ class LogReader: def __init__(self, identifier: str | list[str], default_mode: ReadMode = ReadMode.RLOG, sources: list[Source] | None = None, sort_by_time=False, only_union_types=False): if sources is None: - sources = [internal_source, comma_api_source, openpilotci_source, comma_car_segments_source] + sources = [internal_source, comma_api_source, openpilotci_source, comma_car_segments_source, sunnypilot_car_segments_source] self.default_mode = default_mode self.sources = sources diff --git a/pyproject.toml b/pyproject.toml index d400ff5678..051eb5ffc6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,6 +75,7 @@ testing = [ ] dev = [ + "huggingface_hub", "matplotlib", ] diff --git a/uv.lock b/uv.lock index a0a951d33d..b88ba23d7c 100644 --- a/uv.lock +++ b/uv.lock @@ -5,6 +5,19 @@ requires-python = ">=3.12.3, <3.13" [manifest] overrides = [{ name = "opendbc", editable = "opendbc_repo" }] +[[package]] +name = "anyio" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, +] + [[package]] name = "attrs" version = "26.1.0" @@ -395,6 +408,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, ] +[[package]] +name = "filelock" +version = "3.32.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/57/3ba6e6cb097f85b855b00163d169f35365f44277df044dcf96d55b8f62a3/filelock-3.32.2.tar.gz", hash = "sha256:c33351e1f49cae33414acbc6d56784e6ecee82514ec90795da1161fc4836b5b8", size = 217172, upload-time = "2026-07-29T22:46:04.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/e8/72f8cef9fdfeffe06213fe8508039396ee48daa0e3259457ed766173bfd6/filelock-3.32.2-py3-none-any.whl", hash = "sha256:87dd94cf281e586d135fa51132b8e3d9a598b316e90377a288663c9321036c82", size = 98830, upload-time = "2026-07-29T22:46:03.52Z" }, +] + [[package]] name = "fonttools" version = "4.63.0" @@ -412,6 +434,88 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, ] +[[package]] +name = "fsspec" +version = "2026.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/78/f34251dadb8f3921264a1d9b8946f5e542014ee2614b285261b4e40e6775/fsspec-2026.7.0.tar.gz", hash = "sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88", size = 317040, upload-time = "2026-07-28T16:34:51.052Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583, upload-time = "2026-07-28T16:34:49.538Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/ab/522a2ab67f27971a9d48ca666d4fca85ef7d5282d142e31fd087e27b1bbe/hf_xet-1.6.0.tar.gz", hash = "sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef", size = 920527, upload-time = "2026-08-03T22:33:13.243Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/50/7afa2c9c787405864fc47a0d1bbc02c62e9101947ed43c1f43899fc7d91d/hf_xet-1.6.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d", size = 4071729, upload-time = "2026-08-03T22:33:00.721Z" }, + { url = "https://files.pythonhosted.org/packages/4b/69/55b8dcf636142ae660fec1869fcac14c4da2e8412e14d6eee1523be77e9f/hf_xet-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a", size = 3876287, upload-time = "2026-08-03T22:33:02.251Z" }, + { url = "https://files.pythonhosted.org/packages/67/4e/a28359bf1c1ecf11eba22123168c138698f7cb576ac678f5a2e16cd5da08/hf_xet-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f", size = 4464663, upload-time = "2026-08-03T22:33:03.802Z" }, + { url = "https://files.pythonhosted.org/packages/9a/69/1f0cbc2fb22ae6082d094f743d1b8945a3f36f6089cb95f42b7ee348cda7/hf_xet-1.6.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7", size = 4262538, upload-time = "2026-08-03T22:33:05.287Z" }, + { url = "https://files.pythonhosted.org/packages/d1/3a/4f4f2301ade26e404462d3336fa11f7958d914cabbabdd6e03c3c5d5658c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb", size = 4460520, upload-time = "2026-08-03T22:33:06.81Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/311725e2a905534dfee2dcb5b08414f249147f1f12252bfc2bd24caa075c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c", size = 4675937, upload-time = "2026-08-03T22:33:08.616Z" }, + { url = "https://files.pythonhosted.org/packages/98/b7/8c59a66d15205024662f1d66968136f13893f96df1ddc5087e2e281fc95f/hf_xet-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:fb4fadde1b2b70bf4c0c14a6dccbe7194b1c28947fefd5bbe3fed9d940676c3b", size = 4033128, upload-time = "2026-08-03T22:33:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/73/63/ca511b6f802f28cf3489b280fe77475bcca8de85e81a6299d7916b5b5555/hf_xet-1.6.0-cp38-abi3-win_arm64.whl", hash = "sha256:3dc3e35441ba395006af5aaacc40ef2e603c51ef46c3530b9156185f00935ea3", size = 3859359, upload-time = "2026-08-03T22:33:11.725Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/9b/ddf3d02a8681f1b9ce52fda03d755dad6b74c4f8172304c4c8d2975450f9/huggingface_hub-1.27.0.tar.gz", hash = "sha256:c1fed40ea82a6b41b477f5243546549b792ae0a93abcea608cff66089bf8f8df", size = 942668, upload-time = "2026-08-07T12:48:05.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/d8/95b735e183957c1f26d94c52977f09d466d55119cbbc1558ea4975e4c216/huggingface_hub-1.27.0-py3-none-any.whl", hash = "sha256:7df6827c2f956c60fbaa64646e979e566db76f619dd0a9729dfb8c5a3eb4f68d", size = 784926, upload-time = "2026-08-07T12:48:02.905Z" }, +] + [[package]] name = "hypothesis" version = "6.47.5" @@ -730,6 +834,7 @@ dependencies = [ [package.optional-dependencies] dev = [ + { name = "huggingface-hub" }, { name = "matplotlib" }, ] docs = [ @@ -788,6 +893,7 @@ requires-dist = [ { name = "comma-deps-zstd" }, { name = "coverage", marker = "extra == 'testing'" }, { name = "cython" }, + { name = "huggingface-hub", marker = "extra == 'dev'" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, { name = "jeepney" }, @@ -1302,7 +1408,7 @@ provides-extras = ["dev"] [[package]] name = "tinygrad" -version = "0.12.0" +version = "0.13.0" source = { editable = "tinygrad_repo" } [package.metadata] @@ -1413,6 +1519,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/fc/6a183e71edde90d0c35c2303f23f7a45b6891d1a2c45daf7b8f869831e19/ty-0.0.56-py3-none-win_arm64.whl", hash = "sha256:57538f273d444a5f1293fa7860e967178afe3917611fc5eff16b64e1204fe0d6", size = 11538780, upload-time = "2026-07-01T16:44:53.8Z" }, ] +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + [[package]] name = "urllib3" version = "2.7.0" From f531952be32993cf46ec1a57b65178f2929d608c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 8 Aug 2026 19:49:06 -0400 Subject: [PATCH 3/8] ci: sunnypilot process replay (#1900) * ci: sunnypilot process replay * ours * temp * Revert "temp" This reverts commit d198cbb32739343f6b8360761f9f85c6ce2d4265. * fixes, hopefully * bump --- .github/workflows/tests.yaml | 9 ++++----- opendbc_repo | 2 +- .../selfdrive/test/process_replay/test_processes.py | 2 +- .../selfdrive/controls/lib/latcontrol_torque_v0.py | 2 +- openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py | 5 +++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b5f941fc76..621aa123c5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -132,7 +132,6 @@ jobs: process_replay: name: process replay - if: false # disable process_replay for forks runs-on: ${{ (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || @@ -169,14 +168,14 @@ jobs: name: diff_report_${{ github.event.number }} path: openpilot/selfdrive/test/process_replay/diff_report.txt - name: Checkout ci-artifacts - if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master' + if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master' uses: actions/checkout@v7 with: - repository: commaai/ci-artifacts + repository: sunnypilot/ci-artifacts ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} path: ${{ github.workspace }}/ci-artifacts - name: Prepare refs - if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master' + if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master' working-directory: ${{ github.workspace }}/ci-artifacts run: | git config user.name "GitHub Actions Bot" @@ -188,7 +187,7 @@ jobs: git add . git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit" - name: Push refs - if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master' + if: github.repository == 'sunnypilot/sunnypilot' && github.ref == 'refs/heads/master' uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 with: timeout_minutes: 2 diff --git a/opendbc_repo b/opendbc_repo index 063414f63f..8b9fd4a653 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 063414f63f14f6fe8a662bac6ca372019ccda418 +Subproject commit 8b9fd4a653ab9d9e4f455f01eb61ef22dbc96988 diff --git a/openpilot/selfdrive/test/process_replay/test_processes.py b/openpilot/selfdrive/test/process_replay/test_processes.py index d9d827add5..1627ab0658 100755 --- a/openpilot/selfdrive/test/process_replay/test_processes.py +++ b/openpilot/selfdrive/test/process_replay/test_processes.py @@ -66,7 +66,7 @@ segments = [ # dashcamOnly makes don't need to be tested until a full port is done excluded_interfaces = ["mock", "body", "psa"] -BASE_URL = "https://raw.githubusercontent.com/commaai/ci-artifacts/refs/heads/process-replay/" +BASE_URL = "https://raw.githubusercontent.com/sunnypilot/ci-artifacts/refs/heads/process-replay/" REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit") EXCLUDED_PROCS = {"modeld", "dmonitoringmodeld"} diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_v0.py b/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_v0.py index 4d9e4492f9..6ddfaea231 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_v0.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_v0.py @@ -82,7 +82,7 @@ class LatControlTorque(LatControl): future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 self.lat_accel_request_buffer.append(future_desired_lateral_accel) gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation - desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay + desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / max(lat_delay, self.dt) measurement = measured_curvature * CS.vEgo ** 2 measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt) diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py b/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py index e66072f86f..740e7092ea 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py @@ -36,12 +36,13 @@ class NeuralNetworkLateralControl(LatControlTorqueExtBase): super().__init__(lac_torque, CP, CP_SP, CI) self.params = Params() self.enabled = self.params.get_bool("NeuralNetworkLateralControl") - self.has_nn_model = CP_SP.neuralNetworkLateralControl.model.path != MOCK_MODEL_PATH + model_path = CP_SP.neuralNetworkLateralControl.model.path + self.has_nn_model = model_path not in (MOCK_MODEL_PATH, '') # NN model takes current v_ego, lateral_accel, lat accel/jerk error, roll, and past/future/planned data # of lat accel and roll # Past value is computed using previous desired lat accel and observed roll - self.model = NNTorqueModel(CP_SP.neuralNetworkLateralControl.model.path) + self.model = NNTorqueModel(model_path) if self.has_nn_model else None self.pitch = FirstOrderFilter(0.0, 0.5, 0.01) self.pitch_last = 0.0 From 9e32dee2814fd2c61c62e3988f0dc16f356b8b6f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 9 Aug 2026 03:47:59 -0400 Subject: [PATCH 4/8] ci: fix process replay with missing sunnypilot service ignores (#1902) --- openpilot/selfdrive/controls/plannerd.py | 2 +- openpilot/selfdrive/selfdrived/selfdrived.py | 2 +- openpilot/selfdrive/test/process_replay/process_replay.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpilot/selfdrive/controls/plannerd.py b/openpilot/selfdrive/controls/plannerd.py index d80b69ad19..0af341b121 100755 --- a/openpilot/selfdrive/controls/plannerd.py +++ b/openpilot/selfdrive/controls/plannerd.py @@ -23,7 +23,7 @@ def main(): cloudlog.info("plannerd got CarParamsSP") gps_location_service = get_gps_location_service(params) - ignore_services = ["liveMapDataSP", gps_location_service] + ignore_services = ["liveMapDataSP", "carStateSP", "selfdriveStateSP", gps_location_service] ldw = LaneDepartureWarning() longitudinal_planner = LongitudinalPlanner(CP, CP_SP) diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index cfad1433db..2bc0574e81 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -93,7 +93,7 @@ class SelfdriveD(CruiseHelper): # TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches self.car_state_sock = messaging.sub_sock('carState', timeout=20) - ignore = self.sensor_packets + self.gps_packets + ['alertDebug', 'lateralManeuverPlan'] + ['modelDataV2SP'] + ignore = self.sensor_packets + self.gps_packets + ['alertDebug', 'lateralManeuverPlan'] + ['modelDataV2SP', 'longitudinalPlanSP'] if SIMULATION: ignore += ['driverCameraState', 'managerState'] if REPLAY: diff --git a/openpilot/selfdrive/test/process_replay/process_replay.py b/openpilot/selfdrive/test/process_replay/process_replay.py index fc3463b376..4834cc44e6 100755 --- a/openpilot/selfdrive/test/process_replay/process_replay.py +++ b/openpilot/selfdrive/test/process_replay/process_replay.py @@ -500,7 +500,7 @@ CONFIGS = [ ), ProcessConfig( proc_name="dmonitoringd", - pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "selfdriveState"], + pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "selfdriveState", "carControl"], subs=["driverMonitoringState"], ignore=["logMonoTime"], should_recv_callback=MessageBasedRcvCallback("driverStateV2"), @@ -511,7 +511,7 @@ CONFIGS = [ pubs=[ "cameraOdometry", "accelerometer", "gyroscope", "liveCalibration", "carState" ], - subs=["liveLocationKalman", "livePose"], + subs=["livePose"], ignore=["logMonoTime"], should_recv_callback=MessageBasedRcvCallback("cameraOdometry"), tolerance=NUMPY_TOLERANCE, From fc4699a74783a2f80094f4b890a106c713f7fb96 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 9 Aug 2026 15:19:35 -0400 Subject: [PATCH 5/8] controls: abstract update_output_torque to ExtBase (#1903) --- .../selfdrive/controls/lib/latcontrol_torque_ext_base.py | 7 +++++++ openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext_base.py b/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext_base.py index df773889a9..31ac615db8 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext_base.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext_base.py @@ -132,3 +132,10 @@ class LatControlTorqueExtBase: self.lat_accel_friction_factor = 1.0 self.lateral_jerk_setpoint = self.lat_jerk_friction_factor * self.lookahead_lateral_jerk self.lateral_jerk_measurement = self.lat_jerk_friction_factor * self.actual_lateral_jerk + + def update_output_torque(self, CS): + freeze_integrator = self._steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 + self._output_torque = self._pid.update(self._pid_log.error, + feedforward=self._ff, + speed=CS.vEgo, + freeze_integrator=freeze_integrator) diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py b/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py index 740e7092ea..73c5b526b2 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py @@ -85,13 +85,6 @@ class NeuralNetworkLateralControl(LatControlTorqueExtBase): self._ff += get_friction_in_torque_space(self._desired_lateral_accel - self._actual_lateral_accel, self._lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) - def update_output_torque(self, CS): - freeze_integrator = self._steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 - self._output_torque = self._pid.update(self._pid_log.error, - feedforward=self._ff, - speed=CS.vEgo, - freeze_integrator=freeze_integrator) - def update_neural_network_feedforward(self, CS, params, calibrated_pose) -> None: if not self._nnlc_enabled: return From 91a53aa1610891070cf2877c84f00c2c2965ec31 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 9 Aug 2026 15:56:33 -0400 Subject: [PATCH 6/8] Controls: Lateral Jerk Torque Controller (#693) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * init * more init * keep it alive * fixes * more fixes * more fix * new submodule for nn data * bump submodule * update path to submodule * spacing??? * update submodule path * update submodule path * bump * dump * bump * introduce params * Add Neural Network Lateral Control toggle to developer panel This introduces a new toggle for enabling Neural Network Lateral Control (NNLC), providing detailed descriptions of its functionality and compatibility. It includes UI integration, car compatibility checks, and feedback links for unsupported vehicles. * decouple even more * static * codespell * remove debug * in structs * fix import * convert to capnp * fixes * debug * only initialize if NNLC is enabled or allow to enable * oops * fix initialization * only allow engage if nnlc is off * fix toggle param * fix tests * lint * fix more test * capnp test * try this out * validate if it's not None * make it 33 to match * align * share the same friction input calculation * return stock values if not enabled * unused * split base and child * space * rename * NeuralNetworkFeedForwardModel * less * just use file name * try this * more explicit * rename * move it * child class for additional controllers * rename * time to split out custom lateral acceleration * move around * space * fix * TODO-SP * TODO-SP * update regardless, it's an extension now * update name and expose toggle * ui: sunnypilot Panel -> Steering Panel * Update selfdrive/ui/sunnypilot/qt/offroad/settings/lateral_panel.h * merge * move to steering panel * no need for this * live params in a thread * no live for now * new structs * more ui * more flexible * more ui * no longer needed * another ui * cereal changes * bump opendbc * simplify checks * all in one place * just Enhanced Lat Accel only * no submodule for this * Enhanced Lateral Acceleration: fix bugs, restore NNLC, add UI/schema Fix 4 bugs in latcontrol_torque_lat_accel.py: missing CI param, wrong method name (torque_from_lateral_accel → torque_from_lateral_accel_in_torque_space), self.enabled collision with NNLC, missing output torque recomputation. Restore NNLC + ExtOverride inheritance chain with Enhanced slotted between ExtBase and NNLC. Add mutual exclusion constraints, Python UI toggle, sunnylink schema entries, and controller init/update tests. * fix lint: remove unused default params list, add ty ignore, fix test fixture * use the og name * rename * need gates * TODOs * wrong * gate them all on init --------- Co-authored-by: DevTekVE --- openpilot/common/params_keys.h | 1 + .../sunnypilot/layouts/settings/steering.py | 3 +- .../steering_sub_layouts/torque_settings.py | 10 ++ openpilot/selfdrive/ui/sunnypilot/ui_state.py | 6 + .../sunnypilot/selfdrive/car/interfaces.py | 6 + .../controls/lib/latcontrol_torque_ext.py | 1 + .../lib/latcontrol_torque_jerk_aware.py | 45 ++++++++ .../selfdrive/controls/lib/nnlc/nnlc.py | 6 +- .../lib/tests/test_latcontrol_torque_ext.py | 108 ++++++++++++++++++ .../sunnypilot/sunnylink/settings_ui.json | 31 +++++ .../settings_ui_src/pages/models.yaml | 3 + .../settings_ui_src/pages/steering.yaml | 15 +++ .../sunnylink/tests/test_settings_schema.py | 10 +- 13 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_jerk_aware.py create mode 100644 openpilot/sunnypilot/selfdrive/controls/lib/tests/test_latcontrol_torque_ext.py diff --git a/openpilot/common/params_keys.h b/openpilot/common/params_keys.h index 279d36abfd..f0921b6723 100644 --- a/openpilot/common/params_keys.h +++ b/openpilot/common/params_keys.h @@ -273,6 +273,7 @@ inline static std::unordered_map keys = { // Torque lateral control custom params {"CustomTorqueParams", {PERSISTENT | BACKUP , BOOL}}, {"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}}, + {"LateralJerkTorqueController", {PERSISTENT | BACKUP, BOOL, "0"}}, {"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}}, {"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}}, {"TorqueControlTune", {PERSISTENT | BACKUP, FLOAT, "0.0"}}, diff --git a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/steering.py b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/steering.py index 15cb6a15e0..28d9236361 100644 --- a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/steering.py +++ b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/steering.py @@ -139,7 +139,8 @@ class SteeringLayout(Widget): self._nnlc_toggle.action_item.set_state(False) enforce_torque_enabled = False nnlc_enabled = False - self._nnlc_toggle.action_item.set_enabled(ui_state.is_offroad() and torque_allowed and not enforce_torque_enabled) + jerk_aware_enabled = ui_state.params.get_bool("LateralJerkTorqueController") + self._nnlc_toggle.action_item.set_enabled(ui_state.is_offroad() and torque_allowed and not enforce_torque_enabled and not jerk_aware_enabled) self._torque_control_toggle.action_item.set_enabled(ui_state.is_offroad() and torque_allowed and not nnlc_enabled) self._torque_customization_button.action_item.set_enabled(self._torque_control_toggle.action_item.get_state()) diff --git a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/torque_settings.py b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/torque_settings.py index f3c4419e45..6dae8308cd 100644 --- a/openpilot/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/torque_settings.py +++ b/openpilot/selfdrive/ui/sunnypilot/layouts/settings/steering_sub_layouts/torque_settings.py @@ -40,6 +40,13 @@ class TorqueSettingsLayout(Widget): self.cached_torque_versions = json.load(f) def _initialize_items(self): + self._jerk_aware_toggle = toggle_item_sp( + param="LateralJerkTorqueController", + title=lambda: tr("Lateral Jerk Torque Controller"), + description=lambda: tr("Looks ahead at planned steering to reduce sudden corrections, so the wheel moves " + + "more smoothly through turns. Works with Self-Tune and custom tuning. " + + "Thanks to @twilsonco for the implementation."), + ) self._torque_control_versions = ListItemSP( title=tr("Torque Control Tune Version"), description="Select the version of Torque Control Tune to use.", @@ -95,6 +102,7 @@ class TorqueSettingsLayout(Widget): ) items = [ + self._jerk_aware_toggle, self._torque_control_versions, self._self_tune_toggle, self._relaxed_tune_toggle, @@ -107,6 +115,8 @@ class TorqueSettingsLayout(Widget): def _update_state(self): super()._update_state() + nnlc_enabled = ui_state.params.get_bool("NeuralNetworkLateralControl") + self._jerk_aware_toggle.action_item.set_enabled(ui_state.is_offroad() and not nnlc_enabled) if not ui_state.params.get_bool("LiveTorqueParamsToggle"): ui_state.params.remove("LiveTorqueParamsRelaxedToggle") self._relaxed_tune_toggle.action_item.set_state(False) diff --git a/openpilot/selfdrive/ui/sunnypilot/ui_state.py b/openpilot/selfdrive/ui/sunnypilot/ui_state.py index c2729bbd91..4828f37103 100644 --- a/openpilot/selfdrive/ui/sunnypilot/ui_state.py +++ b/openpilot/selfdrive/ui/sunnypilot/ui_state.py @@ -184,10 +184,15 @@ class UIStateSP: self.params.put_bool("EnforceTorqueControl", False, block=True) self.params.put_bool("NeuralNetworkLateralControl", False, block=True) + if self.params.get_bool("LateralJerkTorqueController") and self.params.get_bool("NeuralNetworkLateralControl"): + self.params.put_bool("LateralJerkTorqueController", False, block=True) + self.params.put_bool("NeuralNetworkLateralControl", False, block=True) + # Angle steering: no torque-based lateral controls if CP.steerControlType == car.CarParams.SteerControlType.angle: self.params.remove("EnforceTorqueControl") self.params.remove("NeuralNetworkLateralControl") + self.params.remove("LateralJerkTorqueController") # Alpha longitudinal: clear if not available if not CP.alphaLongitudinalAvailable: @@ -200,6 +205,7 @@ class UIStateSP: # No CarParams: clear all car-dependent params as safety default self.params.remove("EnforceTorqueControl") self.params.remove("NeuralNetworkLateralControl") + self.params.remove("LateralJerkTorqueController") self.params.remove("AlphaLongitudinalEnabled") # No longitudinal control: no experimental mode or DEC diff --git a/openpilot/sunnypilot/selfdrive/car/interfaces.py b/openpilot/sunnypilot/selfdrive/car/interfaces.py index ed5b71d4b1..ecabc53b57 100644 --- a/openpilot/sunnypilot/selfdrive/car/interfaces.py +++ b/openpilot/sunnypilot/selfdrive/car/interfaces.py @@ -73,10 +73,16 @@ def _cleanup_unsupported_params(CP: structs.CarParams, CP_SP: structs.CarParamsS if params is None: params = Params() + if params.get_bool("LateralJerkTorqueController") and params.get_bool("NeuralNetworkLateralControl"): + cloudlog.warning("LateralJerkTorqueController and NeuralNetworkLateralControl both enabled, disabling both") + params.put_bool("LateralJerkTorqueController", False, block=True) + params.put_bool("NeuralNetworkLateralControl", False, block=True) + if CP.steerControlType == structs.CarParams.SteerControlType.angle: cloudlog.warning("SteerControlType is angle, cleaning up params") params.remove("NeuralNetworkLateralControl") params.remove("EnforceTorqueControl") + params.remove("LateralJerkTorqueController") if not CP_SP.intelligentCruiseButtonManagementAvailable or CP.openpilotLongitudinalControl: cloudlog.warning("ICBM not available or openpilot Longitudinal Control enabled, cleaning up params") diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext.py b/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext.py index 39525b3b8e..50add19cd2 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext.py @@ -33,6 +33,7 @@ class LatControlTorqueExt(NeuralNetworkLateralControl, LatControlTorqueExtOverri self._output_torque = output_torque self.update_calculations(CS, VM, desired_lateral_accel) + self.update_jerk_aware_torque_control(CS, roll_compensation, gravity_adjusted_lateral_accel) self.update_neural_network_feedforward(CS, params, calibrated_pose) return self._pid_log, self._output_torque diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_jerk_aware.py b/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_jerk_aware.py new file mode 100644 index 0000000000..8d780ed4cc --- /dev/null +++ b/openpilot/sunnypilot/selfdrive/controls/lib/latcontrol_torque_jerk_aware.py @@ -0,0 +1,45 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +from opendbc.car.lateral import FRICTION_THRESHOLD +from opendbc.sunnypilot.car.interfaces import LatControlInputs +from opendbc.sunnypilot.car.lateral_ext import get_friction as get_friction_in_torque_space +from openpilot.common.params import Params + +from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_ext_base import LatControlTorqueExtBase + + +class LatControlTorqueJerkAware(LatControlTorqueExtBase): + def __init__(self, lac_torque, CP, CP_SP, CI): + super().__init__(lac_torque, CP, CP_SP, CI) + self.params = Params() + self._jerk_aware_enabled = self.params.get_bool("LateralJerkTorqueController") + + def update_limits(self): + if not self._jerk_aware_enabled: + return + self._pid.set_limits(self.lac_torque.steer_max, -self.lac_torque.steer_max) + + def update_jerk_aware_torque_control(self, CS, roll_compensation, gravity_adjusted_lateral_accel): + if not self._jerk_aware_enabled: + return + + torque_from_setpoint = self.torque_from_lateral_accel_in_torque_space( + LatControlInputs(self._setpoint, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params, gravity_adjusted=False + ) + torque_from_measurement = self.torque_from_lateral_accel_in_torque_space( + LatControlInputs(self._measurement, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params, gravity_adjusted=False + ) + + self._pid_log.error = float(torque_from_setpoint - torque_from_measurement) # ty: ignore[invalid-assignment] + self._ff = self.torque_from_lateral_accel_in_torque_space( + LatControlInputs(gravity_adjusted_lateral_accel, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params, gravity_adjusted=True + ) + + friction_input = self.update_friction_input(self._desired_lateral_accel, self._actual_lateral_accel) + self._ff += get_friction_in_torque_space(friction_input, self._lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) + + self.update_output_torque(CS) diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py b/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py index 73c5b526b2..9684c86688 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py @@ -14,7 +14,8 @@ from opendbc.sunnypilot.car.lateral_ext import get_friction as get_friction_in_t from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.selfdrive.modeld.constants import ModelConstants -from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_ext_base import LatControlTorqueExtBase, sign +from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_ext_base import sign +from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_jerk_aware import LatControlTorqueJerkAware from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.helpers import MOCK_MODEL_PATH from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.model import NNTorqueModel @@ -31,7 +32,7 @@ def roll_pitch_adjust(roll, pitch): return roll * math.cos(pitch) -class NeuralNetworkLateralControl(LatControlTorqueExtBase): +class NeuralNetworkLateralControl(LatControlTorqueJerkAware): def __init__(self, lac_torque, CP, CP_SP, CI): super().__init__(lac_torque, CP, CP_SP, CI) self.params = Params() @@ -65,6 +66,7 @@ class NeuralNetworkLateralControl(LatControlTorqueExtBase): return self.enabled and self.model_valid and self.has_nn_model def update_limits(self): + super().update_limits() if not self._nnlc_enabled: return diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_latcontrol_torque_ext.py b/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_latcontrol_torque_ext.py new file mode 100644 index 0000000000..2b47977514 --- /dev/null +++ b/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_latcontrol_torque_ext.py @@ -0,0 +1,108 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import numpy as np + +from openpilot.cereal import log, messaging +from opendbc.car.structs import car +from opendbc.car.car_helpers import interfaces +from opendbc.car.honda.values import CAR as HONDA +from opendbc.car.vehicle_model import VehicleModel +from openpilot.common.params import Params +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.car.helpers import convert_to_capnp +from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque +from openpilot.selfdrive.locationd.helpers import Pose +from openpilot.common.mock.generators import generate_livePose +from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces +from openpilot.selfdrive.modeld.constants import ModelConstants + + +def _make_controller(enhanced=False, nnlc=False): + params = Params() + params.put_bool("EnforceTorqueControl", True, block=True) + params.put_bool("LateralJerkTorqueController", enhanced, block=True) + params.put_bool("NeuralNetworkLateralControl", nnlc, block=True) + + car_name = HONDA.HONDA_CIVIC + CarInterface = interfaces[car_name] + CP = CarInterface.get_non_essential_params(car_name) + CP_SP = CarInterface.get_non_essential_params_sp(CP, car_name) + CI = CarInterface(CP, CP_SP) + sunnypilot_interfaces.setup_interfaces(CI, params) + CP_SP = convert_to_capnp(CP_SP) + VM = VehicleModel(CP) + controller = LatControlTorque(CP.as_reader(), CP_SP.as_reader(), CI, DT_CTRL) + return controller, VM, CP + + +def _make_model_v2(): + model = messaging.new_message('modelV2') + position = log.XYZTData.new_message() + position.x = [float(x) for x in 30.0 * np.array(ModelConstants.T_IDXS)] + model.modelV2.position = position + orientation = log.XYZTData.new_message() + orientation.x = [0.0 for _ in ModelConstants.T_IDXS] + orientation.y = [0.0 for _ in ModelConstants.T_IDXS] + model.modelV2.orientation = orientation + velocity = log.XYZTData.new_message() + velocity.x = [30.0 for _ in ModelConstants.T_IDXS] + model.modelV2.velocity = velocity + acceleration = log.XYZTData.new_message() + acceleration.x = [0.0 for _ in ModelConstants.T_IDXS] + acceleration.y = [0.0 for _ in ModelConstants.T_IDXS] + model.modelV2.acceleration = acceleration + return model + + +def _run_update(controller, VM): + CS = car.CarState.new_message() + CS.vEgo = 30 + CS.steeringPressed = False + lp = generate_livePose() + pose = Pose.from_live_pose(lp.livePose) + params = log.LiveParametersData.new_message() + model_v2 = _make_model_v2().modelV2 + controller.extension.update_model_v2(model_v2) + controller.extension.update_lateral_lag(0.2) + return controller.update(True, CS, VM, params, False, 0.5, pose, False, 0.2) + + +class TestLatControlTorqueExt: + def test_init_enhanced_only(self): + controller, VM, _ = _make_controller(enhanced=True, nnlc=False) + assert controller.extension._jerk_aware_enabled + assert not controller.extension.enabled # NNLC disabled + + def test_init_nnlc_only(self): + controller, VM, _ = _make_controller(enhanced=False, nnlc=True) + assert not controller.extension._jerk_aware_enabled + assert controller.extension.enabled + + def test_init_neither(self): + controller, VM, _ = _make_controller(enhanced=False, nnlc=False) + assert not controller.extension._jerk_aware_enabled + assert not controller.extension.enabled + + def test_init_both_no_crash(self): + controller, VM, _ = _make_controller(enhanced=True, nnlc=True) + assert not controller.extension._jerk_aware_enabled + assert not controller.extension.enabled + + def test_update_enhanced_only(self): + controller, VM, _ = _make_controller(enhanced=True, nnlc=False) + output_torque, _, pid_log = _run_update(controller, VM) + assert pid_log.active + + def test_update_neither(self): + controller, VM, _ = _make_controller(enhanced=False, nnlc=False) + output_torque, _, pid_log = _run_update(controller, VM) + assert pid_log.active + + def test_update_both_no_crash(self): + controller, VM, _ = _make_controller(enhanced=True, nnlc=True) + output_torque, _, pid_log = _run_update(controller, VM) + assert pid_log.active diff --git a/openpilot/sunnypilot/sunnylink/settings_ui.json b/openpilot/sunnypilot/sunnylink/settings_ui.json index cd5f0b118f..1e6422ac84 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui.json +++ b/openpilot/sunnypilot/sunnylink/settings_ui.json @@ -323,6 +323,32 @@ "equals": true }, "items": [ + { + "key": "LateralJerkTorqueController", + "widget": "toggle", + "title": "Lateral Jerk Torque Controller", + "description": "Looks ahead at planned steering to reduce sudden corrections, so the wheel moves more smoothly through turns. Works with Self-Tune and custom tuning. Thanks to @twilsonco for the implementation.", + "visibility": [ + { + "type": "not", + "condition": { + "type": "capability", + "field": "steer_control_type", + "equals": "angle" + } + } + ], + "enablement": [ + { + "type": "offroad_only" + }, + { + "type": "param", + "key": "NeuralNetworkLateralControl", + "equals": false + } + ] + }, { "key": "LiveTorqueParamsToggle", "widget": "toggle", @@ -2037,6 +2063,11 @@ "type": "param", "key": "EnforceTorqueControl", "equals": false + }, + { + "type": "param", + "key": "LateralJerkTorqueController", + "equals": false } ] } diff --git a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/models.yaml b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/models.yaml index 4ae1fca88b..bcb8b895b9 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/models.yaml +++ b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/models.yaml @@ -73,6 +73,9 @@ sections: - type: param key: EnforceTorqueControl equals: false + - type: param + key: LateralJerkTorqueController + equals: false - id: camera title: Camera description: Camera position and calibration diff --git a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/steering.yaml b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/steering.yaml index 697c5f4f21..a09796ab7a 100644 --- a/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/steering.yaml +++ b/openpilot/sunnypilot/sunnylink/settings_ui_src/pages/steering.yaml @@ -127,6 +127,21 @@ sections: key: EnforceTorqueControl equals: true items: + - key: LateralJerkTorqueController + widget: toggle + title: Lateral Jerk Torque Controller + description: Looks ahead at planned steering to reduce sudden corrections, so the wheel moves more smoothly through turns. Works with Self-Tune and custom tuning. Thanks to @twilsonco for the implementation. + visibility: + - type: not + condition: + type: capability + field: steer_control_type + equals: angle + enablement: + - $ref: '#/macros/offroad' + - type: param + key: NeuralNetworkLateralControl + equals: false - key: LiveTorqueParamsToggle widget: toggle title: Self-Tune diff --git a/openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py b/openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py index 61cc0131cf..579d72b60b 100644 --- a/openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py +++ b/openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py @@ -257,20 +257,26 @@ class TestKnownPanels: assert "mads_settings" in sub_ids def test_mutual_exclusion_torque_nnlc(self, schema): - """EnforceTorqueControl and NNLC must reference each other in enablement.""" - torque = nnlc = None + """EnforceTorqueControl, EnhancedLatAccel, and NNLC must reference each other in enablement.""" + torque = nnlc = enhanced = None for panel in schema["panels"]: for item in _iter_panel_items(panel): if item["key"] == "EnforceTorqueControl": torque = item elif item["key"] == "NeuralNetworkLateralControl": nnlc = item + elif item["key"] == "LateralJerkTorqueController": + enhanced = item assert torque is not None, "EnforceTorqueControl item missing" assert nnlc is not None, "NeuralNetworkLateralControl item missing" + assert enhanced is not None, "LateralJerkTorqueController item missing" torque_enable_keys = {r.get("key") for r in torque.get("enablement", []) if r.get("type") == "param"} assert "NeuralNetworkLateralControl" in torque_enable_keys nnlc_enable_keys = {r.get("key") for r in nnlc.get("enablement", []) if r.get("type") == "param"} assert "EnforceTorqueControl" in nnlc_enable_keys + assert "LateralJerkTorqueController" in nnlc_enable_keys + enhanced_enable_keys = {r.get("key") for r in enhanced.get("enablement", []) if r.get("type") == "param"} + assert "NeuralNetworkLateralControl" in enhanced_enable_keys class TestKnownVehicleSettings: From 617fcd40849f2c8d4df2ab77edefe06d8d1b1781 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2026 20:22:41 -0400 Subject: [PATCH 7/8] [bot] Update Python packages (#1866) * Update Python packages * no --------- Co-authored-by: github-actions[bot] Co-authored-by: Jason Wen --- docs/CARS.md | 15 ++++++++------- opendbc_repo | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index c52707f4f3..22c35b9a12 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -1,10 +1,10 @@ - + # Supported Cars A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 341 Supported Cars +# 342 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -78,8 +78,8 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|Accord 2018-22|All|openpilot available[1,5](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Accord 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Accord Hybrid 2018-22|All|openpilot available[1,5](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Honda|Accord Hybrid 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Honda|City (Brazil only) 2023|All|openpilot available[1,5](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Accord Hybrid 2023-26|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|City (Brazil only) 2023-25|All|openpilot available[1,5](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic 2019-21|All|openpilot available[1,5](#footnotes)|0 mph|2 mph[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic 2022-24|All|openpilot available[1,5](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -187,7 +187,7 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -230,14 +230,15 @@ A supported vehicle is one that just works when you install a comma device. All |Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Nissan[6](#footnotes)|Altima 2019-24|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Nissan[6](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[6](#footnotes)|Leaf IC 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Nissan[6](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Nissan[6](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|32 mph|1 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ram|2500 2020-24|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ram|3500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Rivian|R1S 2025|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Rivian|R1T 2025|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |SEAT[12](#footnotes)|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |SEAT[12](#footnotes)|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| diff --git a/opendbc_repo b/opendbc_repo index 8b9fd4a653..ae445c9b5e 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 8b9fd4a653ab9d9e4f455f01eb61ef22dbc96988 +Subproject commit ae445c9b5ea18cc66ce3b6d53584db238555dd17 From c6595fd99bfac45ff4ce030b9c9b4428b870b947 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 9 Aug 2026 23:12:51 -0400 Subject: [PATCH 8/8] offroad alerts: replace comma references with sunnypilot community and sunnylink (#1904) --- openpilot/selfdrive/selfdrived/alerts_offroad.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpilot/selfdrive/selfdrived/alerts_offroad.json b/openpilot/selfdrive/selfdrived/alerts_offroad.json index c90497e8c1..8a77bd29a4 100644 --- a/openpilot/selfdrive/selfdrived/alerts_offroad.json +++ b/openpilot/selfdrive/selfdrived/alerts_offroad.json @@ -26,7 +26,7 @@ "severity": 1 }, "Offroad_CarUnrecognized": { - "text": "sunnypilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai.", + "text": "sunnypilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please select your vehicle manually at https://www.sunnylink.ai/. Need help? Visit https://community.sunnypilot.ai/", "severity": 0 }, "Offroad_Recalibration": { @@ -42,7 +42,7 @@ "severity": 0 }, "Offroad_ExcessiveActuation": { - "text": "Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting.", + "text": "Excessive %1 actuation detected on your last drive. Please visit https://community.sunnypilot.ai/ and share your device's Dongle ID for troubleshooting.", "severity": 1, "_comment": "Set extra field to lateral or longitudinal." },