From ebb5cc12f5d8ac46002e3ac35575c9f7c611f6f6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Jul 2026 10:55:26 -0700 Subject: [PATCH 001/170] ci: don't pay the big model tax yet (#38371) * ci: don't pay the big model tax yet * skip big in release --- .github/workflows/docs.yaml | 5 +++++ .github/workflows/model_review.yaml | 7 ++++++- .github/workflows/release.yaml | 5 +++++ .github/workflows/repo-maintenance.yaml | 3 +++ .github/workflows/tests.yaml | 3 +++ tools/release/release_files.py | 3 +++ 6 files changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 2d7da672e7..4be4fac916 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -15,6 +15,11 @@ concurrency: group: docs-tests-ci-run-${{ inputs.run_number }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }} cancel-in-progress: true +env: + GIT_CONFIG_COUNT: 1 + GIT_CONFIG_KEY_0: lfs.fetchexclude + GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx + jobs: docs: name: build docs diff --git a/.github/workflows/model_review.yaml b/.github/workflows/model_review.yaml index 82c732dc3b..d491b6397a 100644 --- a/.github/workflows/model_review.yaml +++ b/.github/workflows/model_review.yaml @@ -7,6 +7,11 @@ on: - 'openpilot/selfdrive/modeld/models/*.onnx' workflow_dispatch: +env: + GIT_CONFIG_COUNT: 1 + GIT_CONFIG_KEY_0: lfs.fetchexclude + GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx + jobs: comment: permissions: @@ -39,4 +44,4 @@ jobs: uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 with: header: model-review - message: ${{ steps.report.outputs.content }} \ No newline at end of file + message: ${{ steps.report.outputs.content }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6916fd5ce8..26b28ab330 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,6 +4,11 @@ on: - cron: '0 9 * * *' workflow_dispatch: +env: + GIT_CONFIG_COUNT: 1 + GIT_CONFIG_KEY_0: lfs.fetchexclude + GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx + jobs: build_masterci: name: build master-ci diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 0421efe6e8..e4ca9b2129 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -9,6 +9,9 @@ on: env: PYTHONPATH: ${{ github.workspace }} + GIT_CONFIG_COUNT: 1 + GIT_CONFIG_KEY_0: lfs.fetchexclude + GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx jobs: package_updates: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 08206ee1e2..d0de0fb680 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -21,6 +21,9 @@ env: CI: 1 PYTHONPATH: ${{ github.workspace }} PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical + GIT_CONFIG_COUNT: 1 + GIT_CONFIG_KEY_0: lfs.fetchexclude + GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx jobs: build_release: diff --git a/tools/release/release_files.py b/tools/release/release_files.py index dd6b16253c..223e3f2c77 100755 --- a/tools/release/release_files.py +++ b/tools/release/release_files.py @@ -12,6 +12,9 @@ blacklist = [ "matlab.*.md", + # skip big model for now + "openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx", + # no LFS or submodules in release ".lfsconfig", ".gitattributes", From 911f07ee8763eef8e509cedd847e20a0d494c6d9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Jul 2026 11:02:34 -0700 Subject: [PATCH 002/170] convert tests to unittest (#38387) --- .github/workflows/tests.yaml | 8 +- conftest.py | 92 +--------- .../cereal/messaging/tests/test_messaging.py | 3 +- .../messaging/tests/test_pub_sub_master.py | 5 +- .../cereal/messaging/tests/test_services.py | 3 +- .../hardware/tici/tests/test_agnos_updater.py | 3 +- .../hardware/tici/tests/test_amplifier.py | 14 +- openpilot/common/parameterized.py | 72 ++++++-- openpilot/common/test.py | 172 ++++++++++++++++++ openpilot/common/tests/test_file_helpers.py | 3 +- openpilot/common/tests/test_markdown.py | 3 +- openpilot/common/tests/test_params.py | 12 +- openpilot/common/tests/test_simple_kalman.py | 3 +- .../transformations/tests/test_coordinates.py | 3 +- .../transformations/tests/test_orientation.py | 10 +- .../selfdrive/car/tests/big_cars_test.sh | 2 +- .../car/tests/test_car_interfaces.py | 3 +- .../selfdrive/car/tests/test_cruise_speed.py | 8 +- openpilot/selfdrive/car/tests/test_docs.py | 3 +- openpilot/selfdrive/car/tests/test_models.py | 12 +- .../controls/tests/test_following_distance.py | 7 +- .../controls/tests/test_latcontrol.py | 3 +- .../tests/test_latcontrol_torque_buffer.py | 3 +- .../selfdrive/controls/tests/test_leads.py | 3 +- .../controls/tests/test_longcontrol.py | 25 +-- .../tests/test_torqued_lat_accel_offset.py | 3 +- .../locationd/test/test_calibrationd.py | 3 +- .../selfdrive/locationd/test/test_lagd.py | 7 +- .../test/test_locationd_scenarios.py | 21 ++- .../selfdrive/locationd/test/test_paramsd.py | 3 +- .../selfdrive/locationd/test/test_torqued.py | 3 +- .../selfdrive/monitoring/test_monitoring.py | 3 +- .../selfdrive/pandad/tests/test_pandad.py | 6 +- .../pandad/tests/test_pandad_loopback.py | 6 +- .../selfdrive/pandad/tests/test_pandad_spi.py | 6 +- .../selfdrived/tests/test_alertmanager.py | 3 +- .../selfdrive/selfdrived/tests/test_alerts.py | 3 +- .../selfdrived/tests/test_state_machine.py | 3 +- openpilot/selfdrive/test/cpp_harness.py | 10 - openpilot/selfdrive/test/helpers.py | 3 +- .../test_longitudinal.py | 3 +- .../test/process_replay/test_fuzzy.py | 3 +- .../test/process_replay/test_regen.py | 5 +- openpilot/selfdrive/test/test_onroad.py | 8 +- openpilot/selfdrive/test/test_power_draw.py | 6 +- .../ui/mici/tests/test_widget_leaks.py | 7 +- .../selfdrive/ui/tests/test_feedbackd.py | 10 +- .../selfdrive/ui/tests/test_raylib_ui.py | 3 +- openpilot/selfdrive/ui/tests/test_soundd.py | 4 +- .../selfdrive/ui/tests/test_translations.py | 13 +- openpilot/system/athena/tests/test_athenad.py | 15 +- .../system/athena/tests/test_athenad_ping.py | 9 +- .../system/athena/tests/test_registration.py | 3 +- openpilot/system/camerad/test/test_camerad.py | 44 +++-- .../hardware/tests/test_fan_controller.py | 15 +- .../hardware/tests/test_power_monitoring.py | 10 +- .../loggerd/tests/loggerd_tests_common.py | 3 +- .../system/loggerd/tests/test_deleter.py | 2 +- .../system/loggerd/tests/test_encoder.py | 6 +- .../system/loggerd/tests/test_loggerd.py | 12 +- .../system/loggerd/tests/test_uploader.py | 2 +- openpilot/system/manager/test/test_manager.py | 7 +- .../system/sensord/tests/test_sensord.py | 6 +- openpilot/system/tests/test_logmessaged.py | 4 +- openpilot/system/ubloxd/tests/test_pigeond.py | 6 +- .../ui/lib/tests/test_handle_state_change.py | 37 ++-- .../webrtc/tests/test_stream_session.py | 3 +- openpilot/test_native.py | 25 +++ .../tools/jotpluggler/test_jotpluggler.py | 3 +- openpilot/tools/lib/tests/test_caching.py | 12 +- .../lib/tests/test_comma_car_segments.py | 7 +- openpilot/tools/lib/tests/test_logreader.py | 39 ++-- .../tools/lib/tests/test_route_library.py | 3 +- .../tools/plotjuggler/test_plotjuggler.py | 7 +- openpilot/tools/sim/tests/conftest.py | 8 - .../tools/sim/tests/test_metadrive_bridge.py | 15 +- openpilot/tools/sim/tests/test_sim_bridge.py | 8 +- pyproject.toml | 19 +- tools/op.sh | 4 +- uv.lock | 53 +----- 80 files changed, 573 insertions(+), 434 deletions(-) create mode 100644 openpilot/common/test.py delete mode 100755 openpilot/selfdrive/test/cpp_harness.py create mode 100644 openpilot/test_native.py delete mode 100644 openpilot/tools/sim/tests/conftest.py diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index d0de0fb680..2a9d1ad35d 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -20,7 +20,7 @@ concurrency: env: CI: 1 PYTHONPATH: ${{ github.workspace }} - PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical + PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical --dist worksteal GIT_CONFIG_COUNT: 1 GIT_CONFIG_KEY_0: lfs.fetchexclude GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx @@ -110,9 +110,9 @@ jobs: env: RAYLIB_BACKEND: headless run: | - # Pre-compile Python bytecode so each pytest worker doesn't need to - $PYTEST --collect-only -m 'not slow' -qq - MAX_EXAMPLES=1 $PYTEST -m 'not slow' + # Pre-compile Python bytecode so each worker doesn't need to. + python -m compileall -q -j 0 openpilot + MAX_EXAMPLES=1 SKIP_SLOW=1 $PYTEST process_replay: name: process replay diff --git a/conftest.py b/conftest.py index 48d42aed86..402540b6bf 100644 --- a/conftest.py +++ b/conftest.py @@ -1,95 +1,15 @@ -import contextlib -import gc +"""Pytest runner configuration for the unittest suite.""" + import os -import pytest -from openpilot.common.prefix import OpenpilotPrefix -from openpilot.system.manager import manager -from openpilot.common.hardware import TICI, HARDWARE - -# these are heavy CI-only tests, invoked explicitly in .github/workflows/tests.yaml +# Heavy CI-only tests are invoked explicitly by their dedicated jobs. collect_ignore = [ "openpilot/selfdrive/test/process_replay/test_processes.py", "openpilot/selfdrive/test/process_replay/test_regen.py", - "openpilot/tools/sim/", ] -def pytest_sessionstart(session): - # TODO: fix tests and enable test order randomization - if session.config.pluginmanager.hasplugin('randomly'): - session.config.option.randomly_reorganize = False - - -@pytest.hookimpl(hookwrapper=True, trylast=True) -def pytest_runtest_call(item): - # ensure we run as a hook after capturemanager's - if item.get_closest_marker("nocapture") is not None: - capmanager = item.config.pluginmanager.getplugin("capturemanager") - with capmanager.global_and_fixture_disabled(): - yield - else: - yield - - -@contextlib.contextmanager -def clean_env(): - starting_env = dict(os.environ) - yield - os.environ.clear() - os.environ.update(starting_env) - - -@pytest.fixture(scope="function", autouse=True) -def openpilot_function_fixture(request): - with clean_env(): - # setup a clean environment for each test - with OpenpilotPrefix(shared_download_cache=request.node.get_closest_marker("shared_download_cache") is not None) as prefix: - prefix = os.environ["OPENPILOT_PREFIX"] - - yield - - # ensure the test doesn't change the prefix - assert "OPENPILOT_PREFIX" in os.environ and prefix == os.environ["OPENPILOT_PREFIX"] - - # cleanup any started processes - manager.manager_cleanup() - - # some processes disable gc for performance, re-enable here - if not gc.isenabled(): - gc.enable() - gc.collect() - -# If you use setUpClass, the environment variables won't be cleared properly, -# so we need to hook both the function and class pytest fixtures -@pytest.fixture(scope="class", autouse=True) -def openpilot_class_fixture(): - with clean_env(): - yield - - -@pytest.fixture(scope="function") -def tici_setup_fixture(request, openpilot_function_fixture): - """Ensure a consistent state for tests on-device. Needs the openpilot function fixture to run first.""" - if 'skip_tici_setup' in request.keywords: - return - HARDWARE.initialize_hardware() - HARDWARE.set_power_save(False) - os.system("pkill -9 -f athena") - - -@pytest.hookimpl(tryfirst=True) -def pytest_collection_modifyitems(config, items): - skipper = pytest.mark.skip(reason="Skipping tici test on PC") - for item in items: - if "tici" in item.keywords: - if not TICI: - item.add_marker(skipper) - else: - item.fixturenames.append('tici_setup_fixture') - - if "xdist_group_class_property" in item.keywords: - class_property_name = item.get_closest_marker('xdist_group_class_property').args[0] - class_property_value = getattr(item.cls, class_property_name) - item.add_marker(pytest.mark.xdist_group(class_property_value)) +def pytest_collection_modifyitems(items): + if os.environ.get("SKIP_SLOW"): + items[:] = [item for item in items if not getattr(getattr(item, "cls", None), "SLOW_TEST", False)] diff --git a/openpilot/cereal/messaging/tests/test_messaging.py b/openpilot/cereal/messaging/tests/test_messaging.py index 92d77f1b35..5e92cc7d04 100644 --- a/openpilot/cereal/messaging/tests/test_messaging.py +++ b/openpilot/cereal/messaging/tests/test_messaging.py @@ -4,6 +4,7 @@ import numbers import random import threading import time +from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized from openpilot.cereal import log @@ -46,7 +47,7 @@ def delayed_send(delay, sock, dat): threading.Timer(delay, send_func).start() -class TestMessaging: +class TestMessaging(OpenpilotTestCase): @parameterized.expand(events) def test_new_message(self, evt): try: diff --git a/openpilot/cereal/messaging/tests/test_pub_sub_master.py b/openpilot/cereal/messaging/tests/test_pub_sub_master.py index 7353764aea..4ac7fdbd75 100644 --- a/openpilot/cereal/messaging/tests/test_pub_sub_master.py +++ b/openpilot/cereal/messaging/tests/test_pub_sub_master.py @@ -3,13 +3,14 @@ import time from typing import cast from collections.abc import Sized +from openpilot.common.test import OpenpilotTestCase import openpilot.cereal.messaging as messaging from openpilot.cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \ random_bytes, random_carstate, assert_carstate from openpilot.cereal.services import SERVICE_LIST -class TestSubMaster: +class TestSubMaster(OpenpilotTestCase): def test_init(self): sm = messaging.SubMaster(events) @@ -107,7 +108,7 @@ class TestSubMaster: assert sm[sock].vEgo == n -class TestPubMaster: +class TestPubMaster(OpenpilotTestCase): def test_init(self): messaging.PubMaster(events) diff --git a/openpilot/cereal/messaging/tests/test_services.py b/openpilot/cereal/messaging/tests/test_services.py index 9ae7c3d840..f4c1b81e4f 100644 --- a/openpilot/cereal/messaging/tests/test_services.py +++ b/openpilot/cereal/messaging/tests/test_services.py @@ -1,13 +1,14 @@ import subprocess import tempfile +from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized import openpilot.cereal.services as services from openpilot.cereal.services import SERVICE_LIST -class TestServices: +class TestServices(OpenpilotTestCase): @parameterized.expand(SERVICE_LIST.keys()) def test_services(self, s): diff --git a/openpilot/common/hardware/tici/tests/test_agnos_updater.py b/openpilot/common/hardware/tici/tests/test_agnos_updater.py index a1bbd363fd..f096426707 100644 --- a/openpilot/common/hardware/tici/tests/test_agnos_updater.py +++ b/openpilot/common/hardware/tici/tests/test_agnos_updater.py @@ -6,7 +6,8 @@ TEST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__))) MANIFEST = os.path.join(TEST_DIR, "../agnos.json") -class TestAgnosUpdater: +from openpilot.common.test import OpenpilotTestCase +class TestAgnosUpdater(OpenpilotTestCase): def test_manifest(self): with open(MANIFEST) as f: diff --git a/openpilot/common/hardware/tici/tests/test_amplifier.py b/openpilot/common/hardware/tici/tests/test_amplifier.py index 3e36ff3a3d..08b8c9be80 100644 --- a/openpilot/common/hardware/tici/tests/test_amplifier.py +++ b/openpilot/common/hardware/tici/tests/test_amplifier.py @@ -1,18 +1,14 @@ -import pytest import time import subprocess from panda import Panda -from openpilot.common.hardware import TICI, HARDWARE +from openpilot.common.test import OpenpilotTestCase +from openpilot.common.hardware import HARDWARE from openpilot.common.hardware.tici.amplifier import Amplifier -class TestAmplifier: - - @classmethod - def setup_class(cls): - if not TICI: - pytest.skip() +class TestAmplifier(OpenpilotTestCase): + TICI_TEST = True def setup_method(self): # clear dmesg @@ -65,4 +61,4 @@ class TestAmplifier: if self._check_for_i2c_errors(True): break else: - pytest.fail("didn't hit any i2c errors") + self.fail("didn't hit any i2c errors") diff --git a/openpilot/common/parameterized.py b/openpilot/common/parameterized.py index f36d649dad..2782fb9a57 100644 --- a/openpilot/common/parameterized.py +++ b/openpilot/common/parameterized.py @@ -1,7 +1,7 @@ import re import sys -import pytest import inspect +import unittest def _to_safe_name(s): @@ -10,22 +10,60 @@ def _to_safe_name(s): class parameterized: @staticmethod - def expand(cases): + def expand(cases, names=None, ids=None, serial=False): cases = list(cases) if not cases: - return lambda func: pytest.mark.skip("no parameterized cases")(func) + return lambda func: unittest.skip("no parameterized cases")(func) - def decorator(func): - params = [p for p in inspect.signature(func).parameters if p != 'self'] - normalized = [c if isinstance(c, tuple) else (c,) for c in cases] - # Infer arg count from first case so extra params (e.g. from @given) are left untouched - expand_params = params[: len(normalized[0])] - if len(expand_params) == 1: - return pytest.mark.parametrize(expand_params[0], [c[0] for c in normalized])(func) - return pytest.mark.parametrize(', '.join(expand_params), normalized)(func) + if serial: + def decorator(func): + normalized = [case if isinstance(case, tuple) else (case,) for case in cases] - return decorator + def wrapper(self): + for case in normalized: + with self.subTest(): + func(self, *case) + + wrapper.__name__ = func.__name__ + wrapper.__doc__ = func.__doc__ + return wrapper + + return decorator + + return lambda func: _Expanded(func, cases, names, ids) + + +class _Expanded: + """Descriptor that turns every parameter case into a real unittest method.""" + + def __init__(self, func, cases, names, ids): + self.func = func + self.cases = [c if isinstance(c, tuple) else (c,) for c in cases] + self.names = names + self.ids = ids + + def __set_name__(self, owner, name): + params = [p for p in inspect.signature(self.func).parameters if p != "self"] + + for index, case in enumerate(self.cases): + label = self.ids(*case) if self.ids is not None else None + method_name = f"{name}_{index}" + (f"_{_to_safe_name(label)}" if label is not None else "") + + def test_method(test_case, current_case=case): + if self.names is None: + self.func(test_case, *current_case) + else: + values = dict(zip(self.names, current_case, strict=True)) + values.update({param: test_case._fixture(param) for param in params if param not in values}) + self.func(test_case, **values) + + test_method.__name__ = method_name + test_method.__doc__ = self.func.__doc__ + setattr(owner, method_name, test_method) + + # The descriptor itself is only a method factory, not a test. + setattr(owner, name, None) def parameterized_class(attrs, input_list=None): @@ -39,16 +77,16 @@ def parameterized_class(attrs, input_list=None): def decorator(cls): globs = sys._getframe(1).f_globals for i, params in enumerate(params_list): - # append sanitized string param values so pytest -k can filter by them + # Append sanitized values so unittest's -k can filter by them. suffix = "_".join(filter(None, (_to_safe_name(v) for v in params.values() if isinstance(v, str)))) name = f"{cls.__name__}_{i}" + (f"_{suffix}" if suffix else "") new_cls = type(name, (cls,), dict(params)) new_cls.__module__ = cls.__module__ - new_cls.__test__ = True # override inherited False so pytest collects this subclass + new_cls.__unittest_skip__ = False globs[name] = new_cls - # Don't collect the un-parametrised base, but return it so outer decorators - # (e.g. @pytest.mark.skip) land on it and propagate to subclasses via MRO. - cls.__test__ = False + # Don't collect the un-parametrised base. + cls.__unittest_skip__ = True + cls.__unittest_skip_why__ = "parameterized base class" return cls return decorator diff --git a/openpilot/common/test.py b/openpilot/common/test.py new file mode 100644 index 0000000000..32d06759c4 --- /dev/null +++ b/openpilot/common/test.py @@ -0,0 +1,172 @@ +import contextlib +import gc +import inspect +import os +import subprocess +import unittest +from unittest import mock + +from openpilot.common.hardware import HARDWARE, TICI +from openpilot.common.prefix import OpenpilotPrefix +from openpilot.system.manager import manager + + +@contextlib.contextmanager +def clean_env(): + starting_env = dict(os.environ) + try: + yield + finally: + os.environ.clear() + os.environ.update(starting_env) + + +class OpenpilotTestCase(unittest.TestCase): + """TestCase with openpilot's per-test isolation and legacy hook support.""" + + TICI_TEST = False + SKIP_TICI_SETUP = False + SHARED_DOWNLOAD_CACHE = False + SLOW_TEST = False + + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + # Hide legacy pytest xunit hook names from pytest. unittest invokes the + # preserved hooks inside the OpenpilotPrefix boundary below. + for name in ("setup_method", "teardown_method"): + hook = cls.__dict__.get(name) + if hook is not None: + setattr(cls, f"openpilot_{name}", hook) + setattr(cls, name, None) + + def _fixture(self, name): + if name == "mocker": + return Mocker(self.addCleanup) + if name == "monkeypatch": + return MonkeyPatch(self.addCleanup) + if name == "subtests": + return SubTests(self) + + fixture = getattr(inspect.getmodule(type(self)), name) + kwargs = {p: self._fixture(p) for p in inspect.signature(fixture).parameters} + value = fixture(**kwargs) + if inspect.isgenerator(value): + generator = value + value = next(generator) + self.addCleanup(lambda: next(generator, None)) + return value + + def _callTestMethod(self, method): + params = [name for name, param in inspect.signature(method).parameters.items() + if param.default is inspect.Parameter.empty] + return method(**{name: self._fixture(name) for name in params}) + + def run(self, result=None): + # This boundary cannot live in setUp/tearDown: existing unittest classes + # are allowed to override those hooks without calling super(). + if ((self.SLOW_TEST and os.environ.get("SKIP_SLOW")) or + (self.TICI_TEST and not TICI) or getattr(type(self), "__unittest_skip__", False)): + return super().run(result) + test_env = clean_env() + test_env.__enter__() + prefix = OpenpilotPrefix(shared_download_cache=self.SHARED_DOWNLOAD_CACHE) + prefix.__enter__() + try: + return super().run(result) + finally: + prefix.__exit__(None, None, None) + manager.manager_cleanup() + if not gc.isenabled(): + gc.enable() + gc.collect() + test_env.__exit__(None, None, None) + + @classmethod + def setUpClass(cls): + super().setUpClass() + if cls.SLOW_TEST and os.environ.get("SKIP_SLOW"): + raise unittest.SkipTest("slow test") + if cls.TICI_TEST and not TICI: + raise unittest.SkipTest("Skipping tici test on PC") + cls._class_env = clean_env() + cls._class_env.__enter__() + setup_class = getattr(cls, "setup_class", None) + if setup_class is not None: + setup_class() + + @classmethod + def tearDownClass(cls): + try: + teardown_class = getattr(cls, "teardown_class", None) + if teardown_class is not None: + teardown_class() + finally: + cls._class_env.__exit__(None, None, None) + super().tearDownClass() + + def setUp(self): + super().setUp() + if self.TICI_TEST and not TICI: + self.skipTest("Skipping tici test on PC") + + if self.TICI_TEST and not self.SKIP_TICI_SETUP: + HARDWARE.initialize_hardware() + HARDWARE.set_power_save(False) + subprocess.run(["pkill", "-9", "-f", "athena"], check=False) + + setup_method = getattr(self, "openpilot_setup_method", None) + if setup_method is not None: + setup_method() + + def tearDown(self): + try: + teardown_method = getattr(self, "openpilot_teardown_method", None) + if teardown_method is not None: + teardown_method() + finally: + super().tearDown() + + +class Mocker: + Mock = mock.Mock + MagicMock = mock.MagicMock + call = mock.call + ANY = mock.ANY + + def __init__(self, add_cleanup): + self._add_cleanup = add_cleanup + self.patch = Patch(self._start) + + def _start(self, patcher): + value = patcher.start() + self._add_cleanup(patcher.stop) + return value + + +class Patch: + def __init__(self, start): + self._start = start + + def __call__(self, *args, **kwargs): + return self._start(mock.patch(*args, **kwargs)) + + def object(self, *args, **kwargs): + return self._start(mock.patch.object(*args, **kwargs)) + + +class MonkeyPatch: + def __init__(self, add_cleanup): + self._add_cleanup = add_cleanup + + def setattr(self, target, name, value): + patcher = mock.patch.object(target, name, value) + patcher.start() + self._add_cleanup(patcher.stop) + + +class SubTests: + def __init__(self, test_case): + self._test_case = test_case + + def test(self, label=None, **kwargs): + return self._test_case.subTest(**kwargs) if label is None else self._test_case.subTest(label, **kwargs) diff --git a/openpilot/common/tests/test_file_helpers.py b/openpilot/common/tests/test_file_helpers.py index c2b880f873..09b6990ed6 100644 --- a/openpilot/common/tests/test_file_helpers.py +++ b/openpilot/common/tests/test_file_helpers.py @@ -1,10 +1,11 @@ import os from uuid import uuid4 +from openpilot.common.test import OpenpilotTestCase from openpilot.common.utils import atomic_write -class TestFileHelpers: +class TestFileHelpers(OpenpilotTestCase): def run_atomic_write_func(self, atomic_write_func): path = f"/tmp/tmp{uuid4()}" with atomic_write_func(path) as f: diff --git a/openpilot/common/tests/test_markdown.py b/openpilot/common/tests/test_markdown.py index d3c7e02c69..a0ddc90094 100644 --- a/openpilot/common/tests/test_markdown.py +++ b/openpilot/common/tests/test_markdown.py @@ -1,10 +1,11 @@ import os +from openpilot.common.test import OpenpilotTestCase from openpilot.common.basedir import BASEDIR from openpilot.common.markdown import parse_markdown -class TestMarkdown: +class TestMarkdown(OpenpilotTestCase): def test_all_release_notes(self): with open(os.path.join(BASEDIR, "RELEASES.md")) as f: release_notes = f.read().split("\n\n") diff --git a/openpilot/common/tests/test_params.py b/openpilot/common/tests/test_params.py index bbd411bc37..4fcc5c7f58 100644 --- a/openpilot/common/tests/test_params.py +++ b/openpilot/common/tests/test_params.py @@ -1,13 +1,13 @@ -import pytest import datetime import os import threading import time import uuid +from openpilot.common.test import OpenpilotTestCase from openpilot.common.params import Params, ParamKeyFlag, UnknownKeyName -class TestParams: +class TestParams(OpenpilotTestCase): def setup_method(self): self.params = Params() @@ -50,16 +50,16 @@ class TestParams: assert self.params.get("CarParams", block=True) == b"test" def test_params_unknown_key_fails(self): - with pytest.raises(UnknownKeyName): + with self.assertRaises(UnknownKeyName): self.params.get("swag") - with pytest.raises(UnknownKeyName): + with self.assertRaises(UnknownKeyName): self.params.get_bool("swag") - with pytest.raises(UnknownKeyName): + with self.assertRaises(UnknownKeyName): self.params.put("swag", "abc", block=True) - with pytest.raises(UnknownKeyName): + with self.assertRaises(UnknownKeyName): self.params.put_bool("swag", True, block=True) def test_remove_not_there(self): diff --git a/openpilot/common/tests/test_simple_kalman.py b/openpilot/common/tests/test_simple_kalman.py index e44ac2cc57..ca0f7ece7d 100644 --- a/openpilot/common/tests/test_simple_kalman.py +++ b/openpilot/common/tests/test_simple_kalman.py @@ -1,7 +1,8 @@ +from openpilot.common.test import OpenpilotTestCase from openpilot.common.simple_kalman import KF1D -class TestSimpleKalman: +class TestSimpleKalman(OpenpilotTestCase): def setup_method(self): dt = 0.01 x0_0 = 0.0 diff --git a/openpilot/common/transformations/tests/test_coordinates.py b/openpilot/common/transformations/tests/test_coordinates.py index 0b5d1c36df..febc566a53 100644 --- a/openpilot/common/transformations/tests/test_coordinates.py +++ b/openpilot/common/transformations/tests/test_coordinates.py @@ -1,5 +1,6 @@ import numpy as np +from openpilot.common.test import OpenpilotTestCase import openpilot.common.transformations.coordinates as coord geodetic_positions = np.array([[37.7610403, -122.4778699, 115], @@ -41,7 +42,7 @@ ned_offsets_batch = np.array([[ 53.88103168, 43.83445935, -46.27488057], [ 78.56272609, 18.53100158, -43.25290759]]) -class TestNED: +class TestNED(OpenpilotTestCase): def test_small_distances(self): start_geodetic = np.array([33.8042184, -117.888593, 0.0]) local_coord = coord.LocalCoord.from_geodetic(start_geodetic) diff --git a/openpilot/common/transformations/tests/test_orientation.py b/openpilot/common/transformations/tests/test_orientation.py index 1bf94115c8..7acf16dadf 100644 --- a/openpilot/common/transformations/tests/test_orientation.py +++ b/openpilot/common/transformations/tests/test_orientation.py @@ -1,6 +1,6 @@ import numpy as np -import pytest +from openpilot.common.test import OpenpilotTestCase from openpilot.common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \ rot2quat, quat2rot, \ ned_euler_from_ecef @@ -30,7 +30,7 @@ ned_eulers = np.array([[ 0.46806039, -0.4881889 , 1.65697808], [ 2.50450101, 0.36304151, 0.33136365]]) -class TestOrientation: +class TestOrientation(OpenpilotTestCase): def test_quat_euler(self): for i, eul in enumerate(eulers): np.testing.assert_allclose(quats[i], euler2quat(eul), rtol=1e-7) @@ -62,13 +62,13 @@ class TestOrientation: # np.testing.assert_allclose(ned_eulers, ned_euler_from_ecef(ecef_positions, eulers), rtol=1e-7) def test_inputs(self): - with pytest.raises(ValueError): + with self.assertRaises(ValueError): euler2quat([1, 2]) - with pytest.raises(ValueError): + with self.assertRaises(ValueError): quat2rot([1, 2, 3]) - with pytest.raises(IndexError): + with self.assertRaises(IndexError): rot2quat(np.zeros((2, 2))) def test_euler_rot_consistency(self): diff --git a/openpilot/selfdrive/car/tests/big_cars_test.sh b/openpilot/selfdrive/car/tests/big_cars_test.sh index 456fc698c5..8c0ecee41e 100755 --- a/openpilot/selfdrive/car/tests/big_cars_test.sh +++ b/openpilot/selfdrive/car/tests/big_cars_test.sh @@ -8,4 +8,4 @@ export MAX_EXAMPLES=300 export INTERNAL_SEG_CNT=300 export INTERNAL_SEG_LIST=openpilot/selfdrive/car/tests/test_models_segs.txt -cd openpilot/selfdrive/car/tests && pytest test_models.py test_car_interfaces.py +pytest -n logical --dist worksteal openpilot/selfdrive/car/tests/test_models.py openpilot/selfdrive/car/tests/test_car_interfaces.py diff --git a/openpilot/selfdrive/car/tests/test_car_interfaces.py b/openpilot/selfdrive/car/tests/test_car_interfaces.py index 7ddd73c2d8..1990b25981 100644 --- a/openpilot/selfdrive/car/tests/test_car_interfaces.py +++ b/openpilot/selfdrive/car/tests/test_car_interfaces.py @@ -1,6 +1,7 @@ import os import hypothesis.strategies as st from hypothesis import Phase, given, settings +from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized from opendbc.car.structs import car @@ -18,7 +19,7 @@ from openpilot.selfdrive.test.fuzzy_generation import FuzzyGenerator MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '60')) -class TestCarInterfaces: +class TestCarInterfaces(OpenpilotTestCase): # FIXME: Due to the lists used in carParams, Phase.target is very slow and will cause # many generated examples to overrun when max_examples > ~20, don't use it @parameterized.expand([(car,) for car in sorted(PLATFORMS)] + [MOCK.MOCK]) diff --git a/openpilot/selfdrive/car/tests/test_cruise_speed.py b/openpilot/selfdrive/car/tests/test_cruise_speed.py index 57e89a1117..bf53747df8 100644 --- a/openpilot/selfdrive/car/tests/test_cruise_speed.py +++ b/openpilot/selfdrive/car/tests/test_cruise_speed.py @@ -1,7 +1,7 @@ -import pytest import itertools import numpy as np +from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized_class from openpilot.cereal import log from openpilot.selfdrive.car.cruise import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT @@ -35,18 +35,18 @@ def run_cruise_simulation(cruise, e2e, personality, t_end=20.): [True, False], # e2e log.LongitudinalPersonality.schema.enumerants, # personality [5,35])) # speed -class TestCruiseSpeed: +class TestCruiseSpeed(OpenpilotTestCase): def test_cruise_speed(self): print(f'Testing {self.speed} m/s') cruise_speed = float(self.speed) simulation_steady_state = run_cruise_simulation(cruise_speed, self.e2e, self.personality) - assert simulation_steady_state == pytest.approx(cruise_speed, abs=.01), f'Did not reach {self.speed} m/s' + self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {self.speed} m/s') # TODO: test pcmCruise @parameterized_class(('pcm_cruise',), [(False,)]) -class TestVCruiseHelper: +class TestVCruiseHelper(OpenpilotTestCase): def setup_method(self): self.CP = car.CarParams(pcmCruise=self.pcm_cruise) self.v_cruise_helper = VCruiseHelper(self.CP) diff --git a/openpilot/selfdrive/car/tests/test_docs.py b/openpilot/selfdrive/car/tests/test_docs.py index 8ccbfb5a79..9f5abba336 100644 --- a/openpilot/selfdrive/car/tests/test_docs.py +++ b/openpilot/selfdrive/car/tests/test_docs.py @@ -1,8 +1,9 @@ from opendbc.car.docs import generate_cars_md, get_all_car_docs +from openpilot.common.test import OpenpilotTestCase from openpilot.selfdrive.car.docs import CARS_MD_TEMPLATE -class TestCarDocs: +class TestCarDocs(OpenpilotTestCase): @classmethod def setup_class(cls): cls.all_cars = get_all_car_docs() diff --git a/openpilot/selfdrive/car/tests/test_models.py b/openpilot/selfdrive/car/tests/test_models.py index 5ad31f93ab..e8d9c6cb42 100644 --- a/openpilot/selfdrive/car/tests/test_models.py +++ b/openpilot/selfdrive/car/tests/test_models.py @@ -1,12 +1,12 @@ import time import os -import pytest import random import unittest from collections import defaultdict, Counter import hypothesis.strategies as st from hypothesis import Phase, given, settings from openpilot.common.parameterized import parameterized_class +from openpilot.common.test import OpenpilotTestCase from opendbc.car import DT_CTRL, gen_empty_fingerprint, structs from opendbc.car.can_definitions import CanData from opendbc.car.car_helpers import FRAME_FINGERPRINT, interfaces @@ -66,9 +66,9 @@ def get_test_cases() -> list[tuple[str, CarTestRoute | None]]: return test_cases -@pytest.mark.slow -@pytest.mark.shared_download_cache -class TestCarModelBase(unittest.TestCase): +class TestCarModelBase(OpenpilotTestCase): + SLOW_TEST = True + SHARED_DOWNLOAD_CACHE = True platform: Platform | None = None test_route: CarTestRoute | None = None @@ -302,8 +302,7 @@ class TestCarModelBase(unittest.TestCase): CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(resume=True)) test_car_controller(CC.as_reader()) - # Skip stdout/stderr capture with pytest, causes elevated memory usage - @pytest.mark.nocapture + # Capturing stdout/stderr here causes elevated memory usage. @settings(max_examples=MAX_EXAMPLES, deadline=None, phases=(Phase.reuse, Phase.generate, Phase.shrink)) @given(data=st.data()) @@ -471,7 +470,6 @@ class TestCarModelBase(unittest.TestCase): @parameterized_class(('platform', 'test_route'), get_test_cases()) -@pytest.mark.xdist_group_class_property('test_route') class TestCarModel(TestCarModelBase): pass diff --git a/openpilot/selfdrive/controls/tests/test_following_distance.py b/openpilot/selfdrive/controls/tests/test_following_distance.py index fcabce0387..1914769282 100644 --- a/openpilot/selfdrive/controls/tests/test_following_distance.py +++ b/openpilot/selfdrive/controls/tests/test_following_distance.py @@ -1,5 +1,5 @@ -import pytest import itertools +from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized_class from openpilot.cereal import log @@ -36,11 +36,12 @@ def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False, personalit log.LongitudinalPersonality.standard, log.LongitudinalPersonality.aggressive], [0,10,35])) # speed -class TestFollowingDistance: +class TestFollowingDistance(OpenpilotTestCase): def test_following_distance(self): v_lead = float(self.speed) simulation_steady_state = run_following_distance_simulation(v_lead, e2e=self.e2e, personality=self.personality) correct_steady_state = desired_follow_distance(v_lead, v_lead, get_T_FOLLOW(self.personality)) err_ratio = 0.2 if self.e2e else 0.1 abs_err_margin = 0.5 if v_lead > 0.0 else 1.15 - assert simulation_steady_state == pytest.approx(correct_steady_state, abs=err_ratio * correct_steady_state + abs_err_margin) + self.assertAlmostEqual(simulation_steady_state, correct_steady_state, + delta=err_ratio * correct_steady_state + abs_err_margin) diff --git a/openpilot/selfdrive/controls/tests/test_latcontrol.py b/openpilot/selfdrive/controls/tests/test_latcontrol.py index 8389a1e31a..7f2ab8d6b7 100644 --- a/openpilot/selfdrive/controls/tests/test_latcontrol.py +++ b/openpilot/selfdrive/controls/tests/test_latcontrol.py @@ -1,3 +1,4 @@ +from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized from openpilot.cereal import log @@ -14,7 +15,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle -class TestLatControl: +class TestLatControl(OpenpilotTestCase): @parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque), (NISSAN.NISSAN_LEAF, LatControlAngle), (GM.CHEVROLET_BOLT_EUV, LatControlTorque)]) diff --git a/openpilot/selfdrive/controls/tests/test_latcontrol_torque_buffer.py b/openpilot/selfdrive/controls/tests/test_latcontrol_torque_buffer.py index 9e04316798..65befdec0f 100644 --- a/openpilot/selfdrive/controls/tests/test_latcontrol_torque_buffer.py +++ b/openpilot/selfdrive/controls/tests/test_latcontrol_torque_buffer.py @@ -1,3 +1,4 @@ +from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized from openpilot.cereal import log @@ -16,7 +17,7 @@ def get_controller(car_name): controller = LatControlTorque(CP.as_reader(), CI, DT_CTRL) return controller, VM -class TestLatControlTorqueBuffer: +class TestLatControlTorqueBuffer(OpenpilotTestCase): @parameterized.expand([(TOYOTA.TOYOTA_COROLLA_TSS2,)]) def test_request_buffer_consistency(self, car_name): diff --git a/openpilot/selfdrive/controls/tests/test_leads.py b/openpilot/selfdrive/controls/tests/test_leads.py index 1956bb34ec..a3e226cb4b 100644 --- a/openpilot/selfdrive/controls/tests/test_leads.py +++ b/openpilot/selfdrive/controls/tests/test_leads.py @@ -1,10 +1,11 @@ +from openpilot.common.test import OpenpilotTestCase import openpilot.cereal.messaging as messaging from opendbc.car.toyota.values import CAR as TOYOTA from openpilot.selfdrive.test.process_replay import replay_process_with_name -class TestLeads: +class TestLeads(OpenpilotTestCase): def test_radar_fault(self): # if there's no radar-related can traffic, radard should either not respond or respond with an error # this is tightly coupled with underlying car radar_interface implementation, but it's a good sanity check diff --git a/openpilot/selfdrive/controls/tests/test_longcontrol.py b/openpilot/selfdrive/controls/tests/test_longcontrol.py index 2c52132068..7d670d23df 100644 --- a/openpilot/selfdrive/controls/tests/test_longcontrol.py +++ b/openpilot/selfdrive/controls/tests/test_longcontrol.py @@ -1,7 +1,8 @@ +from openpilot.common.test import OpenpilotTestCase from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans -class TestLongControlStateTransition: +class TestLongControlStateTransition(OpenpilotTestCase): def test_stay_stopped(self): active = True @@ -23,18 +24,18 @@ class TestLongControlStateTransition: should_stop=False, brake_pressed=False, cruise_standstill=False) assert next_state == LongCtrlState.off -def test_engage(): - active = True - current_state = LongCtrlState.off - next_state = long_control_state_trans(active, current_state, + def test_engage(self): + active = True + current_state = LongCtrlState.off + next_state = long_control_state_trans(active, current_state, should_stop=True, brake_pressed=False, cruise_standstill=False) - assert next_state == LongCtrlState.stopping - next_state = long_control_state_trans(active, current_state, + assert next_state == LongCtrlState.stopping + next_state = long_control_state_trans(active, current_state, should_stop=False, brake_pressed=True, cruise_standstill=False) - assert next_state == LongCtrlState.stopping - next_state = long_control_state_trans(active, current_state, + assert next_state == LongCtrlState.stopping + next_state = long_control_state_trans(active, current_state, should_stop=False, brake_pressed=False, cruise_standstill=True) - assert next_state == LongCtrlState.stopping - next_state = long_control_state_trans(active, current_state, + assert next_state == LongCtrlState.stopping + next_state = long_control_state_trans(active, current_state, should_stop=False, brake_pressed=False, cruise_standstill=False) - assert next_state == LongCtrlState.pid + assert next_state == LongCtrlState.pid diff --git a/openpilot/selfdrive/controls/tests/test_torqued_lat_accel_offset.py b/openpilot/selfdrive/controls/tests/test_torqued_lat_accel_offset.py index 7599eb1e7b..4fe855544e 100644 --- a/openpilot/selfdrive/controls/tests/test_torqued_lat_accel_offset.py +++ b/openpilot/selfdrive/controls/tests/test_torqued_lat_accel_offset.py @@ -1,4 +1,5 @@ import numpy as np +from openpilot.common.test import OpenpilotTestCase from openpilot.cereal import messaging from opendbc.car.structs import car from opendbc.car import ACCELERATION_DUE_TO_GRAVITY @@ -56,7 +57,7 @@ def simulate_straight_road_msgs(est): for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('livePose', livePose)): est.handle_log(t, which, msg) -class TestTorquedLatAccelOffset: +class TestTorquedLatAccelOffset(OpenpilotTestCase): def test_estimated_offset(self): steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD) est = get_warmed_up_estimator(steer_torques, lat_accels) diff --git a/openpilot/selfdrive/locationd/test/test_calibrationd.py b/openpilot/selfdrive/locationd/test/test_calibrationd.py index f862b369fe..b3722e344c 100644 --- a/openpilot/selfdrive/locationd/test/test_calibrationd.py +++ b/openpilot/selfdrive/locationd/test/test_calibrationd.py @@ -2,6 +2,7 @@ import random import numpy as np +from openpilot.common.test import OpenpilotTestCase import openpilot.cereal.messaging as messaging from openpilot.cereal import log from openpilot.common.params import Params @@ -29,7 +30,7 @@ def process_messages(c, cam_odo_calib, cycles, [0.0, 0.0, HEIGHT_INIT.item()], [cam_odo_height_std, cam_odo_height_std, cam_odo_height_std]) -class TestCalibrationd: +class TestCalibrationd(OpenpilotTestCase): def test_read_saved_params(self): msg = messaging.new_message('liveCalibration') diff --git a/openpilot/selfdrive/locationd/test/test_lagd.py b/openpilot/selfdrive/locationd/test/test_lagd.py index efaba6ead9..83edc00d1e 100644 --- a/openpilot/selfdrive/locationd/test/test_lagd.py +++ b/openpilot/selfdrive/locationd/test/test_lagd.py @@ -1,8 +1,9 @@ import random import numpy as np import time -import pytest +import unittest +from openpilot.common.test import OpenpilotTestCase from openpilot.cereal import messaging, log from opendbc.car.structs import car from openpilot.selfdrive.locationd.lagd import LateralLagEstimator, retrieve_initial_lag, masked_normalized_cross_correlation, \ @@ -45,7 +46,7 @@ def process_messages(estimator, lag_frames, n_frames, vego=25.0, rejection_thres estimator.update_estimate() -class TestLagd: +class TestLagd(OpenpilotTestCase): def test_read_saved_params(self): params = Params() @@ -137,7 +138,7 @@ class TestLagd: assert np.allclose(msg.liveDelay.lateralDelayEstimateStd, 0.0, atol=0.01) assert msg.liveDelay.calPerc == 100 - @pytest.mark.skipif(PC, reason="only on device") + @unittest.skipIf(PC, "only on device") def test_estimator_performance(self): mocked_CP = car.CarParams(steerActuatorDelay=0.5) estimator = LateralLagEstimator(mocked_CP, DT) diff --git a/openpilot/selfdrive/locationd/test/test_locationd_scenarios.py b/openpilot/selfdrive/locationd/test/test_locationd_scenarios.py index 04d2d6b55b..c5fe1908bc 100644 --- a/openpilot/selfdrive/locationd/test/test_locationd_scenarios.py +++ b/openpilot/selfdrive/locationd/test/test_locationd_scenarios.py @@ -1,7 +1,11 @@ +import fcntl import numpy as np +import os +import tempfile from collections import defaultdict from enum import Enum +from openpilot.common.test import OpenpilotTestCase from openpilot.tools.lib.logreader import LogReader from openpilot.selfdrive.locationd.lagd import masked_symmetric_moving_average from openpilot.selfdrive.test.process_replay.migration import migrate_all @@ -96,7 +100,7 @@ def run_scenarios(scenario, logs): return get_select_fields_data(logs), get_select_fields_data(replayed_logs) -class TestLocationdScenarios: +class TestLocationdScenarios(OpenpilotTestCase): """ Test locationd with different scenarios. In all these scenarios, we expect the following: - locationd kalman filter should never go unstable (we care mostly about yaw_rate, roll, gpsOK, inputsOK, sensorsOK) @@ -105,7 +109,20 @@ class TestLocationdScenarios: @classmethod def setup_class(cls): - cls.logs = migrate_all(LogReader(TEST_ROUTE)) + # xdist can initialize this class in several workers at once. URLFile's + # cache writes are atomic, but cache misses are not locked, so every worker + # otherwise downloads the same route concurrently. + lock_path = os.path.join(tempfile.gettempdir(), "openpilot-locationd-scenarios.lock") + ready_path = f"{lock_path}.ready" + logs = None + with open(lock_path, "w") as lock: + fcntl.flock(lock, fcntl.LOCK_EX) + if not os.path.exists(ready_path): + logs = list(LogReader(TEST_ROUTE)) + open(ready_path, "w").close() + if logs is None: + logs = list(LogReader(TEST_ROUTE)) + cls.logs = migrate_all(logs) def test_base(self): """ diff --git a/openpilot/selfdrive/locationd/test/test_paramsd.py b/openpilot/selfdrive/locationd/test/test_paramsd.py index 74135cb875..09a067f4cf 100644 --- a/openpilot/selfdrive/locationd/test/test_paramsd.py +++ b/openpilot/selfdrive/locationd/test/test_paramsd.py @@ -1,6 +1,7 @@ import random import numpy as np +from openpilot.common.test import OpenpilotTestCase from openpilot.cereal import messaging from openpilot.selfdrive.locationd.paramsd import retrieve_initial_vehicle_params from openpilot.selfdrive.locationd.models.car_kf import CarKalman @@ -19,7 +20,7 @@ def get_random_live_parameters(CP): return msg -class TestParamsd: +class TestParamsd(OpenpilotTestCase): def test_read_saved_params(self): params = Params() diff --git a/openpilot/selfdrive/locationd/test/test_torqued.py b/openpilot/selfdrive/locationd/test/test_torqued.py index 4d337178a0..af3aeb95d6 100644 --- a/openpilot/selfdrive/locationd/test/test_torqued.py +++ b/openpilot/selfdrive/locationd/test/test_torqued.py @@ -1,8 +1,9 @@ from opendbc.car.structs import car +from openpilot.common.test import OpenpilotTestCase from openpilot.selfdrive.locationd.torqued import TorqueEstimator -class TestTorqued: +class TestTorqued(OpenpilotTestCase): def test_cal_percent(self): est = TorqueEstimator(car.CarParams()) msg = est.get_msg() diff --git a/openpilot/selfdrive/monitoring/test_monitoring.py b/openpilot/selfdrive/monitoring/test_monitoring.py index 0368dc9b67..53e40f7019 100644 --- a/openpilot/selfdrive/monitoring/test_monitoring.py +++ b/openpilot/selfdrive/monitoring/test_monitoring.py @@ -1,3 +1,4 @@ +from openpilot.common.test import OpenpilotTestCase from openpilot.cereal import log from openpilot.common.realtime import DT_DMON from openpilot.selfdrive.monitoring.policy import DriverMonitoring, DRIVER_MONITOR_SETTINGS @@ -46,7 +47,7 @@ always_distracted = [msg_DISTRACTED] * int(TEST_TIMESPAN / DT_DMON) always_true = [True] * int(TEST_TIMESPAN / DT_DMON) always_false = [False] * int(TEST_TIMESPAN / DT_DMON) -class TestMonitoring: +class TestMonitoring(OpenpilotTestCase): def _run_seq(self, msgs, interaction, engaged, lowspeed): DM = DriverMonitoring() alert_lvls = [] diff --git a/openpilot/selfdrive/pandad/tests/test_pandad.py b/openpilot/selfdrive/pandad/tests/test_pandad.py index 0f8fd9fc1a..820001bbc7 100644 --- a/openpilot/selfdrive/pandad/tests/test_pandad.py +++ b/openpilot/selfdrive/pandad/tests/test_pandad.py @@ -1,7 +1,7 @@ import os -import pytest import time +from openpilot.common.test import OpenpilotTestCase import openpilot.cereal.messaging as messaging from openpilot.cereal import log from openpilot.common.gpio import gpio_set, gpio_init @@ -13,8 +13,8 @@ from openpilot.common.hardware.tici.pins import GPIO HERE = os.path.dirname(os.path.realpath(__file__)) -@pytest.mark.tici -class TestPandad: +class TestPandad(OpenpilotTestCase): + TICI_TEST = True def teardown_method(self): managed_processes['pandad'].stop() diff --git a/openpilot/selfdrive/pandad/tests/test_pandad_loopback.py b/openpilot/selfdrive/pandad/tests/test_pandad_loopback.py index 0d0aa80046..8dd064bd73 100644 --- a/openpilot/selfdrive/pandad/tests/test_pandad_loopback.py +++ b/openpilot/selfdrive/pandad/tests/test_pandad_loopback.py @@ -2,10 +2,10 @@ import os import copy import random import time -import pytest from collections import defaultdict from pprint import pprint +from openpilot.common.test import OpenpilotTestCase import openpilot.cereal.messaging as messaging from openpilot.cereal import log from opendbc.car.structs import car @@ -69,8 +69,8 @@ def send_random_can_messages(sendcan, count): return sent_msgs -@pytest.mark.tici -class TestBoarddLoopback: +class TestBoarddLoopback(OpenpilotTestCase): + TICI_TEST = True @classmethod def setup_class(cls): os.environ['STARTED'] = '1' diff --git a/openpilot/selfdrive/pandad/tests/test_pandad_spi.py b/openpilot/selfdrive/pandad/tests/test_pandad_spi.py index 2bc302b47c..79eb2dd77e 100644 --- a/openpilot/selfdrive/pandad/tests/test_pandad_spi.py +++ b/openpilot/selfdrive/pandad/tests/test_pandad_spi.py @@ -1,9 +1,9 @@ import os import time import numpy as np -import pytest import random +from openpilot.common.test import OpenpilotTestCase import openpilot.cereal.messaging as messaging from openpilot.cereal.services import SERVICE_LIST from openpilot.common.timeout import Timeout @@ -12,8 +12,8 @@ from openpilot.selfdrive.pandad.tests.test_pandad_loopback import setup_pandad, JUNGLE_SPAM = "JUNGLE_SPAM" in os.environ -@pytest.mark.tici -class TestBoarddSpi: +class TestBoarddSpi(OpenpilotTestCase): + TICI_TEST = True @classmethod def setup_class(cls): os.environ['STARTED'] = '1' diff --git a/openpilot/selfdrive/selfdrived/tests/test_alertmanager.py b/openpilot/selfdrive/selfdrived/tests/test_alertmanager.py index 030b7d4515..23a01d184c 100644 --- a/openpilot/selfdrive/selfdrived/tests/test_alertmanager.py +++ b/openpilot/selfdrive/selfdrived/tests/test_alertmanager.py @@ -1,10 +1,11 @@ import random +from openpilot.common.test import OpenpilotTestCase from openpilot.selfdrive.selfdrived.events import Alert, EmptyAlert, EVENTS from openpilot.selfdrive.selfdrived.alertmanager import AlertManager -class TestAlertManager: +class TestAlertManager(OpenpilotTestCase): def test_duration(self): """ diff --git a/openpilot/selfdrive/selfdrived/tests/test_alerts.py b/openpilot/selfdrive/selfdrived/tests/test_alerts.py index 276de2a6a9..d19b31333a 100644 --- a/openpilot/selfdrive/selfdrived/tests/test_alerts.py +++ b/openpilot/selfdrive/selfdrived/tests/test_alerts.py @@ -4,6 +4,7 @@ import os import random from PIL import Image, ImageDraw, ImageFont +from openpilot.common.test import OpenpilotTestCase from openpilot.cereal import log from opendbc.car.structs import car from openpilot.cereal.messaging import SubMaster @@ -24,7 +25,7 @@ for event_types in EVENTS.values(): ALERTS.append(alert) -class TestAlerts: +class TestAlerts(OpenpilotTestCase): @classmethod def setup_class(cls): diff --git a/openpilot/selfdrive/selfdrived/tests/test_state_machine.py b/openpilot/selfdrive/selfdrived/tests/test_state_machine.py index ec8f068039..22f664718e 100644 --- a/openpilot/selfdrive/selfdrived/tests/test_state_machine.py +++ b/openpilot/selfdrive/selfdrived/tests/test_state_machine.py @@ -1,3 +1,4 @@ +from openpilot.common.test import OpenpilotTestCase from openpilot.cereal import log from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.selfdrived.state import StateMachine, SOFT_DISABLE_TIME @@ -21,7 +22,7 @@ def make_event(event_types: list[str | None]): return 0 -class TestStateMachine: +class TestStateMachine(OpenpilotTestCase): def setup_method(self): self.events = Events() self.state_machine = StateMachine() diff --git a/openpilot/selfdrive/test/cpp_harness.py b/openpilot/selfdrive/test/cpp_harness.py deleted file mode 100755 index f9f425102b..0000000000 --- a/openpilot/selfdrive/test/cpp_harness.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python3 -import subprocess -import sys - -from openpilot.common.prefix import OpenpilotPrefix - -with OpenpilotPrefix(): - ret = subprocess.call(sys.argv[1:]) - -sys.exit(ret) diff --git a/openpilot/selfdrive/test/helpers.py b/openpilot/selfdrive/test/helpers.py index 9d954216b8..2bcb6d8409 100644 --- a/openpilot/selfdrive/test/helpers.py +++ b/openpilot/selfdrive/test/helpers.py @@ -3,7 +3,6 @@ import http.server import os import threading import time -import pytest from functools import wraps @@ -32,7 +31,7 @@ def release_only(f): @wraps(f) def wrap(self, *args, **kwargs): if "RELEASE" not in os.environ: - pytest.skip("This test is only for release branches") + self.skipTest("This test is only for release branches") f(self, *args, **kwargs) return wrap diff --git a/openpilot/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/openpilot/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py index b8f97c5041..9bd22902d3 100644 --- a/openpilot/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/openpilot/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -1,4 +1,5 @@ import itertools +from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized_class from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE @@ -179,7 +180,7 @@ def create_maneuvers(kwargs): @parameterized_class(("e2e", "force_decel"), itertools.product([True, False], repeat=2)) -class TestLongitudinalControl: +class TestLongitudinalControl(OpenpilotTestCase): e2e: bool force_decel: bool diff --git a/openpilot/selfdrive/test/process_replay/test_fuzzy.py b/openpilot/selfdrive/test/process_replay/test_fuzzy.py index 372b368608..9a5bb41d48 100644 --- a/openpilot/selfdrive/test/process_replay/test_fuzzy.py +++ b/openpilot/selfdrive/test/process_replay/test_fuzzy.py @@ -2,6 +2,7 @@ import copy import os from hypothesis import given, HealthCheck, Phase, settings import hypothesis.strategies as st +from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized from openpilot.cereal import log @@ -17,7 +18,7 @@ NOT_TESTED = ['selfdrived', 'controlsd', 'card', 'plannerd', 'calibrationd', 'dm TEST_CASES = [(cfg.proc_name, copy.deepcopy(cfg)) for cfg in pr.CONFIGS if cfg.proc_name not in NOT_TESTED] MAX_EXAMPLES = int(os.environ.get("MAX_EXAMPLES", "10")) -class TestFuzzProcesses: +class TestFuzzProcesses(OpenpilotTestCase): # TODO: make this faster and increase examples @parameterized.expand(TEST_CASES) diff --git a/openpilot/selfdrive/test/process_replay/test_regen.py b/openpilot/selfdrive/test/process_replay/test_regen.py index f4942e486c..1a3dc8442a 100644 --- a/openpilot/selfdrive/test/process_replay/test_regen.py +++ b/openpilot/selfdrive/test/process_replay/test_regen.py @@ -1,3 +1,4 @@ +from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized from openpilot.selfdrive.test.process_replay.regen import regen_segment @@ -26,7 +27,9 @@ def ci_setup_data_readers(route, sidx): return lr, frs -class TestRegen: +class TestRegen(OpenpilotTestCase): + SLOW_TEST = True + @parameterized.expand(TESTED_SEGMENTS) def test_engaged(self, case_name, segment): route, sidx = segment.rsplit("--", 1) diff --git a/openpilot/selfdrive/test/test_onroad.py b/openpilot/selfdrive/test/test_onroad.py index b15d890452..b710fce919 100644 --- a/openpilot/selfdrive/test/test_onroad.py +++ b/openpilot/selfdrive/test/test_onroad.py @@ -1,13 +1,13 @@ import math import json import os -import pytest import shutil import subprocess import time import numpy as np from collections import Counter, defaultdict from pathlib import Path +from openpilot.common.test import OpenpilotTestCase from openpilot.common.utils import tabulate from openpilot.cereal import log @@ -102,9 +102,9 @@ def cputime_total(ct): return ct.cpuUser + ct.cpuSystem + ct.cpuChildrenUser + ct.cpuChildrenSystem -@pytest.mark.tici -@pytest.mark.skip_tici_setup -class TestOnroad: +class TestOnroad(OpenpilotTestCase): + TICI_TEST = True + SKIP_TICI_SETUP = True @classmethod def setup_class(cls): diff --git a/openpilot/selfdrive/test/test_power_draw.py b/openpilot/selfdrive/test/test_power_draw.py index 9bc012ceaa..530ce86f31 100644 --- a/openpilot/selfdrive/test/test_power_draw.py +++ b/openpilot/selfdrive/test/test_power_draw.py @@ -1,8 +1,8 @@ from collections import defaultdict, deque -import pytest import time import numpy as np from dataclasses import dataclass +from openpilot.common.test import OpenpilotTestCase from openpilot.common.utils import tabulate import openpilot.cereal.messaging as messaging @@ -38,8 +38,8 @@ PROCS = [ ] -@pytest.mark.tici -class TestPowerDraw: +class TestPowerDraw(OpenpilotTestCase): + TICI_TEST = True def setup_method(self): Params().put("CarParams", get_demo_car_params().to_bytes(), block=True) diff --git a/openpilot/selfdrive/ui/mici/tests/test_widget_leaks.py b/openpilot/selfdrive/ui/mici/tests/test_widget_leaks.py index 68c61f4ff3..271cb2704f 100755 --- a/openpilot/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/openpilot/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -1,6 +1,6 @@ import gc import weakref -import pytest +import unittest # FIXME: known small leaks not worth worrying about at the moment KNOWN_LEAKS = { @@ -41,8 +41,9 @@ def get_child_widgets(widget) -> list: return children -class TestWidgetLeaks: - @pytest.mark.skip(reason="segfaults") +from openpilot.common.test import OpenpilotTestCase +class TestWidgetLeaks(OpenpilotTestCase): + @unittest.skip("segfaults") def test_dialogs_do_not_leak(self): import pyray as rl rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) diff --git a/openpilot/selfdrive/ui/tests/test_feedbackd.py b/openpilot/selfdrive/ui/tests/test_feedbackd.py index 72bd499081..71803c3452 100644 --- a/openpilot/selfdrive/ui/tests/test_feedbackd.py +++ b/openpilot/selfdrive/ui/tests/test_feedbackd.py @@ -1,12 +1,14 @@ -import pytest +import unittest +from openpilot.common.parameterized import parameterized +from openpilot.common.test import OpenpilotTestCase import openpilot.cereal.messaging as messaging from opendbc.car.structs import car from openpilot.common.params import Params from openpilot.system.manager.process_config import managed_processes -@pytest.mark.skip("tmp disabled") -class TestFeedbackd: +@unittest.skip("tmp disabled") +class TestFeedbackd(OpenpilotTestCase): def setup_method(self): self.pm = messaging.PubMaster(['carState', 'rawAudioData']) self.sm = messaging.SubMaster(['audioFeedback']) @@ -25,7 +27,7 @@ class TestFeedbackd: self.pm.send('rawAudioData', audio_msg) self.sm.update(timeout=100) - @pytest.mark.parametrize("record_feedback", [False, True]) + @parameterized.expand([False, True]) def test_audio_feedback(self, record_feedback): Params().put_bool("RecordAudioFeedback", record_feedback, block=True) diff --git a/openpilot/selfdrive/ui/tests/test_raylib_ui.py b/openpilot/selfdrive/ui/tests/test_raylib_ui.py index d04c940cde..88f40ae6d9 100644 --- a/openpilot/selfdrive/ui/tests/test_raylib_ui.py +++ b/openpilot/selfdrive/ui/tests/test_raylib_ui.py @@ -1,8 +1,9 @@ import time +from openpilot.common.test import OpenpilotTestCase from openpilot.selfdrive.test.helpers import with_processes -class TestRaylibUi: +class TestRaylibUi(OpenpilotTestCase): @with_processes(["ui"]) def test_raylib_ui(self): """Test initialization of the UI widgets is successful.""" diff --git a/openpilot/selfdrive/ui/tests/test_soundd.py b/openpilot/selfdrive/ui/tests/test_soundd.py index da7921a559..3f2753f9fb 100644 --- a/openpilot/selfdrive/ui/tests/test_soundd.py +++ b/openpilot/selfdrive/ui/tests/test_soundd.py @@ -1,3 +1,4 @@ +from openpilot.common.test import OpenpilotTestCase from openpilot.cereal import log, messaging from openpilot.cereal.messaging import SubMaster, PubMaster from openpilot.selfdrive.ui.soundd import SELFDRIVE_STATE_TIMEOUT, check_selfdrive_timeout_alert @@ -7,7 +8,7 @@ import time AudibleAlert = log.SelfdriveState.AudibleAlert -class TestSoundd: +class TestSoundd(OpenpilotTestCase): def test_check_selfdrive_timeout_alert(self): sm = SubMaster(['selfdriveState']) pm = PubMaster(['selfdriveState']) @@ -31,4 +32,3 @@ class TestSoundd: assert check_selfdrive_timeout_alert(sm) # TODO: add test with micd for checking that soundd actually outputs sounds - diff --git a/openpilot/selfdrive/ui/tests/test_translations.py b/openpilot/selfdrive/ui/tests/test_translations.py index 9eae072f21..0d636b61c8 100644 --- a/openpilot/selfdrive/ui/tests/test_translations.py +++ b/openpilot/selfdrive/ui/tests/test_translations.py @@ -3,8 +3,9 @@ import re import string from pathlib import Path -import pytest +from openpilot.common.parameterized import parameterized +from openpilot.common.test import OpenpilotTestCase from openpilot.selfdrive.ui.translations.potools import parse_po from openpilot.system.ui.lib.multilang import LANGUAGES_FILE, TRANSLATIONS_DIR @@ -46,13 +47,13 @@ def load_po_text(po_path: Path) -> str: return po_path.read_text(encoding='utf-8') -class TestTranslations: - @pytest.mark.parametrize("language_code", sorted(TRANSLATION_LANGUAGES.values())) +class TestTranslations(OpenpilotTestCase): + @parameterized.expand(sorted(TRANSLATION_LANGUAGES.values())) def test_translation_file_exists(self, language_code: str): po_path = PO_DIR / f"app_{language_code}.po" assert po_path.exists(), f"missing translation file: {po_path}" - @pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) + @parameterized.expand(sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) def test_translation_placeholders_are_preserved(self, po_path: Path): _, entries = parse_po(po_path) language = po_path.stem.removeprefix("app_") @@ -89,14 +90,14 @@ class TestTranslations: ) assert translated_placeholders == source_placeholders, message - @pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) + @parameterized.expand(sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) def test_translation_refs_do_not_include_line_numbers(self, po_path: Path): for line in load_po_text(po_path).splitlines(): assert not LINE_NUMBER_REF_RE.match(line), ( f"{po_path.name}: line-number source reference found: {line}" ) - @pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) + @parameterized.expand(sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) def test_translation_entities_are_valid(self, po_path: Path): matches = BAD_ENTITY_RE.findall(load_po_text(po_path)) assert not matches, ( diff --git a/openpilot/system/athena/tests/test_athenad.py b/openpilot/system/athena/tests/test_athenad.py index 8c090b93af..dbc1050db8 100644 --- a/openpilot/system/athena/tests/test_athenad.py +++ b/openpilot/system/athena/tests/test_athenad.py @@ -1,4 +1,3 @@ -import pytest from functools import wraps import json import multiprocessing @@ -14,6 +13,8 @@ from datetime import datetime, timedelta from websocket import ABNF from websocket._exceptions import WebSocketConnectionClosedException +from openpilot.common.parameterized import parameterized +from openpilot.common.test import OpenpilotTestCase from openpilot.cereal import messaging from openpilot.common.params import Params @@ -47,16 +48,14 @@ def with_upload_handler(func): thread.join() return wrapper -@pytest.fixture def mock_create_connection(mocker): return mocker.patch('openpilot.system.athena.athenad.create_connection') -@pytest.fixture def host(): with http_server_context(handler=HTTPRequestHandler, setup=seed_athena_server) as (host, port): yield f"http://{host}:{port}" -class TestAthenadMethods: +class TestAthenadMethods(OpenpilotTestCase): @classmethod def setup_class(cls): cls.SOCKET_PORT = 45454 @@ -111,7 +110,7 @@ class TestAthenadMethods: assert dispatcher["echo"]("bob") == "bob" def test_get_message(self): - with pytest.raises(TimeoutError) as _: + with self.assertRaises(TimeoutError) as _: dispatcher["getMessage"]("controlsState") end_event = multiprocessing.Event() @@ -184,14 +183,14 @@ class TestAthenadMethods: if fn.endswith('.zst'): assert athenad.strip_zst_extension(fn) == fn[:-4] - @pytest.mark.parametrize("compress", [True, False]) + @parameterized.expand([True, False], names=("compress",)) def test_do_upload(self, host, compress): # random bytes to ensure rather large object post-compression fn = self._create_file('qlog', data=os.urandom(10000 * 1024)) upload_fn = fn + ('.zst' if compress else '') item = athenad.UploadItem(path=upload_fn, url="http://localhost:1238", headers={}, created_at=int(time.time()*1000), id='') # noqa: TID251 - with pytest.raises(requests.exceptions.ConnectionError): + with self.assertRaises(requests.exceptions.ConnectionError): athenad._do_upload(item) item = athenad.UploadItem(path=upload_fn, url=f"{host}/qlog.zst", headers={}, created_at=int(time.time()*1000), id='') # noqa: TID251 @@ -236,7 +235,7 @@ class TestAthenadMethods: # TODO: also check that end_event and metered network raises AbortTransferException assert athenad.upload_queue.qsize() == 0 - @pytest.mark.parametrize("status,retry", [(500,True), (412,False)]) + @parameterized.expand([(500,True), (412,False)], names=("status", "retry")) @with_upload_handler def test_upload_handler_retry(self, mocker, host, status, retry): mock_put = mocker.patch('openpilot.system.athena.athenad.UPLOAD_SESS.put') diff --git a/openpilot/system/athena/tests/test_athenad_ping.py b/openpilot/system/athena/tests/test_athenad_ping.py index 44a6b8a56b..d5592c40f1 100644 --- a/openpilot/system/athena/tests/test_athenad_ping.py +++ b/openpilot/system/athena/tests/test_athenad_ping.py @@ -1,9 +1,10 @@ -import pytest +import unittest import subprocess import threading import time from typing import cast +from openpilot.common.test import OpenpilotTestCase from openpilot.common.params import Params from openpilot.common.timeout import Timeout from openpilot.system.athena import athenad @@ -19,7 +20,7 @@ def wifi_radio(on: bool) -> None: subprocess.run(["nmcli", "radio", "wifi", "on" if on else "off"], check=True) -class TestAthenadPing: +class TestAthenadPing(OpenpilotTestCase): params: Params dongle_id: str @@ -90,12 +91,12 @@ class TestAthenadPing: time.sleep(0.1) print("ping received") - @pytest.mark.skipif(not TICI, reason="only run on desk") + @unittest.skipIf(not TICI, "only run on desk") def test_offroad(self, subtests, mocker) -> None: self.params.put_bool("IsOffroad", True, block=True) self.assertTimeout(60 + TIMEOUT_TOLERANCE, subtests, mocker) # based using TCP keepalive settings - @pytest.mark.skipif(not TICI, reason="only run on desk") + @unittest.skipIf(not TICI, "only run on desk") def test_onroad(self, subtests, mocker) -> None: self.params.put_bool("IsOffroad", False, block=True) self.assertTimeout(21 + TIMEOUT_TOLERANCE, subtests, mocker) diff --git a/openpilot/system/athena/tests/test_registration.py b/openpilot/system/athena/tests/test_registration.py index bb1523de80..3c75255c61 100644 --- a/openpilot/system/athena/tests/test_registration.py +++ b/openpilot/system/athena/tests/test_registration.py @@ -2,13 +2,14 @@ import json from Crypto.PublicKey import RSA from pathlib import Path +from openpilot.common.test import OpenpilotTestCase from openpilot.common.params import Params from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_ID from openpilot.system.athena.tests.helpers import MockResponse from openpilot.common.hardware.hw import Paths -class TestRegistration: +class TestRegistration(OpenpilotTestCase): def setup_method(self): # clear params and setup key paths diff --git a/openpilot/system/camerad/test/test_camerad.py b/openpilot/system/camerad/test/test_camerad.py index afc49c02bf..ad98a22581 100644 --- a/openpilot/system/camerad/test/test_camerad.py +++ b/openpilot/system/camerad/test/test_camerad.py @@ -1,8 +1,9 @@ import os import time -import pytest import numpy as np +from openpilot.common.parameterized import parameterized +from openpilot.common.test import OpenpilotTestCase from openpilot.cereal.services import SERVICE_LIST from openpilot.tools.lib.log_time_series import msgs_to_time_series from openpilot.system.camerad.snapshot import get_snapshots @@ -38,7 +39,6 @@ def run_and_log(procs, services, duration): with processes_context(procs): return collect_logs(services, duration) -@pytest.fixture(scope="module") def _camera_session(): """Single camerad session that collects logs and exposure data. Runs until exposure stabilizes (min TEST_TIMESPAN seconds for enough log data).""" @@ -69,20 +69,18 @@ def _camera_session(): return ts, exposure -@pytest.fixture(scope="module") -def logs(_camera_session): - return _camera_session[0] +class TestCamerad(OpenpilotTestCase): + TICI_TEST = True -@pytest.fixture(scope="module") -def exposure_data(_camera_session): - return _camera_session[1] + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.logs, cls.exposure_data = _camera_session() -@pytest.mark.tici -class TestCamerad: - @pytest.mark.parametrize("cam", CAMERAS) - def test_camera_exposure(self, exposure_data, cam): + @parameterized.expand(CAMERAS, names=("cam",)) + def test_camera_exposure(self, cam): lo, hi = EXPOSURE_RANGE - checks = exposure_data[cam] + checks = self.exposure_data[cam] assert len(checks) >= EXPOSURE_STABLE_COUNT, f"{cam}: only got {len(checks)} samples" # check that exposure converges into the valid range @@ -96,34 +94,34 @@ class TestCamerad: for i, (median, mean) in enumerate(checks): ok = _in_range(median, mean) if in_range and not ok: - pytest.fail(f"{cam}: exposure regressed on sample {i+1} " + + self.fail(f"{cam}: exposure regressed on sample {i+1} " + f"(median={median:.4f}, mean={mean:.4f}, expected: ({lo}, {hi}))") in_range = ok - def test_frame_skips(self, logs): + def test_frame_skips(self): for c in CAMERAS: - assert set(np.diff(logs[c]['frameId'])) == {1, }, f"{c} has frame skips" + assert set(np.diff(self.logs[c]['frameId'])) == {1, }, f"{c} has frame skips" - def test_frame_sync(self, logs): + def test_frame_sync(self): SYNCED_CAMS = ('roadCameraState', 'wideRoadCameraState') - n = range(len(logs['roadCameraState']['t'][:-10])) + n = range(len(self.logs['roadCameraState']['t'][:-10])) - frame_ids = {i: [logs[cam]['frameId'][i] for cam in CAMERAS] for i in n} + frame_ids = {i: [self.logs[cam]['frameId'][i] for cam in CAMERAS] for i in n} assert all(len(set(v)) == 1 for v in frame_ids.values()), "frame IDs not aligned" # road and wide cameras should be synced within 1.1ms - synced_times = {i: [logs[cam]['timestampSof'][i] for cam in SYNCED_CAMS] for i in n} + synced_times = {i: [self.logs[cam]['timestampSof'][i] for cam in SYNCED_CAMS] for i in n} diffs = {i: (max(ts) - min(ts))/1e6 for i, ts in synced_times.items()} laggy_frames = {k: v for k, v in diffs.items() if v > 1.1} assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" # driver camera should be staggered ~25ms from road camera for i in n: - offset_ms = abs(logs['driverCameraState']['timestampSof'][i] - logs['roadCameraState']['timestampSof'][i]) / 1e6 + offset_ms = abs(self.logs['driverCameraState']['timestampSof'][i] - self.logs['roadCameraState']['timestampSof'][i]) / 1e6 assert 20 < offset_ms < 30, f"driver camera stagger out of range at frame {i}: {offset_ms:.1f}ms (expected ~25ms)" - def test_sanity_checks(self, logs): - self._sanity_checks(logs) + def test_sanity_checks(self): + self._sanity_checks(self.logs) def _sanity_checks(self, ts): for c in CAMERAS: diff --git a/openpilot/system/hardware/tests/test_fan_controller.py b/openpilot/system/hardware/tests/test_fan_controller.py index e1aceeb081..63f9967101 100644 --- a/openpilot/system/hardware/tests/test_fan_controller.py +++ b/openpilot/system/hardware/tests/test_fan_controller.py @@ -1,10 +1,11 @@ -import pytest +from openpilot.common.parameterized import parameterized +from openpilot.common.test import OpenpilotTestCase from openpilot.system.hardware.fan_controller import FanController ALL_CONTROLLERS = [FanController] -class TestFanController: +class TestFanController(OpenpilotTestCase): def wind_up(self, controller, ignition=True): for _ in range(1000): controller.update(100, ignition) @@ -13,31 +14,31 @@ class TestFanController: for _ in range(1000): controller.update(10, ignition) - @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS) + @parameterized.expand(ALL_CONTROLLERS) def test_hot_onroad(self, controller_class): controller = controller_class(2) self.wind_up(controller) assert controller.update(100, True) >= 70 - @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS) + @parameterized.expand(ALL_CONTROLLERS) def test_offroad_limits(self, controller_class): controller = controller_class(2) self.wind_up(controller) assert controller.update(100, False) <= 30 - @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS) + @parameterized.expand(ALL_CONTROLLERS) def test_no_fan_wear(self, controller_class): controller = controller_class(2) self.wind_down(controller) assert controller.update(10, False) == 0 - @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS) + @parameterized.expand(ALL_CONTROLLERS) def test_limited(self, controller_class): controller = controller_class(2) self.wind_up(controller, True) assert controller.update(100, True) == 100 - @pytest.mark.parametrize("controller_class", ALL_CONTROLLERS) + @parameterized.expand(ALL_CONTROLLERS) def test_windup_speed(self, controller_class): controller = controller_class(2) self.wind_down(controller, True) diff --git a/openpilot/system/hardware/tests/test_power_monitoring.py b/openpilot/system/hardware/tests/test_power_monitoring.py index f3ffb98e09..7e0a260412 100644 --- a/openpilot/system/hardware/tests/test_power_monitoring.py +++ b/openpilot/system/hardware/tests/test_power_monitoring.py @@ -1,5 +1,5 @@ -import pytest +from openpilot.common.test import OpenpilotTestCase from openpilot.common.params import Params from openpilot.system.hardware.power_monitoring import PowerMonitoring, CAR_BATTERY_CAPACITY_uWh, \ CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S @@ -22,13 +22,9 @@ def pm_patch(mocker, name, value, constant=False): mocker.patch(f"openpilot.system.hardware.power_monitoring.{name}", return_value=value) -@pytest.fixture(autouse=True) -def mock_time(mocker): - mocker.patch("time.monotonic", mock_time_monotonic) - - -class TestPowerMonitoring: +class TestPowerMonitoring(OpenpilotTestCase): def setup_method(self): + self._fixture("mocker").patch("time.monotonic", mock_time_monotonic) self.params = Params() # Test to see that it doesn't do anything when pandaState is None diff --git a/openpilot/system/loggerd/tests/loggerd_tests_common.py b/openpilot/system/loggerd/tests/loggerd_tests_common.py index 55f5fbb816..689d2c27c0 100644 --- a/openpilot/system/loggerd/tests/loggerd_tests_common.py +++ b/openpilot/system/loggerd/tests/loggerd_tests_common.py @@ -3,6 +3,7 @@ import random from pathlib import Path +from openpilot.common.test import OpenpilotTestCase import openpilot.system.loggerd.deleter as deleter import openpilot.system.loggerd.uploader as uploader from openpilot.common.params import Params @@ -53,7 +54,7 @@ class MockApiIgnore: def get_token(self): return "fake-token" -class UploaderTestCase: +class UploaderTestCase(OpenpilotTestCase): f_type = "UNKNOWN" root: Path diff --git a/openpilot/system/loggerd/tests/test_deleter.py b/openpilot/system/loggerd/tests/test_deleter.py index 8f18c8ceee..6a0696c699 100644 --- a/openpilot/system/loggerd/tests/test_deleter.py +++ b/openpilot/system/loggerd/tests/test_deleter.py @@ -17,7 +17,7 @@ class TestDeleter(UploaderTestCase): def setup_method(self): self.f_type = "fcamera.hevc" - super().setup_method() + super().openpilot_setup_method() self.fake_stats = Stats(f_bavail=0, f_blocks=10, f_frsize=4096) deleter.os.statvfs = self.fake_statvfs # ty: ignore[invalid-assignment] # test double diff --git a/openpilot/system/loggerd/tests/test_encoder.py b/openpilot/system/loggerd/tests/test_encoder.py index 05bc211cbe..4d5a998583 100644 --- a/openpilot/system/loggerd/tests/test_encoder.py +++ b/openpilot/system/loggerd/tests/test_encoder.py @@ -1,6 +1,5 @@ import math import os -import pytest import shutil import subprocess import time @@ -8,6 +7,7 @@ from pathlib import Path from tqdm import trange +from openpilot.common.test import OpenpilotTestCase from openpilot.common.params import Params from openpilot.common.timeout import Timeout from openpilot.common.hardware import TICI @@ -29,8 +29,8 @@ CAMERAS = [ FILE_SIZE_TOLERANCE = 0.7 -@pytest.mark.tici # TODO: all of loggerd should work on PC -class TestEncoder: +class TestEncoder(OpenpilotTestCase): + TICI_TEST = True def setup_method(self): self._clear_logs() diff --git a/openpilot/system/loggerd/tests/test_loggerd.py b/openpilot/system/loggerd/tests/test_loggerd.py index 3678ca95be..6bb7e9bcf7 100644 --- a/openpilot/system/loggerd/tests/test_loggerd.py +++ b/openpilot/system/loggerd/tests/test_loggerd.py @@ -8,8 +8,9 @@ import time from collections.abc import Collection from collections import defaultdict from pathlib import Path -import pytest +from openpilot.common.parameterized import parameterized +from openpilot.common.test import OpenpilotTestCase import openpilot.cereal.messaging as messaging from openpilot.cereal import log from openpilot.cereal.services import SERVICE_LIST @@ -32,7 +33,7 @@ CEREAL_SERVICES = [f for f in log.Event.schema.union_fields if f in SERVICE_LIST and SERVICE_LIST[f].should_log and "encode" not in f.lower()] -class TestLoggerd: +class TestLoggerd(OpenpilotTestCase): def _get_latest_log_dir(self): log_dirs = sorted(Path(Paths.log_root()).iterdir(), key=lambda f: f.stat().st_mtime) return log_dirs[-1] @@ -193,7 +194,6 @@ class TestLoggerd: assert getattr(initData, initData_key) == v assert logged_params[param_key].decode() == v - @pytest.mark.xdist_group("camera_encoder_tests") # setting xdist group ensures tests are run in same worker, prevents encoderd from crashing def test_rotation(self): Params().put("RecordFront", True, block=True) @@ -314,8 +314,7 @@ class TestLoggerd: segment_dir = self._get_latest_log_dir() assert getxattr(segment_dir, PRESERVE_ATTR_NAME) is None - @pytest.mark.xdist_group("camera_encoder_tests") # setting xdist group ensures tests are run in same worker, prevents encoderd from crashing - @pytest.mark.parametrize("record_front", [True, False]) + @parameterized.expand([True, False]) def test_record_front(self, record_front): params = Params() params.put_bool("RecordFront", record_front, block=True) @@ -325,8 +324,7 @@ class TestLoggerd: dcamera_hevc_exists = os.path.exists(os.path.join(self._get_latest_log_dir(), 'dcamera.hevc')) assert dcamera_hevc_exists == record_front - @pytest.mark.xdist_group("camera_encoder_tests") # setting xdist group ensures tests are run in same worker, prevents encoderd from crashing - @pytest.mark.parametrize("record_audio", [True, False]) + @parameterized.expand([True, False]) def test_record_audio(self, record_audio): params = Params() params.put_bool("RecordAudio", record_audio, block=True) diff --git a/openpilot/system/loggerd/tests/test_uploader.py b/openpilot/system/loggerd/tests/test_uploader.py index 3af78e2c37..8b076ef0f4 100644 --- a/openpilot/system/loggerd/tests/test_uploader.py +++ b/openpilot/system/loggerd/tests/test_uploader.py @@ -37,7 +37,7 @@ cloudlog.addHandler(log_handler) class TestUploader(UploaderTestCase): def setup_method(self): - super().setup_method() + super().openpilot_setup_method() log_handler.reset() def start_thread(self): diff --git a/openpilot/system/manager/test/test_manager.py b/openpilot/system/manager/test/test_manager.py index a3808bf29f..d5945a4401 100644 --- a/openpilot/system/manager/test/test_manager.py +++ b/openpilot/system/manager/test/test_manager.py @@ -1,9 +1,10 @@ import os -import pytest +import unittest import signal import time from opendbc.car.structs import car +from openpilot.common.test import OpenpilotTestCase from openpilot.common.params import Params import openpilot.system.manager.manager as manager from openpilot.system.manager.process import ensure_running @@ -16,7 +17,7 @@ MAX_STARTUP_TIME = 3 BLACKLIST_PROCS = ['manage_athenad', 'pandad', 'pigeond'] -class TestManager: +class TestManager(OpenpilotTestCase): def setup_method(self): HARDWARE.set_power_save(False) @@ -47,7 +48,7 @@ class TestManager: assert params.get("OpenpilotEnabledToggle") assert params.get("RouteCount") == 0 - @pytest.mark.skip("this test is flaky the way it's currently written, should be moved to test_onroad") + @unittest.skip("this test is flaky the way it's currently written, should be moved to test_onroad") def test_clean_exit(self, subtests): """ Ensure all processes exit cleanly when stopped. diff --git a/openpilot/system/sensord/tests/test_sensord.py b/openpilot/system/sensord/tests/test_sensord.py index dc96886e4a..868d37c793 100644 --- a/openpilot/system/sensord/tests/test_sensord.py +++ b/openpilot/system/sensord/tests/test_sensord.py @@ -1,10 +1,10 @@ import os import subprocess -import pytest import time import numpy as np from collections import namedtuple, defaultdict +from openpilot.common.test import OpenpilotTestCase import openpilot.cereal.messaging as messaging from openpilot.cereal.services import SERVICE_LIST from openpilot.common.gpio import get_irqs_for_action @@ -54,8 +54,8 @@ def iter_measurements(events): for measurement in msgs: yield measurement, getattr(measurement, measurement.which()) -@pytest.mark.tici -class TestSensord: +class TestSensord(OpenpilotTestCase): + TICI_TEST = True @classmethod def setup_class(cls): # enable LSM self test diff --git a/openpilot/system/tests/test_logmessaged.py b/openpilot/system/tests/test_logmessaged.py index e2637fb0e6..247bfd8a42 100644 --- a/openpilot/system/tests/test_logmessaged.py +++ b/openpilot/system/tests/test_logmessaged.py @@ -2,13 +2,14 @@ import glob import os import time +from openpilot.common.test import OpenpilotTestCase import openpilot.cereal.messaging as messaging from openpilot.system.manager.process_config import managed_processes from openpilot.common.hardware.hw import Paths from openpilot.common.swaglog import cloudlog, ipchandler -class TestLogmessaged: +class TestLogmessaged(OpenpilotTestCase): def setup_method(self): # clear the IPC buffer in case some other tests used cloudlog and filled it ipchandler.close() @@ -52,4 +53,3 @@ class TestLogmessaged: logsize = sum([os.path.getsize(f) for f in self._get_log_files()]) assert (n*len(msg)) < logsize < (n*(len(msg)+1024)) - diff --git a/openpilot/system/ubloxd/tests/test_pigeond.py b/openpilot/system/ubloxd/tests/test_pigeond.py index b894ed718b..b7a0cd216d 100644 --- a/openpilot/system/ubloxd/tests/test_pigeond.py +++ b/openpilot/system/ubloxd/tests/test_pigeond.py @@ -1,6 +1,6 @@ -import pytest import time +from openpilot.common.test import OpenpilotTestCase import openpilot.cereal.messaging as messaging from openpilot.cereal.services import SERVICE_LIST from openpilot.common.gpio import gpio_read @@ -10,8 +10,8 @@ from openpilot.common.hardware.tici.pins import GPIO # TODO: test TTFF when we have good A-GNSS -@pytest.mark.tici -class TestPigeond: +class TestPigeond(OpenpilotTestCase): + TICI_TEST = True def teardown_method(self): managed_processes['pigeond'].stop() diff --git a/openpilot/system/ui/lib/tests/test_handle_state_change.py b/openpilot/system/ui/lib/tests/test_handle_state_change.py index 69aae6fdf3..a7a33834cd 100644 --- a/openpilot/system/ui/lib/tests/test_handle_state_change.py +++ b/openpilot/system/ui/lib/tests/test_handle_state_change.py @@ -3,15 +3,16 @@ Tests the state machine in isolation by constructing a WifiManager with mocked DBus, then calling _handle_state_change directly with NM state transitions. """ -import pytest +import unittest from jeepney.low_level import MessageType -from pytest_mock import MockerFixture +from openpilot.common.parameterized import parameterized +from openpilot.common.test import Mocker, OpenpilotTestCase from openpilot.system.ui.lib.networkmanager import NMDeviceState, NMDeviceStateReason from openpilot.system.ui.lib.wifi_manager import WifiManager, WifiState, ConnectStatus -def _make_wm(mocker: MockerFixture, connections=None): +def _make_wm(mocker: Mocker, connections=None): """Create a WifiManager with only the fields _handle_state_change touches.""" mocker.patch.object(WifiManager, '_initialize') wm = WifiManager.__new__(WifiManager) @@ -50,7 +51,7 @@ def fire_wpa_connect(wm: WifiManager) -> None: # Basic transitions # --------------------------------------------------------------------------- -class TestDisconnected: +class TestDisconnected(OpenpilotTestCase): def test_generic_disconnect_clears_state(self, mocker): wm = _make_wm(mocker) wm._wifi_state = WifiState(ssid="Net", status=ConnectStatus.CONNECTED) @@ -92,7 +93,7 @@ class TestDisconnected: assert wm._wifi_state.status == ConnectStatus.DISCONNECTED -class TestDeactivating: +class TestDeactivating(OpenpilotTestCase): def test_deactivating_noop_for_non_connection_removed(self, mocker): """DEACTIVATING with non-CONNECTION_REMOVED reason is a no-op.""" wm = _make_wm(mocker) @@ -103,10 +104,10 @@ class TestDeactivating: assert wm._wifi_state.ssid == "Net" assert wm._wifi_state.status == ConnectStatus.CONNECTED - @pytest.mark.parametrize("status, expected_clears", [ + @parameterized.expand([ (ConnectStatus.CONNECTED, True), (ConnectStatus.CONNECTING, False), - ]) + ], names=("status", "expected_clears")) def test_deactivating_connection_removed(self, mocker, status, expected_clears): """DEACTIVATING(CONNECTION_REMOVED) clears CONNECTED but preserves CONNECTING. @@ -130,7 +131,7 @@ class TestDeactivating: assert wm._wifi_state.status == ConnectStatus.CONNECTING -class TestPrepareConfig: +class TestPrepareConfig(OpenpilotTestCase): def test_user_initiated_skips_dbus_lookup(self, mocker): """User called _set_connecting('B') — PREPARE must not overwrite via DBus. @@ -148,7 +149,7 @@ class TestPrepareConfig: assert wm._wifi_state.status == ConnectStatus.CONNECTING wm._get_active_wifi_connection.assert_not_called() - @pytest.mark.parametrize("state", [NMDeviceState.PREPARE, NMDeviceState.CONFIG]) + @parameterized.expand([NMDeviceState.PREPARE, NMDeviceState.CONFIG], names=("state",)) def test_auto_connect_looks_up_ssid(self, mocker, state): """Auto-connection (ssid=None): PREPARE and CONFIG must look up ssid from NM.""" wm = _make_wm(mocker, connections={"AutoNet": "/path/auto"}) @@ -179,7 +180,7 @@ class TestPrepareConfig: assert wm._wifi_state.status == ConnectStatus.CONNECTING -class TestNeedAuth: +class TestNeedAuth(OpenpilotTestCase): def test_wrong_password_fires_callback(self, mocker): """NEED_AUTH+SUPPLICANT_DISCONNECT from CONFIG = real wrong password.""" wm = _make_wm(mocker) @@ -272,16 +273,16 @@ class TestNeedAuth: assert len(wm._callback_queue) == 0 -class TestPassthroughStates: +class TestPassthroughStates(OpenpilotTestCase): """NEED_AUTH (generic), IP_CONFIG, IP_CHECK, SECONDARIES, FAILED (generic) are no-ops.""" - @pytest.mark.parametrize("state", [ + @parameterized.expand([ NMDeviceState.NEED_AUTH, NMDeviceState.IP_CONFIG, NMDeviceState.IP_CHECK, NMDeviceState.SECONDARIES, NMDeviceState.FAILED, - ]) + ], names=("state",)) def test_passthrough_is_noop(self, mocker, state): wm = _make_wm(mocker) wm._set_connecting("Net") @@ -293,7 +294,7 @@ class TestPassthroughStates: assert len(wm._callback_queue) == 0 -class TestActivated: +class TestActivated(OpenpilotTestCase): def test_sets_connected(self, mocker): """ACTIVATED sets status to CONNECTED and fires callback.""" wm = _make_wm(mocker, connections={"MyNet": "/path/mynet"}) @@ -344,7 +345,7 @@ class TestActivated: # guard) shrink these race windows significantly. The epoch counter closes the # remaining gaps. -class TestThreadRaces: +class TestThreadRaces(OpenpilotTestCase): def test_prepare_race_user_tap_during_dbus(self, mocker): """User taps B while PREPARE's DBus call is in flight for auto-connect. @@ -416,7 +417,7 @@ class TestThreadRaces: # Full sequences (NM signal order from real devices) # --------------------------------------------------------------------------- -class TestFullSequences: +class TestFullSequences(OpenpilotTestCase): def test_normal_connect(self, mocker): """User connects to saved network: full happy path. @@ -771,7 +772,7 @@ class TestFullSequences: wm.process_callbacks() cb.assert_called_once_with("Hotspot") - @pytest.mark.xfail(reason="TODO: FAILED(SSID_NOT_FOUND) should emit error for UI") + @unittest.expectedFailure # "TODO: FAILED(SSID_NOT_FOUND) should emit error for UI" def test_ssid_not_found(self, mocker): """Network drops off while connected — hotspot turned off. @@ -843,7 +844,7 @@ class TestFullSequences: # Verified on device: when ActivateConnection returns UnknownConnection error, # NM emits no state signals. The worker error path is the only recovery point. -class TestWorkerErrorRecovery: +class TestWorkerErrorRecovery(OpenpilotTestCase): """Worker threads re-sync with NM via _init_wifi_state on DBus errors, preserving actual NM state instead of blindly clearing to DISCONNECTED.""" diff --git a/openpilot/system/webrtc/tests/test_stream_session.py b/openpilot/system/webrtc/tests/test_stream_session.py index 1f7bd5747a..5a9dc8772c 100644 --- a/openpilot/system/webrtc/tests/test_stream_session.py +++ b/openpilot/system/webrtc/tests/test_stream_session.py @@ -3,6 +3,7 @@ import json import time import capnp +from openpilot.common.test import OpenpilotTestCase from openpilot.cereal import messaging, log from teleoprtc.tracks import VIDEO_CLOCK_RATE @@ -10,7 +11,7 @@ from openpilot.system.webrtc.webrtcd import CerealOutgoingMessageProxy, CerealIn from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack -class TestStreamSession: +class TestStreamSession(OpenpilotTestCase): def setup_method(self): self.loop = asyncio.new_event_loop() diff --git a/openpilot/test_native.py b/openpilot/test_native.py new file mode 100644 index 0000000000..a69771f675 --- /dev/null +++ b/openpilot/test_native.py @@ -0,0 +1,25 @@ +import os +import subprocess + +from openpilot.common.basedir import BASEDIR +from openpilot.common.parameterized import parameterized +from openpilot.common.test import OpenpilotTestCase + + +NATIVE_TESTS = ( + "openpilot/common/tests/test_common", + "openpilot/selfdrive/pandad/tests/test_pandad_canprotocol", + "openpilot/system/loggerd/tests/test_logger", + "openpilot/tools/cabana/tests/test_cabana", + "openpilot/tools/cabana/tests/test_dbc_core", + "openpilot/tools/replay/tests/test_replay", +) + + +class TestNative(OpenpilotTestCase): + @parameterized.expand(NATIVE_TESTS) + def test_native(self, executable): + path = os.path.join(BASEDIR, executable) + if not os.path.exists(path): + self.skipTest(f"optional native test was not built: {executable}") + subprocess.run([path], check=True) diff --git a/openpilot/tools/jotpluggler/test_jotpluggler.py b/openpilot/tools/jotpluggler/test_jotpluggler.py index 0729e3b2e3..cbcc0a8168 100644 --- a/openpilot/tools/jotpluggler/test_jotpluggler.py +++ b/openpilot/tools/jotpluggler/test_jotpluggler.py @@ -5,7 +5,8 @@ from pathlib import Path JOTPLUGGLER_DIR = Path(__file__).parent -class TestJotpluggler: +from openpilot.common.test import OpenpilotTestCase +class TestJotpluggler(OpenpilotTestCase): def test_help(self): result = subprocess.run(["./jotpluggler", "-h"], cwd=JOTPLUGGLER_DIR, capture_output=True, text=True) assert result.returncode == 0, result.stderr diff --git a/openpilot/tools/lib/tests/test_caching.py b/openpilot/tools/lib/tests/test_caching.py index c70d7fc81b..565bac40dd 100644 --- a/openpilot/tools/lib/tests/test_caching.py +++ b/openpilot/tools/lib/tests/test_caching.py @@ -3,8 +3,9 @@ import os import shutil import socket import tempfile -import pytest +from openpilot.common.parameterized import parameterized +from openpilot.common.test import OpenpilotTestCase from openpilot.selfdrive.test.helpers import http_server_context from openpilot.common.hardware.hw import Paths from openpilot.tools.lib.url_file import URLFile, prune_cache @@ -30,12 +31,11 @@ class CachingTestRequestHandler(http.server.BaseHTTPRequestHandler): self.end_headers() -@pytest.fixture def host(): with http_server_context(handler=CachingTestRequestHandler) as (host, port): yield f"http://{host}:{port}" -class TestFileDownload: +class TestFileDownload(OpenpilotTestCase): def test_pipeline_defaults(self, host): # TODO: parameterize the defaults so we don't rely on hard-coded values in xx @@ -59,7 +59,7 @@ class TestFileDownload: # ensure caching on by default and cache dir gets created os.environ.pop("DISABLE_FILEREADER_CACHE", None) if os.path.exists(Paths.download_cache_root()): - shutil.rmtree(Paths.download_cache_root()) + shutil.rmtree(Paths.download_cache_root(), ignore_errors=True) URLFile(f"{host}/test.txt").get_length() URLFile(f"{host}/test.txt").read() assert os.path.exists(Paths.download_cache_root()) @@ -117,7 +117,7 @@ class TestFileDownload: self.compare_loads(large_file_url, length - 100, 100) self.compare_loads(large_file_url) - @pytest.mark.parametrize("cache_enabled", [True, False]) + @parameterized.expand([True, False], names=("cache_enabled",)) def test_recover_from_missing_file(self, host, cache_enabled): if cache_enabled: os.environ.pop("DISABLE_FILEREADER_CACHE", None) @@ -135,7 +135,7 @@ class TestFileDownload: assert length == 4 -class TestCache: +class TestCache(OpenpilotTestCase): def test_prune_cache(self, monkeypatch): with tempfile.TemporaryDirectory() as tmpdir: monkeypatch.setattr(Paths, 'download_cache_root', staticmethod(lambda: tmpdir + "/")) diff --git a/openpilot/tools/lib/tests/test_comma_car_segments.py b/openpilot/tools/lib/tests/test_comma_car_segments.py index 1b0f07ee63..a678aad51d 100644 --- a/openpilot/tools/lib/tests/test_comma_car_segments.py +++ b/openpilot/tools/lib/tests/test_comma_car_segments.py @@ -1,13 +1,14 @@ -import pytest +import unittest import requests from opendbc.car.fingerprints import MIGRATION +from openpilot.common.test import OpenpilotTestCase from openpilot.tools.lib.comma_car_segments import get_comma_car_segments_database, get_url from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.route import SegmentRange -@pytest.mark.skip(reason="huggingface is flaky, run this test manually to check for issues") -class TestCommaCarSegments: +@unittest.skip("huggingface is flaky, run this test manually to check for issues") +class TestCommaCarSegments(OpenpilotTestCase): def test_database(self): database = get_comma_car_segments_database() diff --git a/openpilot/tools/lib/tests/test_logreader.py b/openpilot/tools/lib/tests/test_logreader.py index a27a348d9e..edf2314ee1 100644 --- a/openpilot/tools/lib/tests/test_logreader.py +++ b/openpilot/tools/lib/tests/test_logreader.py @@ -4,9 +4,10 @@ import io import shutil import tempfile import os -import pytest +import unittest import requests +from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized from openpilot.cereal import log as capnp_log @@ -47,7 +48,7 @@ def setup_source_scenario(mocker, is_internal=False): yield -class TestLogReader: +class TestLogReader(OpenpilotTestCase): @parameterized.expand([ (f"{TEST_ROUTE}", ALL_SEGS), (f"{TEST_ROUTE.replace('/', '|')}", ALL_SEGS), @@ -72,7 +73,7 @@ class TestLogReader: (f"https://useradmin.comma.ai/?onebox={TEST_ROUTE.replace('/', '|')}", ALL_SEGS), (f"https://useradmin.comma.ai/?onebox={TEST_ROUTE.replace('/', '%7C')}", ALL_SEGS), ]) - @pytest.mark.skip("this got flaky. internet tests are stupid.") + @unittest.skip("this got flaky. internet tests are stupid.") def test_indirect_parsing(self, identifier, expected): parsed = parse_indirect(identifier) sr = SegmentRange(parsed) @@ -90,7 +91,7 @@ class TestLogReader: sr = SegmentRange(identifier) assert str(sr) == expected - @pytest.mark.parametrize("cache_enabled", [True, False]) + @parameterized.expand([True, False], names=("cache_enabled",)) def test_direct_parsing(self, mocker, cache_enabled): file_exists_mock = mocker.patch("openpilot.tools.lib.filereader.file_exists") if cache_enabled: @@ -107,7 +108,7 @@ class TestLogReader: l = len(list(LogReader(f))) assert l > 100 - with pytest.raises(URLFileException) if not cache_enabled else pytest.raises(AssertionError): + with self.assertRaises(URLFileException if not cache_enabled else AssertionError): l = len(list(LogReader(QLOG_FILE.replace("/3/", "/200/")))) # file_exists should not be called for direct files @@ -126,44 +127,44 @@ class TestLogReader: (f"{TEST_ROUTE}--3a",), ]) def test_bad_ranges(self, segment_range): - with pytest.raises(AssertionError): + with self.assertRaises(AssertionError): _ = SegmentRange(segment_range).seg_idxs - @pytest.mark.parametrize("segment_range, api_call", [ + @parameterized.expand([ (f"{TEST_ROUTE}/0", False), (f"{TEST_ROUTE}/:2", False), (f"{TEST_ROUTE}/0:", True), (f"{TEST_ROUTE}/-1", True), (f"{TEST_ROUTE}", True), - ]) + ], names=("segment_range", "api_call")) def test_slicing_api_call(self, mocker, segment_range, api_call): max_seg_mock = mocker.patch("openpilot.tools.lib.route.get_max_seg_number_cached") max_seg_mock.return_value = NUM_SEGS _ = SegmentRange(segment_range).seg_idxs assert api_call == max_seg_mock.called - @pytest.mark.slow + @unittest.skipIf(os.environ.get("SKIP_SLOW"), "slow test") def test_modes(self): qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0", ReadMode.QLOG))) rlog_len = len(list(LogReader(f"{TEST_ROUTE}/0", ReadMode.RLOG))) assert qlog_len * 6 < rlog_len - @pytest.mark.slow + @unittest.skipIf(os.environ.get("SKIP_SLOW"), "slow test") def test_modes_from_name(self): qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/q"))) rlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/r"))) assert qlog_len * 6 < rlog_len - @pytest.mark.slow + @unittest.skipIf(os.environ.get("SKIP_SLOW"), "slow test") def test_list(self): qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/q"))) qlog_len_2 = len(list(LogReader([f"{TEST_ROUTE}/0/q", f"{TEST_ROUTE}/0/q"]))) assert qlog_len * 2 == qlog_len_2 - @pytest.mark.slow + @unittest.skipIf(os.environ.get("SKIP_SLOW"), "slow test") def test_multiple_iterations(self, mocker): init_mock = mocker.patch("openpilot.tools.lib.logreader._LogFileReader") lr = LogReader(f"{TEST_ROUTE}/0/q") @@ -175,14 +176,14 @@ class TestLogReader: assert qlog_len1 == qlog_len2 - @pytest.mark.slow + @unittest.skipIf(os.environ.get("SKIP_SLOW"), "slow test") def test_helpers(self): lr = LogReader(f"{TEST_ROUTE}/0/q") assert lr.first("carParams").carFingerprint == "SUBARU OUTBACK 6TH GEN" assert 0 < len(list(lr.filter("carParams"))) < len(list(lr)) @parameterized.expand([(True,), (False,)]) - @pytest.mark.slow + @unittest.skipIf(os.environ.get("SKIP_SLOW"), "slow test") def test_run_across_segments(self, cache_enabled): if cache_enabled: os.environ.pop("DISABLE_FILEREADER_CACHE", None) @@ -191,7 +192,7 @@ class TestLogReader: lr = LogReader(f"{TEST_ROUTE}/0:4") assert len(lr.run_across_segments(4, noop)) == len(list(lr)) - @pytest.mark.slow + @unittest.skipIf(os.environ.get("SKIP_SLOW"), "slow test") def test_auto_mode(self, subtests, mocker): lr = LogReader(f"{TEST_ROUTE}/0/q") qlog_len = len(list(lr)) @@ -207,7 +208,7 @@ class TestLogReader: with subtests.test("interactive_no"): mocker.patch("sys.stdin", new=io.StringIO("n\n")) - with pytest.raises(LogsUnavailable): + with self.assertRaises(LogsUnavailable): lr = LogReader(f"{TEST_ROUTE}/0", default_mode=ReadMode.AUTO_INTERACTIVE, sources=[comma_api_source]) with subtests.test("non_interactive"): @@ -215,7 +216,7 @@ class TestLogReader: log_len = len(list(lr)) assert qlog_len == log_len - @pytest.mark.parametrize("is_internal", [True, False]) + @parameterized.expand([True, False], names=("is_internal",)) def test_auto_source_scenarios(self, mocker, is_internal): lr = LogReader(QLOG_FILE) qlog_len = len(list(lr)) @@ -225,7 +226,7 @@ class TestLogReader: log_len = len(list(lr)) assert qlog_len == log_len - @pytest.mark.slow + @unittest.skipIf(os.environ.get("SKIP_SLOW"), "slow test") def test_sort_by_time(self): msgs = list(LogReader(f"{TEST_ROUTE}/0/q")) assert msgs != sorted(msgs, key=lambda m: m.logMonoTime) @@ -254,7 +255,7 @@ class TestLogReader: # ensure new message is added, but is not a union type msgs = list(LogReader(qlog.name)) assert len(msgs) == num_msgs + 1 - with pytest.raises(capnp.KjException): + with self.assertRaises(capnp.KjException): [m.which() for m in msgs] # should not be added when only_union_types=True diff --git a/openpilot/tools/lib/tests/test_route_library.py b/openpilot/tools/lib/tests/test_route_library.py index 491bb81327..392484c794 100644 --- a/openpilot/tools/lib/tests/test_route_library.py +++ b/openpilot/tools/lib/tests/test_route_library.py @@ -1,8 +1,9 @@ from collections import namedtuple +from openpilot.common.test import OpenpilotTestCase from openpilot.tools.lib.route import SegmentName -class TestRouteLibrary: +class TestRouteLibrary(OpenpilotTestCase): def test_segment_name_formats(self): Case = namedtuple('Case', ['input', 'expected_route', 'expected_segment_num', 'expected_data_dir']) diff --git a/openpilot/tools/plotjuggler/test_plotjuggler.py b/openpilot/tools/plotjuggler/test_plotjuggler.py index d55aafe9be..10cd342dc8 100644 --- a/openpilot/tools/plotjuggler/test_plotjuggler.py +++ b/openpilot/tools/plotjuggler/test_plotjuggler.py @@ -5,17 +5,18 @@ import signal import subprocess import time -import pytest +import unittest +from openpilot.common.test import OpenpilotTestCase from openpilot.common.basedir import BASEDIR from openpilot.common.timeout import Timeout from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE, install PJ_DIR = os.path.join(BASEDIR, "openpilot/tools/plotjuggler") -class TestPlotJuggler: +class TestPlotJuggler(OpenpilotTestCase): - @pytest.mark.skipif(not shutil.which('qmake'), reason="Qt not installed") + @unittest.skipIf(not shutil.which('qmake'), "Qt not installed") def test_demo(self): install() diff --git a/openpilot/tools/sim/tests/conftest.py b/openpilot/tools/sim/tests/conftest.py deleted file mode 100644 index ddf6635276..0000000000 --- a/openpilot/tools/sim/tests/conftest.py +++ /dev/null @@ -1,8 +0,0 @@ -import pytest - -def pytest_addoption(parser): - parser.addoption("--test_duration", action="store", default=60, type=int, help="Seconds to run metadrive drive") - -@pytest.fixture -def test_duration(request): - return request.config.getoption("--test_duration") diff --git a/openpilot/tools/sim/tests/test_metadrive_bridge.py b/openpilot/tools/sim/tests/test_metadrive_bridge.py index 9be640d736..4e7560907b 100644 --- a/openpilot/tools/sim/tests/test_metadrive_bridge.py +++ b/openpilot/tools/sim/tests/test_metadrive_bridge.py @@ -1,17 +1,22 @@ -import pytest import warnings +import unittest +import importlib # Since metadrive depends on pkg_resources, and pkg_resources is deprecated as an API warnings.filterwarnings("ignore", category=DeprecationWarning) -from openpilot.tools.sim.bridge.metadrive.metadrive_bridge import MetaDriveBridge +try: + MetaDriveBridge = importlib.import_module("openpilot.tools.sim.bridge.metadrive.metadrive_bridge").MetaDriveBridge +except ModuleNotFoundError: + MetaDriveBridge = None from openpilot.tools.sim.tests.test_sim_bridge import TestSimBridgeBase -@pytest.mark.slow +@unittest.skipIf(MetaDriveBridge is None, "metadrive is not installed") class TestMetaDriveBridge(TestSimBridgeBase): - @pytest.fixture(autouse=True) - def setup_create_bridge(self, test_duration): + def setup_method(self): + super().openpilot_setup_method() self.test_duration = 30 def create_bridge(self): + assert MetaDriveBridge is not None return MetaDriveBridge(False, False, self.test_duration, True) diff --git a/openpilot/tools/sim/tests/test_sim_bridge.py b/openpilot/tools/sim/tests/test_sim_bridge.py index f93cc2ef50..a2baddad60 100644 --- a/openpilot/tools/sim/tests/test_sim_bridge.py +++ b/openpilot/tools/sim/tests/test_sim_bridge.py @@ -1,21 +1,23 @@ import os import subprocess import time -import pytest +import unittest from multiprocessing import Queue +from openpilot.common.test import OpenpilotTestCase from openpilot.cereal import messaging from openpilot.common.basedir import BASEDIR from openpilot.tools.sim.bridge.common import QueueMessageType SIM_DIR = os.path.join(BASEDIR, "openpilot/tools/sim") -class TestSimBridgeBase: +class TestSimBridgeBase(OpenpilotTestCase): + SLOW_TEST = True @classmethod def setup_class(cls): if cls is TestSimBridgeBase: - raise pytest.skip("Don't run this base class, run test_metadrive_bridge.py instead") + raise unittest.SkipTest("Don't run this base class, run test_metadrive_bridge.py instead") def setup_method(self): self.processes = [] diff --git a/pyproject.toml b/pyproject.toml index c3769f0401..68c5dd7fe7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,11 +65,7 @@ testing = [ "hypothesis ==6.47.*", "ty", "pytest", - "pytest-cpp", - "pytest-subtests", - # https://github.com/pytest-dev/pytest-xdist/pull/1229 - "pytest-xdist @ git+https://github.com/sshane/pytest-xdist@2b4372bd62699fb412c4fe2f95bf9f01bd2018da", - "pytest-mock", + "pytest-xdist", "ruff", "codespell", ] @@ -120,18 +116,8 @@ allow-direct-references = true [tool.pytest.ini_options] minversion = "6.0" -addopts = "-Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" -cpp_files = "test_*" -cpp_harness = "openpilot/selfdrive/test/cpp_harness.py" +addopts = "-Werror --strict-config --durations=10" python_files = "test_*.py" -markers = [ - "slow: tests that take awhile to run and can be skipped with -m 'not slow'", - "tici: tests that are only meant to run on the C3/C3X", - "skip_tici_setup: mark test to skip tici setup fixture", - "nocapture: don't capture test output", - "shared_download_cache: share download cache between tests", - "xdist_group_class_property: group tests by a property of the class that contains them", -] testpaths = [ "openpilot", ] @@ -168,6 +154,7 @@ line-length = 160 lint.flake8-implicit-str-concat.allow-multiline = false [tool.ruff.lint.flake8-tidy-imports.banned-api] +"pytest".msg = "Use unittest" "pytest.main".msg = "pytest.main requires special handling that is easy to mess up!" "time.time".msg = "Use time.monotonic" # time.time can skip due to its reference clock, you probably want a monotonic clock diff --git a/tools/op.sh b/tools/op.sh index 1ee7b232b0..6d70310668 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -327,7 +327,7 @@ function op_lint() { function op_test() { op_before_cmd - op_run_command pytest "$@" + op_run_command pytest -n logical --dist worksteal "$@" } function op_replay() { @@ -431,7 +431,7 @@ function op_default() { echo -e " ${BOLD}sim${NC} Run openpilot in a simulator" echo -e " ${BOLD}lint${NC} Run the linter" echo -e " ${BOLD}post-commit${NC} Install the linter as a post-commit hook" - echo -e " ${BOLD}test${NC} Run all unit tests from pytest" + echo -e " ${BOLD}test${NC} Run all unit tests" echo "" echo -e "${BOLD}${UNDERLINE}Options:${NC}" echo -e " ${BOLD}-d, --dir${NC}" diff --git a/uv.lock b/uv.lock index d03a44160e..ff155ab3bc 100644 --- a/uv.lock +++ b/uv.lock @@ -749,9 +749,6 @@ testing = [ { name = "coverage" }, { name = "hypothesis" }, { name = "pytest" }, - { name = "pytest-cpp" }, - { name = "pytest-mock" }, - { name = "pytest-subtests" }, { name = "pytest-xdist" }, { name = "ruff" }, { name = "ty" }, @@ -802,10 +799,7 @@ requires-dist = [ { name = "pycapnp", specifier = "==2.1.0" }, { name = "pyjwt", extras = ["crypto"] }, { name = "pytest", marker = "extra == 'testing'" }, - { name = "pytest-cpp", marker = "extra == 'testing'" }, - { name = "pytest-mock", marker = "extra == 'testing'" }, - { name = "pytest-subtests", marker = "extra == 'testing'" }, - { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, + { name = "pytest-xdist", marker = "extra == 'testing'" }, { name = "pyzmq" }, { name = "qrcode" }, { name = "rednose", marker = "extra == 'submodules'", editable = "rednose_repo" }, @@ -1004,51 +998,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] -[[package]] -name = "pytest-cpp" -version = "2.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/a1/c2679d7ff2da20a0f89c7820ae2739cde739eac9b43c192531117b31b5f4/pytest_cpp-2.6.0.tar.gz", hash = "sha256:c2f49d3c038539ac84786a94d852e4f4619c34c95979c2bc69c20b3bdf051d85", size = 465490, upload-time = "2024-09-18T00:08:08.251Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/44/dc2f5d53165264ae5831f361fe7723c45da05718a97015b2eddc452cf503/pytest_cpp-2.6.0-py3-none-any.whl", hash = "sha256:b33de94609450feea2fba9efff3558b8ac8f1fdf40a99e263b395d4798b911bb", size = 15074, upload-time = "2024-09-18T00:08:06.415Z" }, -] - -[[package]] -name = "pytest-mock" -version = "3.15.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, -] - -[[package]] -name = "pytest-subtests" -version = "0.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bb/d9/20097971a8d315e011e055d512fa120fd6be3bdb8f4b3aa3e3c6bf77bebc/pytest_subtests-0.15.0.tar.gz", hash = "sha256:cb495bde05551b784b8f0b8adfaa27edb4131469a27c339b80fd8d6ba33f887c", size = 18525, upload-time = "2025-10-20T16:26:18.358Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/64/bba465299b37448b4c1b84c7a04178399ac22d47b3dc5db1874fe55a2bd3/pytest_subtests-0.15.0-py3-none-any.whl", hash = "sha256:da2d0ce348e1f8d831d5a40d81e3aeac439fec50bd5251cbb7791402696a9493", size = 9185, upload-time = "2025-10-20T16:26:17.239Z" }, -] - [[package]] name = "pytest-xdist" -version = "3.7.1.dev24+g2b4372bd6" -source = { git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da#2b4372bd62699fb412c4fe2f95bf9f01bd2018da" } +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "execnet" }, { name = "pytest" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] [[package]] name = "python-dateutil" From 1262e26899cf8439f514a6e4cabedeb1d02401c6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Jul 2026 12:45:56 -0700 Subject: [PATCH 003/170] athena: bring back port migration This reverts commit d9596fa9985edc3e516174fc0b2de1f35021734e. --- openpilot/system/athena/athenad.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpilot/system/athena/athenad.py b/openpilot/system/athena/athenad.py index ef0561b122..91b12fc0a7 100755 --- a/openpilot/system/athena/athenad.py +++ b/openpilot/system/athena/athenad.py @@ -488,6 +488,10 @@ def setRouteViewed(route: str) -> dict[str, int | str]: def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local_port: int) -> dict[str, int]: try: + # migration, can be removed once 0.9.8 is out for a while + if local_port == 8022: + local_port = 22 + if local_port not in LOCAL_PORT_WHITELIST: raise Exception("Requested local port not whitelisted") From eb6c9c7f0dbe4fa99129e706af9a755c1a54d772 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Jul 2026 12:52:48 -0700 Subject: [PATCH 004/170] Run device tests directly (#38397) --- Jenkinsfile | 24 +++++++++---------- .../hardware/tici/tests/test_amplifier.py | 7 ++++++ .../selfdrive/pandad/tests/test_pandad.py | 7 ++++++ .../pandad/tests/test_pandad_loopback.py | 7 ++++++ .../selfdrive/pandad/tests/test_pandad_spi.py | 7 ++++++ openpilot/selfdrive/test/test_onroad.py | 7 ++++++ openpilot/selfdrive/test/test_power_draw.py | 7 ++++++ openpilot/system/camerad/test/test_camerad.py | 7 ++++++ .../system/loggerd/tests/test_encoder.py | 7 ++++++ openpilot/system/manager/test/test_manager.py | 6 +++++ .../system/sensord/tests/test_sensord.py | 7 ++++++ tools/release/build_release.sh | 2 +- 12 files changed, 82 insertions(+), 13 deletions(-) mode change 100644 => 100755 openpilot/common/hardware/tici/tests/test_amplifier.py mode change 100644 => 100755 openpilot/selfdrive/pandad/tests/test_pandad.py mode change 100644 => 100755 openpilot/selfdrive/pandad/tests/test_pandad_loopback.py mode change 100644 => 100755 openpilot/selfdrive/pandad/tests/test_pandad_spi.py mode change 100644 => 100755 openpilot/selfdrive/test/test_onroad.py mode change 100644 => 100755 openpilot/selfdrive/test/test_power_draw.py mode change 100644 => 100755 openpilot/system/camerad/test/test_camerad.py mode change 100644 => 100755 openpilot/system/loggerd/tests/test_encoder.py mode change 100644 => 100755 openpilot/system/manager/test/test_manager.py mode change 100644 => 100755 openpilot/system/sensord/tests/test_sensord.py diff --git a/Jenkinsfile b/Jenkinsfile index d57e9502aa..7c8d1c060e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -206,35 +206,35 @@ node { deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [ step("build openpilot", "cd openpilot/system/manager && ./build.py"), step("check dirty", "tools/release/check-dirty.sh"), - step("onroad tests", "pytest openpilot/selfdrive/test/test_onroad.py -s", [timeout: 60]), + step("onroad tests", "./openpilot/selfdrive/test/test_onroad.py", [timeout: 60]), ]) }, 'HW + Unit Tests': { deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [ step("build", "cd openpilot/system/manager && ./build.py"), - step("test power draw", "pytest -s openpilot/selfdrive/test//test_power_draw.py"), - step("test encoder", "pytest openpilot/system/loggerd/tests/test_encoder.py", [diffPaths: ["openpilot/system/loggerd/"]]), - step("test manager", "pytest openpilot/system/manager/test/test_manager.py"), + step("test power draw", "./openpilot/selfdrive/test/test_power_draw.py"), + step("test encoder", "./openpilot/system/loggerd/tests/test_encoder.py", [diffPaths: ["openpilot/system/loggerd/"]]), + step("test manager", "./openpilot/system/manager/test/test_manager.py"), ]) }, 'camerad OX03C10': { deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [ step("build", "cd openpilot/system/manager && ./build.py"), - step("test pandad", "pytest openpilot/selfdrive/pandad/tests/test_pandad.py"), - step("test camerad", "pytest openpilot/system/camerad/test/test_camerad.py", [timeout: 90]), + step("test pandad", "./openpilot/selfdrive/pandad/tests/test_pandad.py"), + step("test camerad", "./openpilot/system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, 'camerad OS04C10': { deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [ step("build", "cd openpilot/system/manager && ./build.py"), - step("test pandad", "pytest openpilot/selfdrive/pandad/tests/test_pandad.py"), - step("test camerad", "pytest openpilot/system/camerad/test/test_camerad.py", [timeout: 90]), + step("test pandad", "./openpilot/selfdrive/pandad/tests/test_pandad.py"), + step("test camerad", "./openpilot/system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, 'sensord': { deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [ step("build", "cd openpilot/system/manager && ./build.py"), - step("test sensord", "pytest openpilot/system/sensord/tests/test_sensord.py"), + step("test sensord", "./openpilot/system/sensord/tests/test_sensord.py"), ]) }, 'replay': { @@ -246,9 +246,9 @@ node { 'tizi': { deviceStage("tizi", "tizi", ["UNSAFE=1"], [ step("build openpilot", "cd openpilot/system/manager && ./build.py"), - step("test pandad loopback", "pytest openpilot/selfdrive/pandad/tests/test_pandad_loopback.py"), - step("test pandad spi", "pytest openpilot/selfdrive/pandad/tests/test_pandad_spi.py"), - step("test amp", "pytest openpilot/common/hardware/tici/tests/test_amplifier.py"), + step("test pandad loopback", "./openpilot/selfdrive/pandad/tests/test_pandad_loopback.py"), + step("test pandad spi", "./openpilot/selfdrive/pandad/tests/test_pandad_spi.py"), + step("test amp", "./openpilot/common/hardware/tici/tests/test_amplifier.py"), ]) }, diff --git a/openpilot/common/hardware/tici/tests/test_amplifier.py b/openpilot/common/hardware/tici/tests/test_amplifier.py old mode 100644 new mode 100755 index 08b8c9be80..2845805860 --- a/openpilot/common/hardware/tici/tests/test_amplifier.py +++ b/openpilot/common/hardware/tici/tests/test_amplifier.py @@ -1,5 +1,8 @@ +#!/usr/bin/env python3 + import time import subprocess +import unittest from panda import Panda from openpilot.common.test import OpenpilotTestCase @@ -62,3 +65,7 @@ class TestAmplifier(OpenpilotTestCase): break else: self.fail("didn't hit any i2c errors") + + +if __name__ == "__main__": + unittest.main() diff --git a/openpilot/selfdrive/pandad/tests/test_pandad.py b/openpilot/selfdrive/pandad/tests/test_pandad.py old mode 100644 new mode 100755 index 820001bbc7..bf70d5af79 --- a/openpilot/selfdrive/pandad/tests/test_pandad.py +++ b/openpilot/selfdrive/pandad/tests/test_pandad.py @@ -1,5 +1,8 @@ +#!/usr/bin/env python3 + import os import time +import unittest from openpilot.common.test import OpenpilotTestCase import openpilot.cereal.messaging as messaging @@ -79,3 +82,7 @@ class TestPandad(OpenpilotTestCase): assert not PandaDFU.list() self._run_test() + + +if __name__ == "__main__": + unittest.main() diff --git a/openpilot/selfdrive/pandad/tests/test_pandad_loopback.py b/openpilot/selfdrive/pandad/tests/test_pandad_loopback.py old mode 100644 new mode 100755 index 8dd064bd73..5895a32060 --- a/openpilot/selfdrive/pandad/tests/test_pandad_loopback.py +++ b/openpilot/selfdrive/pandad/tests/test_pandad_loopback.py @@ -1,7 +1,10 @@ +#!/usr/bin/env python3 + import os import copy import random import time +import unittest from collections import defaultdict from pprint import pprint @@ -114,3 +117,7 @@ class TestBoarddLoopback(OpenpilotTestCase): pprint(sm['pandaStates']) # may drop messages due to RX buffer overflow for bus in sent_loopback.keys(): assert not len(sent_loopback[bus]), f"loop {i}: bus {bus} missing {len(sent_loopback[bus])} out of {sent_total[bus]} messages" + + +if __name__ == "__main__": + unittest.main() diff --git a/openpilot/selfdrive/pandad/tests/test_pandad_spi.py b/openpilot/selfdrive/pandad/tests/test_pandad_spi.py old mode 100644 new mode 100755 index 79eb2dd77e..9cb0ac30c8 --- a/openpilot/selfdrive/pandad/tests/test_pandad_spi.py +++ b/openpilot/selfdrive/pandad/tests/test_pandad_spi.py @@ -1,5 +1,8 @@ +#!/usr/bin/env python3 + import os import time +import unittest import numpy as np import random @@ -105,3 +108,7 @@ class TestBoarddSpi(OpenpilotTestCase): with subtests.test(msg="CAN traffic"): print(f"Sent {total_sent_count} CAN messages, got {total_recv_count} back. {total_recv_count/(total_sent_count+1e-4):.2%} received") assert total_recv_count > 20 + + +if __name__ == "__main__": + unittest.main() diff --git a/openpilot/selfdrive/test/test_onroad.py b/openpilot/selfdrive/test/test_onroad.py old mode 100644 new mode 100755 index b710fce919..2fa7c75fb1 --- a/openpilot/selfdrive/test/test_onroad.py +++ b/openpilot/selfdrive/test/test_onroad.py @@ -1,9 +1,12 @@ +#!/usr/bin/env python3 + import math import json import os import shutil import subprocess import time +import unittest import numpy as np from collections import Counter, defaultdict from pathlib import Path @@ -442,3 +445,7 @@ class TestOnroad(OpenpilotTestCase): eng = [m.selfdriveState.engageable for m in self.msgs['selfdriveState'][offset:]] assert all(eng), \ f"Not engageable for whole segment:\n- selfdriveState.engageable: {Counter(eng)}\n- No entry events: {no_entries}" + + +if __name__ == "__main__": + unittest.main() diff --git a/openpilot/selfdrive/test/test_power_draw.py b/openpilot/selfdrive/test/test_power_draw.py old mode 100644 new mode 100755 index 530ce86f31..4c2a15a41f --- a/openpilot/selfdrive/test/test_power_draw.py +++ b/openpilot/selfdrive/test/test_power_draw.py @@ -1,5 +1,8 @@ +#!/usr/bin/env python3 + from collections import defaultdict, deque import time +import unittest import numpy as np from dataclasses import dataclass from openpilot.common.test import OpenpilotTestCase @@ -123,3 +126,7 @@ class TestPowerDraw(OpenpilotTestCase): assert self.valid_power_draw(proc, cur), f"expected {expected:.2f}W, got {cur:.2f}W" print(tabulate(tab)) print(f"Baseline {baseline:.2f}W\n") + + +if __name__ == "__main__": + unittest.main() diff --git a/openpilot/system/camerad/test/test_camerad.py b/openpilot/system/camerad/test/test_camerad.py old mode 100644 new mode 100755 index ad98a22581..0d66f0e16b --- a/openpilot/system/camerad/test/test_camerad.py +++ b/openpilot/system/camerad/test/test_camerad.py @@ -1,5 +1,8 @@ +#!/usr/bin/env python3 + import os import time +import unittest import numpy as np from openpilot.common.parameterized import parameterized @@ -158,3 +161,7 @@ class TestCamerad(OpenpilotTestCase): assert np.max([ np.max(np.diff(ts[c]['requestId'])) for c in CAMERAS ]) > 1 self._sanity_checks(ts) + + +if __name__ == "__main__": + unittest.main() diff --git a/openpilot/system/loggerd/tests/test_encoder.py b/openpilot/system/loggerd/tests/test_encoder.py old mode 100644 new mode 100755 index 4d5a998583..62c040cc19 --- a/openpilot/system/loggerd/tests/test_encoder.py +++ b/openpilot/system/loggerd/tests/test_encoder.py @@ -1,8 +1,11 @@ +#!/usr/bin/env python3 + import math import os import shutil import subprocess import time +import unittest from pathlib import Path from tqdm import trange @@ -144,3 +147,7 @@ class TestEncoder(OpenpilotTestCase): managed_processes['encoderd'].stop() managed_processes['camerad'].stop() managed_processes['sensord'].stop() + + +if __name__ == "__main__": + unittest.main() diff --git a/openpilot/system/manager/test/test_manager.py b/openpilot/system/manager/test/test_manager.py old mode 100644 new mode 100755 index d5945a4401..54bdaec6fe --- a/openpilot/system/manager/test/test_manager.py +++ b/openpilot/system/manager/test/test_manager.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import os import unittest import signal @@ -76,3 +78,7 @@ class TestManager(OpenpilotTestCase): if p.sigkill: exit_codes = [-signal.SIGKILL] assert exit_code in exit_codes, f"{p.name} died with {exit_code}" + + +if __name__ == "__main__": + unittest.main() diff --git a/openpilot/system/sensord/tests/test_sensord.py b/openpilot/system/sensord/tests/test_sensord.py old mode 100644 new mode 100755 index 868d37c793..e1580af9bb --- a/openpilot/system/sensord/tests/test_sensord.py +++ b/openpilot/system/sensord/tests/test_sensord.py @@ -1,6 +1,9 @@ +#!/usr/bin/env python3 + import os import subprocess import time +import unittest import numpy as np from collections import namedtuple, defaultdict @@ -183,3 +186,7 @@ class TestSensord(OpenpilotTestCase): time.sleep(1) state_two = get_irq_count(self.sensord_irq) assert state_one == state_two, "Interrupts received after sensord stop!" + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/release/build_release.sh b/tools/release/build_release.sh index cf03c8b393..0b6ed16ba9 100755 --- a/tools/release/build_release.sh +++ b/tools/release/build_release.sh @@ -85,7 +85,7 @@ git commit --amend -m "openpilot v$VERSION" # Run tests cd $BUILD_DIR -RELEASE=1 pytest -n0 -s openpilot/selfdrive/test/test_onroad.py +RELEASE=1 ./openpilot/selfdrive/test/test_onroad.py #pytest openpilot/selfdrive/car/tests/test_car_interfaces.py echo "[-] pushing release T=$SECONDS" From f1977eaa9300db5a4607acb698362574bc260cdf Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Tue, 21 Jul 2026 16:08:19 -0700 Subject: [PATCH 005/170] bump tg (#38398) * bump tg * slightly nicer --- openpilot/selfdrive/modeld/compile_modeld.py | 17 ----------------- openpilot/selfdrive/modeld/helpers.py | 10 +++------- tinygrad_repo | 2 +- 3 files changed, 4 insertions(+), 25 deletions(-) diff --git a/openpilot/selfdrive/modeld/compile_modeld.py b/openpilot/selfdrive/modeld/compile_modeld.py index ebc3f21a13..34e3095557 100755 --- a/openpilot/selfdrive/modeld/compile_modeld.py +++ b/openpilot/selfdrive/modeld/compile_modeld.py @@ -3,7 +3,6 @@ import argparse import atexit import math import os -import pickle import tempfile import time import shutil @@ -31,22 +30,6 @@ def _patch_tinygrad_fetch_fw(): helpers.fetch_fw = fetch_fw _patch_tinygrad_fetch_fw() -def _patch_tinygrad_buffer_reduce(): - from tinygrad.device import Buffer - def __reduce_ex__(self, protocol): - buf = None - if self._base is not None: - return self.__class__, (self.device, self.size, self.dtype, None, None, None, 0, self.base, self.offset, self.is_allocated()) - if self.device == "NPY": - return self.__class__, (self.device, self.size, self.dtype, self._buf, self.options, None, self.uop_refcount) - if self.is_allocated(): - buf = bytearray(self.nbytes) - self.copyout(memoryview(buf)) - if protocol >= 5: - buf = pickle.PickleBuffer(buf) - return self.__class__, (self.device, self.size, self.dtype, None, self.options, buf, self.uop_refcount) - Buffer.__reduce_ex__ = __reduce_ex__ -_patch_tinygrad_buffer_reduce() from tinygrad.tensor import Tensor from tinygrad.helpers import Context diff --git a/openpilot/selfdrive/modeld/helpers.py b/openpilot/selfdrive/modeld/helpers.py index 608bc6aa64..7c322c5993 100644 --- a/openpilot/selfdrive/modeld/helpers.py +++ b/openpilot/selfdrive/modeld/helpers.py @@ -38,14 +38,10 @@ def dump_oob(obj, f): def load_oob(f): opcodes = f.read(struct.unpack(' bool: diff --git a/tinygrad_repo b/tinygrad_repo index e6fbede157..ef37830d13 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit e6fbede1576f0a201e0b2c112499552a61280abd +Subproject commit ef37830d138838933d6d70d0024de9f130cb2308 From 9e0293671764290991462ff2af84ead16eb9bae5 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 21 Jul 2026 16:23:27 -0700 Subject: [PATCH 006/170] AGNOS 18.6 (#38393) * version * agnos 18.6 * fix --- launch_env.sh | 2 +- openpilot/common/hardware/tici/agnos.json | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 3e0ca602b4..696d0fe78d 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="18.5" + export AGNOS_VERSION="18.6" fi export STAGING_ROOT="/data/safe_staging" diff --git a/openpilot/common/hardware/tici/agnos.json b/openpilot/common/hardware/tici/agnos.json index f22a57db75..a1202ef7a4 100644 --- a/openpilot/common/hardware/tici/agnos.json +++ b/openpilot/common/hardware/tici/agnos.json @@ -56,28 +56,28 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39.img.xz", - "hash": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39", - "hash_raw": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39", + "url": "https://commadist.azureedge.net/agnosupdate/boot-e230f456b4849fc7c54ead79cb5ea1c8be91b7ea3431403797e23614834a6190.img.xz", + "hash": "e230f456b4849fc7c54ead79cb5ea1c8be91b7ea3431403797e23614834a6190", + "hash_raw": "e230f456b4849fc7c54ead79cb5ea1c8be91b7ea3431403797e23614834a6190", "size": 17487872, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "ddfe93cc6a8531af92ee331d9bbaeae2f1d933bdb38e579769dc9fe7998eb626" + "ondevice_hash": "34b160f05881d6b4cd7d9a85ad84ac8289d60d9854ccfc00fa31a4534a2c63fc" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img.xz", - "hash": "4dc41c2c072f5f5d5cd484cd6173049cd96acfb9a67bc20049775585fe881539", - "hash_raw": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724", + "url": "https://commadist.azureedge.net/agnosupdate/system-ef79d27902c9a432609c028e2f17c0609bab554a8644f0ecfdcfe4f3c936ea95.img.xz", + "hash": "e34ea7ee73ce85ac357ca2a0ade022fc56c11e9c5af3989cd0c83896a4f9266b", + "hash_raw": "ef79d27902c9a432609c028e2f17c0609bab554a8644f0ecfdcfe4f3c936ea95", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "cf1229630b7a2b8497705bca4ba947dbf0c217418ff4febff571aa4f4a878134", + "ondevice_hash": "7a2bc0374a2719a48b5c27012b16ae9654a473eb3a37c96d5d669d2a1cf45184", "alt": { - "hash": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724", - "url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img", + "hash": "ef79d27902c9a432609c028e2f17c0609bab554a8644f0ecfdcfe4f3c936ea95", + "url": "https://commadist.azureedge.net/agnosupdate/system-ef79d27902c9a432609c028e2f17c0609bab554a8644f0ecfdcfe4f3c936ea95.img", "size": 4718592000 } } From 765d6fff9b3029e19b9c475f7d70545e0544478f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Jul 2026 18:30:20 -0700 Subject: [PATCH 007/170] remove audio feedback (#38401) * remove audio feedback * rm feedbackd * backwards --- openpilot/cereal/deprecated.capnp | 10 +++ openpilot/cereal/log.capnp | 9 +-- openpilot/cereal/services.py | 1 - openpilot/common/params_keys.h | 1 - openpilot/selfdrive/selfdrived/events.py | 14 ---- openpilot/selfdrive/selfdrived/selfdrived.py | 7 +- .../test/process_replay/process_replay.py | 2 +- openpilot/selfdrive/test/test_onroad.py | 1 - openpilot/selfdrive/ui/feedback/feedbackd.py | 71 ------------------- openpilot/selfdrive/ui/layouts/main.py | 8 +-- openpilot/selfdrive/ui/mici/layouts/main.py | 8 +-- .../selfdrive/ui/tests/test_feedbackd.py | 55 -------------- openpilot/system/loggerd/loggerd.cc | 2 +- .../system/loggerd/tests/test_loggerd.py | 2 +- openpilot/system/manager/process_config.py | 1 - 15 files changed, 25 insertions(+), 167 deletions(-) delete mode 100755 openpilot/selfdrive/ui/feedback/feedbackd.py delete mode 100644 openpilot/selfdrive/ui/tests/test_feedbackd.py diff --git a/openpilot/cereal/deprecated.capnp b/openpilot/cereal/deprecated.capnp index 37153e4d62..b119e7091f 100644 --- a/openpilot/cereal/deprecated.capnp +++ b/openpilot/cereal/deprecated.capnp @@ -775,3 +775,13 @@ struct GpsTrajectory @0x8cfeb072f5301000 { x @0 :List(Float32); y @1 :List(Float32); } + +struct AudioFeedbackDEPRECATED @0xed47e3c075be372a { + audio @0 :AudioData; + blockNum @1 :UInt16; + + struct AudioData { + data @0 :Data; + sampleRate @1 :UInt32; + } +} diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp index 2148e81d3b..60e0bb9010 100644 --- a/openpilot/cereal/log.capnp +++ b/openpilot/cereal/log.capnp @@ -131,9 +131,9 @@ struct OnroadEvent @0xc4fa6047f024e718 { aeb @92; userBookmark @95; excessiveActuation @96; - audioFeedback @97; soundsUnavailableDEPRECATED @47; + audioFeedbackDEPRECATED @97; } } @@ -2485,11 +2485,6 @@ struct AudioData { sampleRate @1 :UInt32; } -struct AudioFeedback { - audio @0 :AudioData; - blockNum @1 :UInt16; -} - struct Touch { sec @0 :Int64; usec @1 :Int64; @@ -2583,7 +2578,6 @@ struct Event { # driving feedback userBookmark @93 :UserBookmark; bookmarkButton @148 :UserBookmark; - audioFeedback @149 :AudioFeedback; lateralManeuverPlan @150 :LateralManeuverPlan; @@ -2633,6 +2627,7 @@ struct Event { # *********** legacy + deprecated *********** model @9 :Deprecated.ModelData; # TODO: rename modelV2 and mark this as deprecated + audioFeedbackDEPRECATED @149 :Deprecated.AudioFeedbackDEPRECATED; liveMpcDEPRECATED @36 :Deprecated.LiveMpcData; liveLongitudinalMpcDEPRECATED @37 :Deprecated.LiveLongitudinalMpcData; liveLocationKalmanDeprecatedDEPRECATED @51 :Deprecated.LiveLocationData; diff --git a/openpilot/cereal/services.py b/openpilot/cereal/services.py index db1731a986..42669688b0 100755 --- a/openpilot/cereal/services.py +++ b/openpilot/cereal/services.py @@ -75,7 +75,6 @@ _services: dict[str, tuple] = { "soundPressure": (True, 10., 10), "rawAudioData": (False, 20.), "bookmarkButton": (True, 0., 1), - "audioFeedback": (True, 0., 1), "roadEncodeData": (False, 20., None, QueueSize.BIG), "driverEncodeData": (False, 20., None, QueueSize.BIG), "wideRoadEncodeData": (False, 20., None, QueueSize.BIG), diff --git a/openpilot/common/params_keys.h b/openpilot/common/params_keys.h index 6441fc91df..7ecac1c729 100644 --- a/openpilot/common/params_keys.h +++ b/openpilot/common/params_keys.h @@ -105,7 +105,6 @@ inline static std::unordered_map keys = { {"PandaHeartbeatLost", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"PrimeType", {PERSISTENT, INT}}, {"RecordAudio", {PERSISTENT, BOOL}}, - {"RecordAudioFeedback", {PERSISTENT, BOOL, "0"}}, {"RecordFront", {PERSISTENT, BOOL}}, {"RecordFrontLock", {PERSISTENT, BOOL}}, // for the internal fleet {"SecOCKey", {PERSISTENT | DONT_LOG, STRING}}, diff --git a/openpilot/selfdrive/selfdrived/events.py b/openpilot/selfdrive/selfdrived/events.py index 367bc03d33..ee9191fd1e 100755 --- a/openpilot/selfdrive/selfdrived/events.py +++ b/openpilot/selfdrive/selfdrived/events.py @@ -12,8 +12,6 @@ from openpilot.common.constants import CV from openpilot.common.git import get_short_branch from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER -from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER -from openpilot.selfdrive.ui.feedback.feedbackd import FEEDBACK_MAX_DURATION from openpilot.common.hardware import HARDWARE AlertSize = log.SelfdriveState.AlertSize @@ -272,14 +270,6 @@ def too_distracted_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubM return NoEntryAlert("Pay Attention to Engage", priority=Priority.HIGH) -def audio_feedback_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: - duration = FEEDBACK_MAX_DURATION - ((sm['audioFeedback'].blockNum + 1) * SAMPLE_BUFFER / SAMPLE_RATE) - return NormalPermanentAlert( - "Recording Audio Feedback", - f"{round(duration)} second{'s' if round(duration) != 1 else ''} remaining. Press again to save early.", - priority=Priority.LOW) - - # *** debug alerts *** def out_of_space_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: @@ -1032,10 +1022,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { EventName.userBookmark: { ET.PERMANENT: NormalPermanentAlert("Bookmark Saved", duration=1.5), }, - - EventName.audioFeedback: { - ET.PERMANENT: audio_feedback_alert, - }, } diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index d89c564925..9fed889be3 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -86,7 +86,7 @@ class SelfdriveD: self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', 'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay', 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', - 'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback', + 'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'lateralManeuverPlan'] + \ self.camera_packets + self.sensor_packets + self.gps_packets, ignore_alive=ignore, ignore_avg_freq=ignore, @@ -171,13 +171,10 @@ class SelfdriveD: self.events.add(EventName.selfdriveInitializing) return - # Check for user bookmark press (bookmark button or end of LKAS button feedback) + # Check for user bookmark press if self.sm.updated['userBookmark']: self.events.add(EventName.userBookmark) - if self.sm.updated['audioFeedback']: - self.events.add(EventName.audioFeedback) - # Don't add any more events while in dashcam mode if self.CP.passive: return diff --git a/openpilot/selfdrive/test/process_replay/process_replay.py b/openpilot/selfdrive/test/process_replay/process_replay.py index 2a90dfc295..5d2e5a22fa 100755 --- a/openpilot/selfdrive/test/process_replay/process_replay.py +++ b/openpilot/selfdrive/test/process_replay/process_replay.py @@ -436,7 +436,7 @@ CONFIGS = [ "longitudinalPlan", "livePose", "liveDelay", "liveParameters", "radarState", "modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState", "liveTorqueParameters", "accelerometer", "gyroscope", "carOutput", "gpsLocationExternal", "gpsLocation", "controlsState", - "carControl", "driverAssistance", "alertDebug", "audioFeedback", + "carControl", "driverAssistance", "alertDebug", ], subs=["selfdriveState", "onroadEvents"], ignore=["logMonoTime"], diff --git a/openpilot/selfdrive/test/test_onroad.py b/openpilot/selfdrive/test/test_onroad.py index 2fa7c75fb1..2504afb7cd 100755 --- a/openpilot/selfdrive/test/test_onroad.py +++ b/openpilot/selfdrive/test/test_onroad.py @@ -57,7 +57,6 @@ PROCS = { "openpilot.selfdrive.locationd.paramsd": 9.0, "openpilot.selfdrive.locationd.lagd": 11.0, "openpilot.selfdrive.ui.soundd": 3.0, - "openpilot.selfdrive.ui.feedback.feedbackd": 1.0, "openpilot.selfdrive.monitoring.dmonitoringd": 4.0, "openpilot.system.proclogd": 7.0, "openpilot.system.logmessaged": 1.0, diff --git a/openpilot/selfdrive/ui/feedback/feedbackd.py b/openpilot/selfdrive/ui/feedback/feedbackd.py deleted file mode 100755 index 57f6b881e4..0000000000 --- a/openpilot/selfdrive/ui/feedback/feedbackd.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 -import openpilot.cereal.messaging as messaging -from openpilot.common.params import Params -from openpilot.common.swaglog import cloudlog -from opendbc.car.structs import car -from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER - -FEEDBACK_MAX_DURATION = 10.0 -ButtonType = car.CarState.ButtonEvent.Type - - -def main(): - params = Params() - pm = messaging.PubMaster(['userBookmark', 'audioFeedback']) - sm = messaging.SubMaster(['rawAudioData', 'bookmarkButton', 'carState']) - should_record_audio = False - block_num = 0 - waiting_for_release = False - early_stop_triggered = False - - while True: - sm.update() - should_send_bookmark = False - - # TODO: https://github.com/commaai/openpilot/issues/36015 - if False and sm.updated['carState'] and sm['carState'].canValid: - for be in sm['carState'].buttonEvents: - if be.type == ButtonType.lkas: - if be.pressed: - if not should_record_audio: - if params.get_bool("RecordAudioFeedback"): # Start recording on first press if toggle set - should_record_audio = True - block_num = 0 - waiting_for_release = False - early_stop_triggered = False - cloudlog.info("LKAS button pressed - starting 10-second audio feedback") - else: - should_send_bookmark = True # immediately send bookmark if toggle false - cloudlog.info("LKAS button pressed - bookmarking") - elif should_record_audio and not waiting_for_release: # Wait for release of second press to stop recording early - waiting_for_release = True - elif waiting_for_release: # Second press released - waiting_for_release = False - early_stop_triggered = True - cloudlog.info("LKAS button released - ending recording early") - - if should_record_audio and sm.updated['rawAudioData']: - raw_audio = sm['rawAudioData'] - msg = messaging.new_message('audioFeedback', valid=True) - msg.audioFeedback.audio.data = raw_audio.data - msg.audioFeedback.audio.sampleRate = raw_audio.sampleRate - msg.audioFeedback.blockNum = block_num - block_num += 1 - if (block_num * SAMPLE_BUFFER / SAMPLE_RATE) >= FEEDBACK_MAX_DURATION or early_stop_triggered: # Check for timeout or early stop - should_send_bookmark = True # send bookmark at end of audio segment - should_record_audio = False - early_stop_triggered = False - cloudlog.info("10-second recording completed or second button press - stopping audio feedback") - pm.send('audioFeedback', msg) - - if sm.updated['bookmarkButton']: - cloudlog.info("Bookmark button pressed!") - should_send_bookmark = True - - if should_send_bookmark: - msg = messaging.new_message('userBookmark', valid=True) - pm.send('userBookmark', msg) - - -if __name__ == '__main__': - main() diff --git a/openpilot/selfdrive/ui/layouts/main.py b/openpilot/selfdrive/ui/layouts/main.py index 277b6f1404..9ce43b9e48 100644 --- a/openpilot/selfdrive/ui/layouts/main.py +++ b/openpilot/selfdrive/ui/layouts/main.py @@ -22,7 +22,7 @@ class MainLayout(Widget): def __init__(self): super().__init__() - self._pm = messaging.PubMaster(['bookmarkButton']) + self._pm = messaging.PubMaster(['bookmarkButton', 'userBookmark']) self._sidebar = Sidebar() self._current_mode = MainState.HOME @@ -111,9 +111,9 @@ class MainLayout(Widget): self.open_settings(PanelType.DEVICE) def _on_bookmark_clicked(self): - user_bookmark = messaging.new_message('bookmarkButton') - user_bookmark.valid = True - self._pm.send('bookmarkButton', user_bookmark) + for service in ('bookmarkButton', 'userBookmark'): + msg = messaging.new_message(service, valid=True) + self._pm.send(service, msg) def _on_onroad_clicked(self): self._sidebar.set_visible(not self._sidebar.is_visible) diff --git a/openpilot/selfdrive/ui/mici/layouts/main.py b/openpilot/selfdrive/ui/mici/layouts/main.py index e592253544..20925ab63c 100644 --- a/openpilot/selfdrive/ui/mici/layouts/main.py +++ b/openpilot/selfdrive/ui/mici/layouts/main.py @@ -19,7 +19,7 @@ class MiciMainLayout(Scroller): def __init__(self): super().__init__(snap_items=True, spacing=0, pad=0, scroll_indicator=False, edge_shadows=False) - self._pm = messaging.PubMaster(['bookmarkButton']) + self._pm = messaging.PubMaster(['bookmarkButton', 'userBookmark']) self._prev_onroad = False self._prev_standstill = False @@ -140,9 +140,9 @@ class MiciMainLayout(Scroller): self._scroll_to(self._home_layout) def _on_bookmark_clicked(self): - user_bookmark = messaging.new_message('bookmarkButton') - user_bookmark.valid = True - self._pm.send('bookmarkButton', user_bookmark) + for service in ('bookmarkButton', 'userBookmark'): + msg = messaging.new_message(service, valid=True) + self._pm.send(service, msg) def _on_body_changed(self): self._car_onroad_layout.set_visible(not ui_state.is_body) diff --git a/openpilot/selfdrive/ui/tests/test_feedbackd.py b/openpilot/selfdrive/ui/tests/test_feedbackd.py deleted file mode 100644 index 71803c3452..0000000000 --- a/openpilot/selfdrive/ui/tests/test_feedbackd.py +++ /dev/null @@ -1,55 +0,0 @@ -import unittest -from openpilot.common.parameterized import parameterized -from openpilot.common.test import OpenpilotTestCase -import openpilot.cereal.messaging as messaging -from opendbc.car.structs import car -from openpilot.common.params import Params -from openpilot.system.manager.process_config import managed_processes - - -@unittest.skip("tmp disabled") -class TestFeedbackd(OpenpilotTestCase): - def setup_method(self): - self.pm = messaging.PubMaster(['carState', 'rawAudioData']) - self.sm = messaging.SubMaster(['audioFeedback']) - - def _send_lkas_button(self, pressed: bool): - msg = messaging.new_message('carState') - msg.carState.canValid = True - msg.carState.buttonEvents = [{'type': car.CarState.ButtonEvent.Type.lkas, 'pressed': pressed}] - self.pm.send('carState', msg) - - def _send_audio_data(self, count: int = 5): - for _ in range(count): - audio_msg = messaging.new_message('rawAudioData') - audio_msg.rawAudioData.data = bytes(1600) # 800 samples of int16 - audio_msg.rawAudioData.sampleRate = 16000 - self.pm.send('rawAudioData', audio_msg) - self.sm.update(timeout=100) - - @parameterized.expand([False, True]) - def test_audio_feedback(self, record_feedback): - Params().put_bool("RecordAudioFeedback", record_feedback, block=True) - - managed_processes["feedbackd"].start() - assert self.pm.wait_for_readers_to_update('carState', timeout=5) - assert self.pm.wait_for_readers_to_update('rawAudioData', timeout=5) - - self._send_lkas_button(pressed=True) - self._send_audio_data() - self._send_lkas_button(pressed=False) - self._send_audio_data() - - if record_feedback: - assert self.sm.updated['audioFeedback'], "audioFeedback should be published when enabled" - else: - assert not self.sm.updated['audioFeedback'], "audioFeedback should not be published when disabled" - - self._send_lkas_button(pressed=True) - self._send_audio_data() - self._send_lkas_button(pressed=False) - self._send_audio_data() - - assert not self.sm.updated['audioFeedback'], "audioFeedback should not be published after second press" - - managed_processes["feedbackd"].stop() diff --git a/openpilot/system/loggerd/loggerd.cc b/openpilot/system/loggerd/loggerd.cc index 3755929619..9f848608ac 100644 --- a/openpilot/system/loggerd/loggerd.cc +++ b/openpilot/system/loggerd/loggerd.cc @@ -246,7 +246,7 @@ void loggerd_thread() { .counter = 0, .freq = it.decimation, .encoder = encoder, - .preserve_segment = (it.name == "userBookmark") || (it.name == "audioFeedback"), + .preserve_segment = it.name == "userBookmark", .record_audio = record_audio, }; } diff --git a/openpilot/system/loggerd/tests/test_loggerd.py b/openpilot/system/loggerd/tests/test_loggerd.py index 6bb7e9bcf7..618e3d0016 100644 --- a/openpilot/system/loggerd/tests/test_loggerd.py +++ b/openpilot/system/loggerd/tests/test_loggerd.py @@ -308,7 +308,7 @@ class TestLoggerd(OpenpilotTestCase): assert getxattr(segment_dir, PRESERVE_ATTR_NAME) == PRESERVE_ATTR_VALUE def test_not_preserving_nonbookmarked_segments(self): - services = set(random.sample(CEREAL_SERVICES, random.randint(5, 10))) - {"userBookmark", "audioFeedback"} + services = set(random.sample(CEREAL_SERVICES, random.randint(5, 10))) - {"userBookmark"} self._publish_random_messages(services) segment_dir = self._get_latest_log_dir() diff --git a/openpilot/system/manager/process_config.py b/openpilot/system/manager/process_config.py index 655d1c4e29..e60598a7b6 100644 --- a/openpilot/system/manager/process_config.py +++ b/openpilot/system/manager/process_config.py @@ -116,7 +116,6 @@ procs = [ PythonProcess("tombstoned", "openpilot.system.tombstoned", always_run, enabled=not PC), PythonProcess("updated", "openpilot.system.updated.updated", only_offroad, enabled=not PC), PythonProcess("uploader", "openpilot.system.loggerd.uploader", always_run), - PythonProcess("feedbackd", "openpilot.selfdrive.ui.feedback.feedbackd", only_onroad), # debug procs NativeProcess("bridge", "openpilot/cereal/messaging", ["./bridge"], notcar), From c9b9fd56ea3c55f7e439b155e8e292f628f4ceae Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Jul 2026 18:49:47 -0700 Subject: [PATCH 008/170] gc dead events (#38402) --- openpilot/cereal/log.capnp | 6 ++--- openpilot/selfdrive/selfdrived/events.py | 24 +------------------- openpilot/selfdrive/selfdrived/selfdrived.py | 2 -- 3 files changed, 4 insertions(+), 28 deletions(-) diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp index 60e0bb9010..9f0baf3594 100644 --- a/openpilot/cereal/log.capnp +++ b/openpilot/cereal/log.capnp @@ -75,7 +75,6 @@ struct OnroadEvent @0xc4fa6047f024e718 { driverUnresponsive2 @37; driverUnresponsive3 @38; belowSteerSpeed @39; - lowBattery @40; accFaulted @41; sensorDataInvalid @42; commIssue @43; @@ -107,14 +106,12 @@ struct OnroadEvent @0xc4fa6047f024e718 { noGps @68; wrongCruiseMode @69; modeldLagging @70; - deviceFalling @71; fanMalfunction @72; cameraMalfunction @73; cameraFrameRate @74; processNotRunning @75; dashcamMode @76; selfdriveInitializing @77; - usbError @78; cruiseMismatch @79; canBusMissing @80; selfdrivedLagging @81; @@ -132,7 +129,10 @@ struct OnroadEvent @0xc4fa6047f024e718 { userBookmark @95; excessiveActuation @96; + lowBatteryDEPRECATED @40; soundsUnavailableDEPRECATED @47; + deviceFallingDEPRECATED @71; + usbErrorDEPRECATED @78; audioFeedbackDEPRECATED @97; } } diff --git a/openpilot/selfdrive/selfdrived/events.py b/openpilot/selfdrive/selfdrived/events.py index ee9191fd1e..772b35f7cb 100755 --- a/openpilot/selfdrive/selfdrived/events.py +++ b/openpilot/selfdrive/selfdrived/events.py @@ -392,6 +392,7 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { # ********** events with no alerts ********** + EventName.noGps: {}, EventName.stockFcw: {}, EventName.actuatorsApiUnavailable: {}, @@ -779,9 +780,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { ET.SOFT_DISABLE: soft_disable_alert("Sensor Data Invalid"), }, - EventName.noGps: { - }, - EventName.tooDistracted: { ET.NO_ENTRY: too_distracted_alert, }, @@ -840,11 +838,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { ET.NO_ENTRY: NoEntryAlert("Electronic Stability Control Disabled"), }, - EventName.lowBattery: { - ET.SOFT_DISABLE: soft_disable_alert("Low Battery"), - ET.NO_ENTRY: NoEntryAlert("Low Battery"), - }, - # Different openpilot services communicate between each other at a certain # interval. If communication does not follow the regular schedule this alert # is thrown. This can mean a service crashed, did not broadcast a message for @@ -898,13 +891,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { ET.NO_ENTRY: posenet_invalid_alert, }, - # When the localizer detects an acceleration of more than 40 m/s^2 (~4G) we - # alert the driver the device might have fallen from the windshield. - EventName.deviceFalling: { - ET.SOFT_DISABLE: soft_disable_alert("Device Fell Off Mount"), - ET.NO_ENTRY: NoEntryAlert("Device Fell Off Mount"), - }, - EventName.lowMemory: { ET.SOFT_DISABLE: soft_disable_alert("Low Memory: Reboot Your Device"), ET.PERMANENT: low_memory_alert, @@ -927,14 +913,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { ET.NO_ENTRY: NoEntryAlert("Controls Mismatch"), }, - # Sometimes the USB stack on the device can get into a bad state - # causing the connection to the panda to be lost - EventName.usbError: { - ET.SOFT_DISABLE: soft_disable_alert("USB Error: Reboot Your Device"), - ET.PERMANENT: NormalPermanentAlert("USB Error: Reboot Your Device"), - ET.NO_ENTRY: NoEntryAlert("USB Error: Reboot Your Device"), - }, - # This alert can be thrown for the following reasons: # - No CAN data received at all # - CAN data is received, but some message are not received at the right frequency diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index 9fed889be3..dd94439b95 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -339,8 +339,6 @@ class SelfdriveD: self.events.add(EventName.radarTempUnavailable) elif any(self.sm['radarState'].radarErrors.to_dict().values()): self.events.add(EventName.radarFault) - if not self.sm.valid['pandaStates']: - self.events.add(EventName.usbError) if CS.canTimeout: self.events.add(EventName.canBusMissing) elif not CS.canValid: From 86753ea93db7638994a4f5a1b77cf9ce51221f2a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Jul 2026 21:01:19 -0700 Subject: [PATCH 009/170] better alerts (#38403) * better alerts * just rm that * lil more --- openpilot/selfdrive/selfdrived/events.py | 6 +++--- openpilot/selfdrive/ui/mici/onroad/alert_renderer.py | 11 ++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/openpilot/selfdrive/selfdrived/events.py b/openpilot/selfdrive/selfdrived/events.py index 772b35f7cb..220efd9c18 100755 --- a/openpilot/selfdrive/selfdrived/events.py +++ b/openpilot/selfdrive/selfdrived/events.py @@ -622,7 +622,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { # Thrown when the fan is driven at >50% but is not rotating EventName.fanMalfunction: { - ET.PERMANENT: NormalPermanentAlert("Fan Malfunction", "Likely Hardware Issue"), + ET.PERMANENT: NormalPermanentAlert("Fan Malfunction", "Contact comma.ai/support"), }, # Camera is not outputting frames @@ -773,7 +773,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { EventName.sensorDataInvalid: { ET.PERMANENT: Alert( "Sensor Data Invalid", - "Possible Hardware Issue", + "Contact comma.ai/support", AlertStatus.normal, AlertSize.mid, Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=1.), ET.NO_ENTRY: NoEntryAlert("Sensor Data Invalid"), @@ -965,7 +965,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { # and this alert is thrown. EventName.relayMalfunction: { ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Harness Relay Malfunction"), - ET.PERMANENT: NormalPermanentAlert("Harness Relay Malfunction", "Check Hardware"), + ET.PERMANENT: NormalPermanentAlert("Harness Relay Malfunction", "Contact comma.ai/support"), ET.NO_ENTRY: NoEntryAlert("Harness Relay Malfunction"), }, diff --git a/openpilot/selfdrive/ui/mici/onroad/alert_renderer.py b/openpilot/selfdrive/ui/mici/onroad/alert_renderer.py index e2e398a4e1..12b62924c4 100644 --- a/openpilot/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/openpilot/selfdrive/ui/mici/onroad/alert_renderer.py @@ -296,13 +296,10 @@ class AlertRenderer(Widget): # TODO: hack alert_text1 = alert.text1.lower().replace('calibrating: ', 'calibrating:\n') - can_draw_second_line = False # TODO: there should be a common way to determine font size based on text length to maximize rect if len(alert_text1) <= 12: - can_draw_second_line = True font_size = 92 - 10 elif len(alert_text1) <= 16: - can_draw_second_line = True font_size = 70 else: font_size = 64 - 10 @@ -334,13 +331,13 @@ class AlertRenderer(Widget): self._text_gen_time = time.monotonic() alert_text2 = self._alert_text2_gen or alert_text2 - if can_draw_second_line and alert_text2: + if alert_text2: last_line_h = self._alert_text1_label.rect.y + self._alert_text1_label.get_content_height(int(alert_layout.text_rect.width)) last_line_h -= 4 - if len(alert_text2) > 18: - small_font_size = 36 - elif len(alert_text2) > 24: + if len(alert_text2) > 24: small_font_size = 32 + elif len(alert_text2) > 18: + small_font_size = 36 else: small_font_size = 40 text_rect2 = rl.Rectangle( From 576de9c7ed8906631450be127543139f3f50e3bc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Jul 2026 21:28:21 -0700 Subject: [PATCH 010/170] speed up jotpluggler build (#38406) --- .../jotpluggler/generate_event_extractors.py | 123 ++++++++++++++---- openpilot/tools/jotpluggler/sketch_layout.cc | 4 +- 2 files changed, 103 insertions(+), 24 deletions(-) diff --git a/openpilot/tools/jotpluggler/generate_event_extractors.py b/openpilot/tools/jotpluggler/generate_event_extractors.py index be9bd49003..a424ebc237 100644 --- a/openpilot/tools/jotpluggler/generate_event_extractors.py +++ b/openpilot/tools/jotpluggler/generate_event_extractors.py @@ -62,6 +62,8 @@ class Generator: def __init__(self, event_schema): self.event_schema = event_schema self.fixed_paths = [] + self.event_base_slots = {} + self.static_enums = [] self.tmp_index = 0 self.lines = [] self.emits_memo = {} @@ -103,9 +105,13 @@ class Generator: self.emit(indent, f"append_dynamic_scalar_point({path_expr}, tm, {double_expr}, series);") else: slot = self.add_fixed_path(path) - if kind == "Enum": - self.emit_enum_capture(indent, cxx_string(path), enum_names(schema)) - self.emit(indent, f"append_fixed_scalar_point(&series->fixed_series[{slot}], tm, {double_expr});") + names = enum_names(schema) if kind == "Enum" else [] + if names: + enum_index = len(self.static_enums) + self.static_enums.append(names) + self.emit(indent, f"append_fixed_enum_point({slot}, {enum_index}, tm, {double_expr}, series);") + else: + self.emit(indent, f"append_fixed_scalar_point(&series->fixed_series[{slot}], tm, {double_expr});") return if type_kind == "struct": @@ -144,9 +150,13 @@ class Generator: self.emit(indent, f"if ({' && '.join(conditions)}) {{") indent += 2 - value_var = self.tmp("value") - self.emit(indent, f"const auto {value_var} = {get_call};") - self.emit_node(indent, type_kind, type_proto, value_schema, value_var, field_path, field_path_expr, dynamic_path) + # Scalar getters are only consumed once. Emitting them directly avoids + # thousands of single-use locals in the generated extractor. + value_expr = get_call + if kind is None: + value_expr = self.tmp("value") + self.emit(indent, f"const auto {value_expr} = {get_call};") + self.emit_node(indent, type_kind, type_proto, value_schema, value_expr, field_path, field_path_expr, dynamic_path) if conditions: indent -= 2 @@ -247,31 +257,43 @@ class Generator: self.emit(indent + 2, "}") self.emit(indent, "}") self.emit(indent, "if (skip_raw_can) {") - self.emit(indent + 2, "return true;") + self.emit(indent + 2, "return;") self.emit(indent, "}") - def emit_event_case(self, field_name): + def emit_event_reader(self, field_name): field = self.event_schema.fields[field_name] proto = field.proto type_kind = field_type(field) type_proto = field_type_proto(field) kind = scalar_kind(type_proto) schema = field.schema if kind == "Enum" or type_kind in NESTED_TYPE_KINDS else None - self.emit(4, f"case static_cast({proto.discriminantValue}): {{") valid_slot = self.add_fixed_path(f"/{field_name}/valid") - mono_slot = self.add_fixed_path(f"/{field_name}/logMonoTime") - seconds_slot = self.add_fixed_path(f"/{field_name}/t") - self.emit(6, f"append_fixed_scalar_point(&series->fixed_series[{valid_slot}], tm, event.getValid() ? 1.0 : 0.0);") - self.emit(6, f"append_fixed_scalar_point(&series->fixed_series[{mono_slot}], tm, static_cast(event.getLogMonoTime()));") - self.emit(6, f"append_fixed_scalar_point(&series->fixed_series[{seconds_slot}], tm, tm);") - if field_name in {"can", "sendcan"}: - self.emit_can_special(6, field_name) - if self.node_emits(type_kind, type_proto, schema): + self.add_fixed_path(f"/{field_name}/logMonoTime") + self.add_fixed_path(f"/{field_name}/t") + self.event_base_slots[proto.discriminantValue] = valid_slot + + emits_payload = self.node_emits(type_kind, type_proto, schema) + if field_name not in {"can", "sendcan"} and not emits_payload: + return None + + reader_name = f"append_event_{proto.discriminantValue}" + needs_can = field_name in {"can", "sendcan"} + header_index = len(self.lines) + self.emit(0, "") + if needs_can: + self.emit_can_special(2, field_name) + if emits_payload: payload = self.tmp("payload") - self.emit(6, f"const auto {payload} = event.{accessor('get', field_name)}();") - self.emit_node(6, type_kind, type_proto, schema, payload, f"/{field_name}", None, False) - self.emit(6, "return true;") - self.emit(4, "}") + self.emit(2, f"const auto {payload} = event.{accessor('get', field_name)}();") + self.emit_node(2, type_kind, type_proto, schema, payload, f"/{field_name}", None, False) + self.emit(0, "}") + self.emit(0, "") + if needs_can: + signature = "const cereal::Event::Reader &event, const dbc::Database *can_dbc, bool skip_raw_can, double tm, SeriesAccumulator *series" + else: + signature = "const cereal::Event::Reader &event, double tm, SeriesAccumulator *series" + self.lines[header_index] = f"__attribute__((noinline)) void {reader_name}({signature}) {{" + return reader_name, needs_can def generate(self): self.lines = [] @@ -298,11 +320,66 @@ class Generator: self.emit(2, "}") self.emit(0, "}") self.emit(0, "") + self.emit(0, "__attribute__((noinline)) void append_fixed_enum_point(size_t series_slot, size_t enum_index, double tm, double value, SeriesAccumulator *series);") # noqa: E501 + self.emit(0, "") + + self.emit(0, "// Keep each event payload behind its own optimizer boundary. Combining the") + self.emit(0, "// whole schema into one function creates much more code and runs slower.") + event_readers = {} + for field_name in self.event_schema.union_fields: + event_readers[field_name] = self.emit_event_reader(field_name) + + self.emit(0, "static const std::initializer_list static_event_enum_names[] = {") + for names in self.static_enums: + names_expr = "{" + ", ".join(cxx_string(name) for name in names) + "}" + self.emit(2, f"{names_expr},") + self.emit(0, "};") + self.emit(0, "") + self.emit(0, "__attribute__((noinline)) void append_fixed_enum_point(size_t series_slot, size_t enum_index, double tm, double value, SeriesAccumulator *series) {") # noqa: E501 + self.emit(2, "RouteSeries *fixed_series = &series->fixed_series[series_slot];") + self.emit(2, "capture_static_enum_info(fixed_series->path, static_event_enum_names[enum_index], series);") + self.emit(2, "fixed_series->times.push_back(tm);") + self.emit(2, "fixed_series->values.push_back(value);") + self.emit(0, "}") + self.emit(0, "") + self.emit(0, "bool append_event_static_reader(cereal::Event::Which which, const cereal::Event::Reader &event, const dbc::Database *can_dbc, bool skip_raw_can, double time_offset, SeriesAccumulator *series) {") # noqa: E501 - self.emit(2, "const double tm = static_cast(event.getLogMonoTime()) / 1.0e9 - time_offset;") + self.emit(2, "const auto log_mono_time = event.getLogMonoTime();") + self.emit(2, "const double tm = static_cast(log_mono_time) / 1.0e9 - time_offset;") + + invalid_slot = "static_cast(-1)" + max_discriminant = max(self.event_base_slots) + base_slots = [self.event_base_slots.get(i, invalid_slot) for i in range(max_discriminant + 1)] + self.emit(2, "static constexpr size_t event_base_slots[] = {") + for slot in base_slots: + self.emit(4, f"{slot},") + self.emit(2, "};") + self.emit(2, "const size_t event_index = static_cast(which);") + self.emit(2, "if (event_index >= sizeof(event_base_slots) / sizeof(event_base_slots[0])) {") + self.emit(4, "return false;") + self.emit(2, "}") + self.emit(2, "const size_t base_slot = event_base_slots[event_index];") + self.emit(2, f"if (base_slot == {invalid_slot}) {{") + self.emit(4, "return false;") + self.emit(2, "}") + self.emit(2, "RouteSeries *base_series = &series->fixed_series[base_slot];") + self.emit(2, "base_series[0].times.push_back(tm);") + self.emit(2, "base_series[0].values.push_back(event.getValid() ? 1.0 : 0.0);") + self.emit(2, "base_series[1].times.push_back(tm);") + self.emit(2, "base_series[1].values.push_back(static_cast(log_mono_time));") + self.emit(2, "base_series[2].times.push_back(tm);") + self.emit(2, "base_series[2].values.push_back(tm);") self.emit(2, "switch (which) {") for field_name in self.event_schema.union_fields: - self.emit_event_case(field_name) + field = self.event_schema.fields[field_name] + self.emit(4, f"case static_cast({field.proto.discriminantValue}):") + reader = event_readers[field_name] + if reader is not None: + if reader[1]: + self.emit(6, f"{reader[0]}(event, can_dbc, skip_raw_can, tm, series);") + else: + self.emit(6, f"{reader[0]}(event, tm, series);") + self.emit(6, "return true;") self.emit(4, "default:") self.emit(6, "return false;") self.emit(2, "}") diff --git a/openpilot/tools/jotpluggler/sketch_layout.cc b/openpilot/tools/jotpluggler/sketch_layout.cc index 1f7df72fed..a489160b22 100644 --- a/openpilot/tools/jotpluggler/sketch_layout.cc +++ b/openpilot/tools/jotpluggler/sketch_layout.cc @@ -926,7 +926,9 @@ void append_scalar_point(RouteSeries *series, series->values.push_back(value); } -void append_fixed_scalar_point(RouteSeries *series, double tm, double value) { +// This has thousands of generated call sites. Inlining it duplicates vector +// growth logic throughout the extractor and is slower both to compile and run. +__attribute__((noinline)) void append_fixed_scalar_point(RouteSeries *series, double tm, double value) { series->times.push_back(tm); series->values.push_back(value); } From 75d590fb909b8c93dfe6aec966ae8c1bf55379de Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Jul 2026 22:20:09 -0700 Subject: [PATCH 011/170] replace catch2 tests for 20% faster builds (#38408) * replace catch2 tests * lil more * lil more * rm dep! --- SConstruct | 2 +- msgq_repo | 2 +- openpilot/common/SConscript | 4 +- openpilot/common/tests/native_test.h | 25 +++ openpilot/common/tests/test_runner.cc | 2 - openpilot/common/tests/test_swaglog.cc | 117 +++++--------- openpilot/common/tests/test_util.cc | 147 ------------------ .../pandad/tests/test_pandad_canprotocol.cc | 76 ++++----- openpilot/system/camerad/SConscript | 3 - openpilot/system/camerad/test/test_ae_gray.cc | 84 ---------- openpilot/system/loggerd/SConscript | 3 - openpilot/system/loggerd/tests/test_logger.cc | 75 --------- openpilot/system/loggerd/tests/test_runner.cc | 2 - .../system/loggerd/tests/test_zstd_writer.cc | 44 ------ openpilot/test_native.py | 5 +- openpilot/tools/cabana/SConscript | 3 - openpilot/tools/cabana/tests/test_cabana.cc | 62 +++----- openpilot/tools/cabana/tests/test_runner.cc | 7 - openpilot/tools/replay/SConscript | 3 - openpilot/tools/replay/tests/test_replay.cc | 18 --- pyproject.toml | 1 - uv.lock | 13 -- 22 files changed, 114 insertions(+), 584 deletions(-) create mode 100644 openpilot/common/tests/native_test.h delete mode 100644 openpilot/common/tests/test_runner.cc delete mode 100644 openpilot/common/tests/test_util.cc delete mode 100644 openpilot/system/camerad/test/test_ae_gray.cc delete mode 100644 openpilot/system/loggerd/tests/test_logger.cc delete mode 100644 openpilot/system/loggerd/tests/test_runner.cc delete mode 100644 openpilot/system/loggerd/tests/test_zstd_writer.cc delete mode 100644 openpilot/tools/cabana/tests/test_runner.cc delete mode 100644 openpilot/tools/replay/tests/test_replay.cc diff --git a/SConstruct b/SConstruct index 2a69cd2e38..4538bee69c 100644 --- a/SConstruct +++ b/SConstruct @@ -55,7 +55,7 @@ assert arch in [ "Darwin", # macOS arm64 (x86 not supported) ] -pkg_names = ['acados', 'bzip2', 'capnproto', 'catch2', 'ffmpeg', 'json11', 'ncurses', 'zeromq', 'zstd'] +pkg_names = ['acados', 'bzip2', 'capnproto', 'ffmpeg', 'json11', 'ncurses', 'zeromq', 'zstd'] pkgs = [importlib.import_module(name) for name in pkg_names] acados = pkgs[pkg_names.index('acados')] ffmpeg = pkgs[pkg_names.index('ffmpeg')] diff --git a/msgq_repo b/msgq_repo index a771d031ec..6a8d2e8f06 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit a771d031ec58716824f365a89f0607fd786a1161 +Subproject commit 6a8d2e8f06f7864976d148f3cf423e8b3a418e8a diff --git a/openpilot/common/SConscript b/openpilot/common/SConscript index 13935b80d5..600cbec2e5 100644 --- a/openpilot/common/SConscript +++ b/openpilot/common/SConscript @@ -12,9 +12,7 @@ _common = env.Library('common', common_libs, LIBS="json11") Export('_common') if GetOption('extras'): - env.Program('tests/test_common', - ['tests/test_runner.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc'], - LIBS=[_common, 'json11', 'zmq', 'pthread']) + env.Program('tests/test_swaglog', 'tests/test_swaglog.cc', LIBS=[_common, 'json11', 'zmq', 'pthread']) # Cython bindings params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11']) diff --git a/openpilot/common/tests/native_test.h b/openpilot/common/tests/native_test.h new file mode 100644 index 0000000000..d3dfbb9ce1 --- /dev/null +++ b/openpilot/common/tests/native_test.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +inline void native_test_check(bool condition, const char *expression, const char *file, int line) { + if (!condition) { + throw std::runtime_error(std::string(file) + ":" + std::to_string(line) + ": check failed: " + expression); + } +} + +#define CHECK(condition) native_test_check(static_cast(condition), #condition, __FILE__, __LINE__) +#define REQUIRE(...) CHECK((__VA_ARGS__)) + +template +int run_native_test(Function &&function) { + try { + function(); + return 0; + } catch (const std::exception &error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/openpilot/common/tests/test_runner.cc b/openpilot/common/tests/test_runner.cc deleted file mode 100644 index 62bf7476a1..0000000000 --- a/openpilot/common/tests/test_runner.cc +++ /dev/null @@ -1,2 +0,0 @@ -#define CATCH_CONFIG_MAIN -#include "catch2/catch.hpp" diff --git a/openpilot/common/tests/test_swaglog.cc b/openpilot/common/tests/test_swaglog.cc index 0c9cfcc1e6..07e2b16bc1 100644 --- a/openpilot/common/tests/test_swaglog.cc +++ b/openpilot/common/tests/test_swaglog.cc @@ -1,88 +1,47 @@ +#include +#include + #include -#include - -#include "catch2/catch.hpp" -#include "common/swaglog.h" -#include "common/util.h" -#include "common/version.h" #include "common/hardware/hw.h" +#include "common/swaglog.h" +#include "common/tests/native_test.h" #include "json11/json11.hpp" -std::string daemon_name = "testy"; -std::string dongle_id = "test_dongle_id"; -int LINE_NO = 0; +void test_swaglog() { + setenv("MANAGER_DAEMON", "swaglog_test", 1); + setenv("DONGLE_ID", "test_dongle_id", 1); + setenv("CLEAN", "1", 1); -void log_thread(int thread_id, int msg_cnt) { - for (int i = 0; i < msg_cnt; ++i) { - LOGD("%d", thread_id); - LINE_NO = __LINE__ - 1; - usleep(1); - } + void *context = zmq_ctx_new(); + CHECK(context != nullptr); + void *socket = zmq_socket(context, ZMQ_PULL); + CHECK(socket != nullptr); + int timeout = 5000; + CHECK(zmq_setsockopt(socket, ZMQ_RCVTIMEO, &timeout, sizeof(timeout)) == 0); + CHECK(zmq_bind(socket, Path::swaglog_ipc().c_str()) == 0); + + LOGD("native-cpp-log"); + + char buffer[4096] = {}; + const int size = zmq_recv(socket, buffer, sizeof(buffer), 0); + CHECK(size > 1); + CHECK(buffer[0] == CLOUDLOG_DEBUG); + std::string error; + const auto message = json11::Json::parse(std::string(buffer + 1, size - 1), error); + CHECK(error.empty()); + CHECK(message["levelnum"].int_value() == CLOUDLOG_DEBUG); + CHECK(message["msg"].string_value() == "native-cpp-log"); + CHECK(message["funcname"].string_value() == "test_swaglog"); + CHECK(message["filename"].string_value().find("test_swaglog.cc") != std::string::npos); + CHECK(message["ctx"]["daemon"].string_value() == "swaglog_test"); + CHECK(message["ctx"]["dongle_id"].string_value() == "test_dongle_id"); + CHECK(message["ctx"]["dirty"].bool_value() == false); + + CHECK(zmq_close(socket) == 0); + CHECK(zmq_ctx_destroy(context) == 0); } -void recv_log(int thread_cnt, int thread_msg_cnt) { - void *zctx = zmq_ctx_new(); - void *sock = zmq_socket(zctx, ZMQ_PULL); - zmq_bind(sock, Path::swaglog_ipc().c_str()); - std::vector thread_msgs(thread_cnt); - int total_count = 0; - - for (auto start = std::chrono::steady_clock::now(), now = start; - now < start + std::chrono::seconds{1} && total_count < (thread_cnt * thread_msg_cnt); - now = std::chrono::steady_clock::now()) { - char buf[4096] = {}; - if (zmq_recv(sock, buf, sizeof(buf), ZMQ_DONTWAIT) <= 0) { - if (errno == EAGAIN || errno == EINTR || errno == EFSM) continue; - break; - } - - REQUIRE(buf[0] == CLOUDLOG_DEBUG); - std::string err; - auto msg = json11::Json::parse(buf + 1, err); - REQUIRE(!msg.is_null()); - - REQUIRE(msg["levelnum"].int_value() == CLOUDLOG_DEBUG); - REQUIRE_THAT(msg["filename"].string_value(), Catch::Contains("test_swaglog.cc")); - REQUIRE(msg["funcname"].string_value() == "log_thread"); - REQUIRE(msg["lineno"].int_value() == LINE_NO); - - auto ctx = msg["ctx"]; - - REQUIRE(ctx["daemon"].string_value() == daemon_name); - REQUIRE(ctx["dongle_id"].string_value() == dongle_id); - REQUIRE(ctx["dirty"].bool_value() == true); - - REQUIRE(ctx["version"].string_value() == COMMA_VERSION); - - std::string device = Hardware::get_name(); - REQUIRE(ctx["device"].string_value() == device); - - int thread_id = atoi(msg["msg"].string_value().c_str()); - REQUIRE((thread_id >= 0 && thread_id < thread_cnt)); - thread_msgs[thread_id]++; - total_count++; - } - for (int i = 0; i < thread_cnt; ++i) { - INFO("thread :" << i); - REQUIRE(thread_msgs[i] == thread_msg_cnt); - } - zmq_close(sock); - zmq_ctx_destroy(zctx); -} - -TEST_CASE("swaglog") { - setenv("MANAGER_DAEMON", daemon_name.c_str(), 1); - setenv("DONGLE_ID", dongle_id.c_str(), 1); - setenv("dirty", "1", 1); - const int thread_cnt = 5; - const int thread_msg_cnt = 100; - - std::vector log_threads; - for (int i = 0; i < thread_cnt; ++i) { - log_threads.push_back(std::thread(log_thread, i, thread_msg_cnt)); - } - for (auto &t : log_threads) t.join(); - - recv_log(thread_cnt, thread_msg_cnt); +int main() { + return run_native_test(test_swaglog); } diff --git a/openpilot/common/tests/test_util.cc b/openpilot/common/tests/test_util.cc deleted file mode 100644 index d927b98a4d..0000000000 --- a/openpilot/common/tests/test_util.cc +++ /dev/null @@ -1,147 +0,0 @@ - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "catch2/catch.hpp" -#include "common/util.h" - -std::string random_bytes(int size) { - std::random_device rd; - std::independent_bits_engine rbe(rd()); - std::string bytes(size + 1, '\0'); - std::generate(bytes.begin(), bytes.end(), std::ref(rbe)); - return bytes; -} - -TEST_CASE("util::read_file") { - SECTION("read /proc/version") { - std::string ret = util::read_file("/proc/version"); - REQUIRE(ret.find("Linux version") != std::string::npos); - } - SECTION("read from sysfs") { - std::string ret = util::read_file("/sys/power/wakeup_count"); - REQUIRE(!ret.empty()); - } - SECTION("read file") { - char filename[] = "/tmp/test_read_XXXXXX"; - int fd = mkstemp(filename); - - REQUIRE(util::read_file(filename).empty()); - - std::string content = random_bytes(64 * 1024); - REQUIRE(write(fd, content.c_str(), content.size()) == (ssize_t)content.size()); - std::string ret = util::read_file(filename); - bool equal = (ret == content); - REQUIRE(equal); - close(fd); - } - SECTION("read directory") { - REQUIRE(util::read_file(".").empty()); - } - SECTION("read non-existent file") { - std::string ret = util::read_file("does_not_exist"); - REQUIRE(ret.empty()); - } - SECTION("read non-permission") { - REQUIRE(util::read_file("/proc/kmsg").empty()); - } -} - -TEST_CASE("util::file_exists") { - char filename[] = "/tmp/test_file_exists_XXXXXX"; - int fd = mkstemp(filename); - REQUIRE(fd != -1); - close(fd); - - SECTION("existent file") { - REQUIRE(util::file_exists(filename)); - REQUIRE(util::file_exists("/tmp")); - } - SECTION("nonexistent file") { - std::string fn = filename; - REQUIRE(!util::file_exists(fn + "/nonexistent")); - } - SECTION("file has no access permissions") { - std::string fn = "/proc/kmsg"; - std::ifstream f(fn); - REQUIRE(f.good() == false); - REQUIRE(util::file_exists(fn)); - } - ::remove(filename); -} - -TEST_CASE("util::read_files_in_dir") { - char tmp_path[] = "/tmp/test_XXXXXX"; - const std::string test_path = mkdtemp(tmp_path); - const std::string files[] = {".test1", "'test2'", "test3"}; - for (auto fn : files) { - std::ofstream{test_path + "/" + fn} << fn; - } - mkdir((test_path + "/dir").c_str(), 0777); - - std::map result = util::read_files_in_dir(test_path); - REQUIRE(result.find("dir") == result.end()); - REQUIRE(result.size() == std::size(files)); - for (auto& [k, v] : result) { - REQUIRE(k == v); - } -} - - -TEST_CASE("util::safe_fwrite") { - char filename[] = "/tmp/XXXXXX"; - int fd = mkstemp(filename); - close(fd); - std::string dat = random_bytes(1024 * 1024); - - FILE *f = util::safe_fopen(filename, "wb"); - REQUIRE(f != nullptr); - size_t size = util::safe_fwrite(dat.data(), 1, dat.size(), f); - REQUIRE(size == dat.size()); - int ret = util::safe_fflush(f); - REQUIRE(ret == 0); - ret = fclose(f); - REQUIRE(ret == 0); - bool equal = (dat == util::read_file(filename)); - REQUIRE(equal); -} - -TEST_CASE("util::create_directories") { - REQUIRE(system("rm /tmp/test_create_directories -rf") == 0); - std::string dir = "/tmp/test_create_directories/a/b/c/d/e/f"; - - auto check_dir_permissions = [](const std::string &path, mode_t mode) -> bool { - struct stat st = {}; - return stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == mode; - }; - - SECTION("create_directories") { - REQUIRE(util::create_directories(dir, 0755)); - REQUIRE(check_dir_permissions(dir, 0755)); - } - SECTION("dir already exists") { - REQUIRE(util::create_directories(dir, 0755)); - REQUIRE(util::create_directories(dir, 0755)); - } - SECTION("a file exists with the same name") { - REQUIRE(util::create_directories(dir, 0755)); - int f = open((dir + "/file").c_str(), O_RDWR | O_CREAT, 0644); - REQUIRE(f != -1); - close(f); - REQUIRE(util::create_directories(dir + "/file", 0755) == false); - REQUIRE(util::create_directories(dir + "/file/1/2/3", 0755) == false); - } - SECTION("end with slashes") { - REQUIRE(util::create_directories(dir + "/", 0755)); - } - SECTION("empty") { - REQUIRE(util::create_directories("", 0755) == false); - } -} diff --git a/openpilot/selfdrive/pandad/tests/test_pandad_canprotocol.cc b/openpilot/selfdrive/pandad/tests/test_pandad_canprotocol.cc index 83339a4c1c..50bbc51f49 100644 --- a/openpilot/selfdrive/pandad/tests/test_pandad_canprotocol.cc +++ b/openpilot/selfdrive/pandad/tests/test_pandad_canprotocol.cc @@ -1,11 +1,7 @@ -#define CATCH_CONFIG_MAIN -#define CATCH_CONFIG_ENABLE_BENCHMARKING - #include -#include "catch2/catch.hpp" +#include "common/tests/native_test.h" #include "openpilot/cereal/messaging/messaging.h" -#include "common/util.h" #include "selfdrive/pandad/panda.h" struct PandaTest : public Panda { @@ -26,12 +22,9 @@ PandaTest::PandaTest(int can_list_size_, cereal::PandaState::PandaType hw_type_) int data_limit = ((hw_type == cereal::PandaState::PandaType::RED_PANDA) ? std::size(dlc_to_len) : 8); // prepare test data for (int i = 0; i < data_limit; ++i) { - std::random_device rd; - std::independent_bits_engine rbe(rd()); - int data_len = dlc_to_len[i]; std::string bytes(data_len, '\0'); - std::generate(bytes.begin(), bytes.end(), std::ref(rbe)); + for (int j = 0; j < data_len; ++j) bytes[j] = static_cast((i * 31 + j) & 0xff); test_data[data_len] = bytes; } @@ -39,16 +32,15 @@ PandaTest::PandaTest(int can_list_size_, cereal::PandaState::PandaType hw_type_) auto can_list = msg.initEvent().initSendcan(can_list_size); for (uint8_t i = 0; i < can_list_size; ++i) { auto can = can_list[i]; - uint32_t id = util::random_int(0, std::size(dlc_to_len) - 1); + uint32_t id = i % data_limit; const std::string &dat = test_data[dlc_to_len[id]]; can.setAddress(i); - can.setSrc(util::random_int(0, 2)); + can.setSrc(i % 3); can.setDat(kj::ArrayPtr((uint8_t *)dat.data(), dat.size())); total_pakets_size += sizeof(can_header) + dat.size(); } can_data_list = can_list.asReader(); - INFO("test " << can_list_size << " packets, total size " << total_pakets_size); } void PandaTest::test_can_send() { @@ -56,30 +48,29 @@ void PandaTest::test_can_send() { this->pack_can_buffer(can_data_list, [&](uint8_t *chunk, size_t size) { unpacked_data.insert(unpacked_data.end(), chunk, &chunk[size]); }); - REQUIRE(unpacked_data.size() == total_pakets_size); + CHECK(unpacked_data.size() == total_pakets_size); int cnt = 0; - INFO("test can message integrity"); for (int pos = 0, pckt_len = 0; pos < unpacked_data.size(); pos += pckt_len) { can_header header; memcpy(&header, &unpacked_data[pos], sizeof(can_header)); const uint8_t data_len = dlc_to_len[header.data_len_code]; pckt_len = sizeof(can_header) + data_len; - REQUIRE(header.addr == cnt); - REQUIRE(test_data.find(data_len) != test_data.end()); + CHECK(header.addr == cnt); + CHECK(test_data.find(data_len) != test_data.end()); const std::string &dat = test_data[data_len]; - REQUIRE(memcmp(dat.data(), &unpacked_data[pos + sizeof(can_header)], dat.size()) == 0); + CHECK(memcmp(dat.data(), &unpacked_data[pos + sizeof(can_header)], dat.size()) == 0); ++cnt; } - REQUIRE(cnt == can_list_size); + CHECK(cnt == can_list_size); } void PandaTest::test_can_recv(uint32_t rx_chunk_size) { std::vector frames; this->pack_can_buffer(can_data_list, [&](uint8_t *data, uint32_t size) { if (rx_chunk_size == 0) { - REQUIRE(this->unpack_can_buffer(data, size, frames)); + CHECK(this->unpack_can_buffer(data, size, frames)); } else { this->receive_buffer_size = 0; uint32_t pos = 0; @@ -90,46 +81,35 @@ void PandaTest::test_can_recv(uint32_t rx_chunk_size) { this->receive_buffer_size += chunk_size; pos += chunk_size; - REQUIRE(this->unpack_can_buffer(this->receive_buffer, this->receive_buffer_size, frames)); + CHECK(this->unpack_can_buffer(this->receive_buffer, this->receive_buffer_size, frames)); } } }); - REQUIRE(frames.size() == can_list_size); + CHECK(frames.size() == can_list_size); for (int i = 0; i < frames.size(); ++i) { - REQUIRE(frames[i].address == i); - REQUIRE(test_data.find(frames[i].dat.size()) != test_data.end()); + CHECK(frames[i].address == i); + CHECK(test_data.find(frames[i].dat.size()) != test_data.end()); const std::string &dat = test_data[frames[i].dat.size()]; - REQUIRE(memcmp(dat.data(), frames[i].dat.data(), dat.size()) == 0); + CHECK(memcmp(dat.data(), frames[i].dat.data(), dat.size()) == 0); } } -TEST_CASE("send/recv CAN 2.0 packets") { - auto can_list_size = GENERATE(1, 3, 5, 10, 30, 60, 100, 200); - PandaTest test(can_list_size, cereal::PandaState::PandaType::DOS); +void test_can_protocol() { + for (auto hw_type : {cereal::PandaState::PandaType::DOS, cereal::PandaState::PandaType::RED_PANDA}) { + for (int can_list_size : {1, 3, 5, 10, 30, 60, 100, 200}) { + PandaTest send_test(can_list_size, hw_type); + send_test.test_can_send(); - SECTION("can_send") { - test.test_can_send(); - } - SECTION("can_receive") { - test.test_can_recv(); - } - SECTION("chunked_can_receive") { - test.test_can_recv(0x40); + PandaTest receive_test(can_list_size, hw_type); + receive_test.test_can_recv(); + + PandaTest chunked_receive_test(can_list_size, hw_type); + chunked_receive_test.test_can_recv(0x40); + } } } -TEST_CASE("send/recv CAN FD packets") { - auto can_list_size = GENERATE(1, 3, 5, 10, 30, 60, 100, 200); - PandaTest test(can_list_size, cereal::PandaState::PandaType::RED_PANDA); - - SECTION("can_send") { - test.test_can_send(); - } - SECTION("can_receive") { - test.test_can_recv(); - } - SECTION("chunked_can_receive") { - test.test_can_recv(0x40); - } +int main() { + return run_native_test(test_can_protocol); } diff --git a/openpilot/system/camerad/SConscript b/openpilot/system/camerad/SConscript index c28330b32c..e6bc3f2bfb 100644 --- a/openpilot/system/camerad/SConscript +++ b/openpilot/system/camerad/SConscript @@ -6,6 +6,3 @@ if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', 'cameras/cdm.cc', 'sensors/ox03c10.cc', 'sensors/os04c10.cc']) env.Program('camerad', ['main.cc', camera_obj], LIBS=libs) - -if GetOption("extras") and arch == "x86_64": - env.Program('test/test_ae_gray', ['test/test_ae_gray.cc', camera_obj], LIBS=libs) diff --git a/openpilot/system/camerad/test/test_ae_gray.cc b/openpilot/system/camerad/test/test_ae_gray.cc deleted file mode 100644 index 39c3d9c4e5..0000000000 --- a/openpilot/system/camerad/test/test_ae_gray.cc +++ /dev/null @@ -1,84 +0,0 @@ -#define CATCH_CONFIG_MAIN -#include "catch2/catch.hpp" - -#include - -#include -#include - -#include "common/util.h" -#include "system/camerad/cameras/camera_common.h" - -#define W 240 -#define H 160 - - -#define TONE_SPLITS 3 - -float gts[TONE_SPLITS * TONE_SPLITS * TONE_SPLITS * TONE_SPLITS] = { - 0.917969, 0.917969, 0.375000, 0.917969, 0.375000, 0.375000, 0.187500, 0.187500, 0.187500, 0.917969, - 0.375000, 0.375000, 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.093750, 0.093750, - 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.917969, 0.375000, 0.375000, - 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.093750, 0.093750, 0.093750, 0.093750, - 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, - 0.093750, 0.093750, 0.093750, 0.093750, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, - 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, - 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, - 0.000000}; - - -TEST_CASE("camera.test_calculate_exposure_value") { - // set up fake camerabuf - CameraBuf cb = {}; - VisionBuf vb = {}; - uint8_t * fb_y = new uint8_t[W*H]; - vb.y = fb_y; - cb.cur_yuv_buf = &vb; - cb.out_img_width = W; - cb.out_img_height = H; - Rect rect = {0, 0, W-1, H-1}; - - printf("AE test patterns %dx%d\n", cb.out_img_width, cb.out_img_height); - - // mix of 5 tones - uint8_t l[5] = {0, 24, 48, 96, 235}; // 235 is yuv max - - bool passed = true; - float rtol = 0.05; - // generate pattern and calculate EV - int cnt = 0; - for (int i_0=0; i_0 rtol*evgt) { - passed = false; - } - - // report - printf("%d/%d/%d/%d/%d: ev %f, gt %f, err %f\n", h_0, h_1, h_2, h_3, h_4, ev, evgt, fabs(ev - evgt) / (evgt != 0 ? evgt : 0.00001f)); - cnt++; - } - } - } - } - assert(passed); - - delete[] fb_y; -} diff --git a/openpilot/system/loggerd/SConscript b/openpilot/system/loggerd/SConscript index 7f6d4faf0c..0222803f1d 100644 --- a/openpilot/system/loggerd/SConscript +++ b/openpilot/system/loggerd/SConscript @@ -17,6 +17,3 @@ libs.insert(0, logger_lib) env.Program('loggerd', ['loggerd.cc'], LIBS=libs, FRAMEWORKS=frameworks) env.Program('encoderd', ['encoderd.cc'], LIBS=libs, FRAMEWORKS=frameworks) env.Program('bootlog.cc', LIBS=libs, FRAMEWORKS=frameworks) - -if GetOption('extras'): - env.Program('tests/test_logger', ['tests/test_runner.cc', 'tests/test_logger.cc', 'tests/test_zstd_writer.cc'], LIBS=libs) diff --git a/openpilot/system/loggerd/tests/test_logger.cc b/openpilot/system/loggerd/tests/test_logger.cc deleted file mode 100644 index 61509c256c..0000000000 --- a/openpilot/system/loggerd/tests/test_logger.cc +++ /dev/null @@ -1,75 +0,0 @@ -#include "catch2/catch.hpp" -#include "system/loggerd/logger.h" - -typedef cereal::Sentinel::SentinelType SentinelType; - -void verify_segment(const std::string &route_path, int segment, int max_segment, int required_event_cnt) { - const std::string segment_path = route_path + "--" + std::to_string(segment); - SentinelType begin_sentinel = segment == 0 ? SentinelType::START_OF_ROUTE : SentinelType::START_OF_SEGMENT; - SentinelType end_sentinel = segment == max_segment - 1 ? SentinelType::END_OF_ROUTE : SentinelType::END_OF_SEGMENT; - - REQUIRE(!util::file_exists(segment_path + "/rlog.lock")); - for (const char *fn : {"/rlog.zst", "/qlog.zst"}) { - const std::string log_file = segment_path + fn; - std::string log = util::read_file(log_file); - REQUIRE(!log.empty()); - std::string decompressed_log = zstd_decompress(log); - int event_cnt = 0, i = 0; - kj::ArrayPtr words((capnp::word *)decompressed_log.data(), decompressed_log.size() / sizeof(capnp::word)); - while (words.size() > 0) { - try { - capnp::FlatArrayMessageReader reader(words); - auto event = reader.getRoot(); - words = kj::arrayPtr(reader.getEnd(), words.end()); - if (i == 0) { - REQUIRE(event.which() == cereal::Event::INIT_DATA); - } else if (i == 1) { - REQUIRE(event.which() == cereal::Event::SENTINEL); - REQUIRE(event.getSentinel().getType() == begin_sentinel); - REQUIRE(event.getSentinel().getSignal() == 0); - } else if (words.size() > 0) { - REQUIRE(event.which() == cereal::Event::CLOCKS); - ++event_cnt; - } else { - // the last event must be SENTINEL - REQUIRE(event.which() == cereal::Event::SENTINEL); - REQUIRE(event.getSentinel().getType() == end_sentinel); - REQUIRE(event.getSentinel().getSignal() == (end_sentinel == SentinelType::END_OF_ROUTE ? 1 : 0)); - } - ++i; - } catch (const kj::Exception &ex) { - INFO("failed parse " << i << " exception :" << ex.getDescription()); - REQUIRE(0); - break; - } - } - REQUIRE(event_cnt == required_event_cnt); - } -} - -void write_msg(LoggerState *logger) { - MessageBuilder msg; - msg.initEvent().initClocks(); - logger->write(msg.toBytes(), true); -} - -TEST_CASE("logger") { - const int segment_cnt = 100; - const std::string log_root = "/tmp/test_logger"; - REQUIRE(system(("rm " + log_root + " -rf").c_str()) == 0); - std::string route_name; - { - LoggerState logger(log_root); - route_name = logger.routeName(); - for (int i = 0; i < segment_cnt; ++i) { - REQUIRE(logger.next()); - REQUIRE(util::file_exists(logger.segmentPath() + "/rlog.lock")); - REQUIRE(logger.segment() == i); - write_msg(&logger); - } - logger.setExitSignal(1); - } - for (int i = 0; i < segment_cnt; ++i) { - verify_segment(log_root + "/" + route_name, i, segment_cnt, 1); - } -} diff --git a/openpilot/system/loggerd/tests/test_runner.cc b/openpilot/system/loggerd/tests/test_runner.cc deleted file mode 100644 index 62bf7476a1..0000000000 --- a/openpilot/system/loggerd/tests/test_runner.cc +++ /dev/null @@ -1,2 +0,0 @@ -#define CATCH_CONFIG_MAIN -#include "catch2/catch.hpp" diff --git a/openpilot/system/loggerd/tests/test_zstd_writer.cc b/openpilot/system/loggerd/tests/test_zstd_writer.cc deleted file mode 100644 index 479e866a14..0000000000 --- a/openpilot/system/loggerd/tests/test_zstd_writer.cc +++ /dev/null @@ -1,44 +0,0 @@ -#include - -#include -#include -#include - -#include "common/util.h" -#include "system/loggerd/logger.h" -#include "system/loggerd/zstd_writer.h" - -TEST_CASE("ZstdFileWriter writes and compresses data correctly in loops", "[ZstdFileWriter]") { - const std::string filename = "test_zstd_file.zst"; - const int iterations = 100; - const size_t dataSize = 1024; - - std::string totalTestData; - - // Step 1: Write compressed data to file in a loop - { - ZstdFileWriter writer(filename, LOG_COMPRESSION_LEVEL); - // Write various data sizes including edge cases - std::vector testSizes = {dataSize, 1, 0, dataSize * 2}; // Normal, minimal, empty, large - for (int i = 0; i < iterations; ++i) { - size_t currentSize = testSizes[i % testSizes.size()]; - std::string testData = util::random_string(currentSize); - totalTestData.append(testData); - - writer.write((void *)testData.c_str(), testData.size()); - } - } - - // Step 2: Decompress the file and verify the data - auto compressedContent = util::read_file(filename); - REQUIRE(compressedContent.size() > 0); - REQUIRE(compressedContent.size() < totalTestData.size()); - std::string decompressedData = zstd_decompress(compressedContent); - - // Step 3: Verify that the decompressed data matches the original accumulated data - REQUIRE(decompressedData.size() == totalTestData.size()); - REQUIRE(std::memcmp(decompressedData.data(), totalTestData.c_str(), totalTestData.size()) == 0); - - // Clean up the test file - std::remove(filename.c_str()); -} diff --git a/openpilot/test_native.py b/openpilot/test_native.py index a69771f675..eed549f4e4 100644 --- a/openpilot/test_native.py +++ b/openpilot/test_native.py @@ -7,12 +7,9 @@ from openpilot.common.test import OpenpilotTestCase NATIVE_TESTS = ( - "openpilot/common/tests/test_common", + "openpilot/common/tests/test_swaglog", "openpilot/selfdrive/pandad/tests/test_pandad_canprotocol", - "openpilot/system/loggerd/tests/test_logger", - "openpilot/tools/cabana/tests/test_cabana", "openpilot/tools/cabana/tests/test_dbc_core", - "openpilot/tools/replay/tests/test_replay", ) diff --git a/openpilot/tools/cabana/SConscript b/openpilot/tools/cabana/SConscript index fcaa3b7930..805bc41876 100644 --- a/openpilot/tools/cabana/SConscript +++ b/openpilot/tools/cabana/SConscript @@ -110,14 +110,11 @@ cabana_lib = cabana_env.Library("cabana_lib", cabana_srcs + [bootstrap_icons_src cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): - cabana_env.Program('tests/test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs]) - # This target deliberately uses the base environment and links no Qt libraries. # It prevents Qt dependencies from creeping back into the DBC core. dbc_core_test_env = env.Clone() dbc_core_test_env['CXXFLAGS'] += [opendbc_path] dbc_core_test_objects = [ - dbc_core_test_env.Object('tests/dbc_core_test_runner', 'tests/test_runner.cc'), dbc_core_test_env.Object('tests/dbc_core_tests', 'tests/test_cabana.cc'), dbc_core_test_env.Object('tests/dbc_core_model', 'dbc/dbc.cc'), dbc_core_test_env.Object('tests/dbc_core_file', 'dbc/dbcfile.cc'), diff --git a/openpilot/tools/cabana/tests/test_cabana.cc b/openpilot/tools/cabana/tests/test_cabana.cc index c66f57d593..53be1b0afa 100644 --- a/openpilot/tools/cabana/tests/test_cabana.cc +++ b/openpilot/tools/cabana/tests/test_cabana.cc @@ -1,20 +1,14 @@ -#undef INFO #include #include -#include "catch2/catch.hpp" +#include "common/tests/native_test.h" #include "tools/cabana/dbc/dbcfile.h" #include "tools/cabana/dbc/dbcmanager.h" -#include "tools/cabana/core/settings.h" - -#ifdef QT_CORE_LIB -#include -#endif const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; -TEST_CASE("DBCFile::generateDBC") { +void test_generate_dbc() { std::string fn = std::string(OPENDBC_FILE_PATH) + "/tesla_can.dbc"; DBCFile dbc_origin(fn); DBCFile dbc_from_generated("", dbc_origin.generateDBC()); @@ -35,7 +29,7 @@ TEST_CASE("DBCFile::generateDBC") { } } -TEST_CASE("DBCFile::generateDBC - comment order") { +void test_comment_order() { // Ensure that message comments are followed by signal comments and in the correct order std::string content = R"(BO_ 160 message_1: 8 EON SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX @@ -52,7 +46,7 @@ CM_ SG_ 162 signal_2 "signal comment"; REQUIRE(dbc.generateDBC() == content); } -TEST_CASE("DBCFile::generateDBC -- preserve original header") { +void test_preserve_original_header() { std::string content = R"(VERSION "1.0" NS_ : @@ -72,7 +66,7 @@ CM_ SG_ 160 signal_1 "signal comment"; REQUIRE(dbc.generateDBC() == content); } -TEST_CASE("DBCFile::generateDBC - escaped quotes") { +void test_escaped_quotes() { std::string content = R"(BO_ 160 message_1: 8 EON SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX @@ -83,7 +77,7 @@ CM_ SG_ 160 signal_1 "signal comment with \"escaped quotes\""; REQUIRE(dbc.generateDBC() == content); } -TEST_CASE("parse_dbc") { +void test_parse_dbc() { std::string content = R"( BO_ 160 message_1: 8 EON SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX @@ -149,7 +143,7 @@ CM_ SG_ 162 signal_1 "signal comment with \"escaped quotes\""; REQUIRE(msg->sigs[0]->comment == "signal comment with \"escaped quotes\""); } -TEST_CASE("parse_opendbc") { +void test_parse_opendbc() { std::vector errors; for (const auto &entry : std::filesystem::directory_iterator(OPENDBC_FILE_PATH)) { if (!entry.is_regular_file() || entry.path().extension() != ".dbc") continue; @@ -161,11 +155,11 @@ TEST_CASE("parse_opendbc") { } std::ostringstream details; for (const auto &error : errors) details << error << '\n'; - INFO(details.str()); + if (!errors.empty()) std::cerr << details.str(); REQUIRE(errors.empty()); } -TEST_CASE("DBCManager core callbacks") { +void test_dbc_manager() { DBCManager manager; int files_changed = 0; int signals_added = 0; @@ -192,34 +186,16 @@ TEST_CASE("DBCManager core callbacks") { REQUIRE(manager.msg({.source = 0, .address = 160})->sig("speed") != nullptr); } -TEST_CASE("Cabana settings core defaults") { - CabanaSettingsState state; - REQUIRE(state.fps == 10); - REQUIRE(state.chart_range == 180); - REQUIRE(state.drag_direction == CabanaSettingsState::MsbFirst); - REQUIRE(state.recent_files.empty()); +void test_cabana_core() { + test_generate_dbc(); + test_comment_order(); + test_preserve_original_header(); + test_escaped_quotes(); + test_parse_dbc(); + test_parse_opendbc(); + test_dbc_manager(); } -#ifdef QT_CORE_LIB -TEST_CASE("CabanaColor preserves QColor transformations") { - const std::vector colors = { - QColor(102, 86, 169, 64), QColor(0, 187, 255, 128), QColor(255, 0, 0, 128), QColor(45, 120, 75, 255), - }; - for (const auto &qt_color : colors) { - CabanaColor color(qt_color.red(), qt_color.green(), qt_color.blue(), qt_color.alpha()); - for (int factor : {75, 100, 135, 150, 200}) { - const auto lighter = color.lighter(factor); - const auto qt_lighter = qt_color.lighter(factor); - CHECK(std::abs(lighter.red() - qt_lighter.red()) <= 1); - CHECK(std::abs(lighter.green() - qt_lighter.green()) <= 1); - CHECK(std::abs(lighter.blue() - qt_lighter.blue()) <= 1); - - const auto darker = color.darker(factor); - const auto qt_darker = qt_color.darker(factor); - CHECK(std::abs(darker.red() - qt_darker.red()) <= 1); - CHECK(std::abs(darker.green() - qt_darker.green()) <= 1); - CHECK(std::abs(darker.blue() - qt_darker.blue()) <= 1); - } - } +int main() { + return run_native_test(test_cabana_core); } -#endif diff --git a/openpilot/tools/cabana/tests/test_runner.cc b/openpilot/tools/cabana/tests/test_runner.cc deleted file mode 100644 index a76c8e16b9..0000000000 --- a/openpilot/tools/cabana/tests/test_runner.cc +++ /dev/null @@ -1,7 +0,0 @@ -#define CATCH_CONFIG_RUNNER -#include "catch2/catch.hpp" - -int main(int argc, char **argv) { - const int res = Catch::Session().run(argc, argv); - return (res < 0xff ? res : 0xff); -} diff --git a/openpilot/tools/replay/SConscript b/openpilot/tools/replay/SConscript index 643de97bb9..c5abae502c 100644 --- a/openpilot/tools/replay/SConscript +++ b/openpilot/tools/replay/SConscript @@ -14,6 +14,3 @@ replay_lib = replay_env.Library("replay", replay_lib_src, LIBS=base_libs, FRAMEW Export('replay_lib') replay_libs = [replay_lib] + ffmpeg_libs + ['bz2', 'zstd', 'ncurses'] + base_libs replay_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) - -if GetOption('extras'): - replay_env.Program('tests/test_replay', ['tests/test_replay.cc'], LIBS=replay_libs) diff --git a/openpilot/tools/replay/tests/test_replay.cc b/openpilot/tools/replay/tests/test_replay.cc deleted file mode 100644 index 45fcc98191..0000000000 --- a/openpilot/tools/replay/tests/test_replay.cc +++ /dev/null @@ -1,18 +0,0 @@ -#define CATCH_CONFIG_MAIN -#include "catch2/catch.hpp" -#include "tools/replay/filereader.h" -#include "tools/replay/replay.h" - -const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; - -TEST_CASE("LogReader") { - SECTION("corrupt log") { - FileReader reader(true); - std::string corrupt_content = reader.read(TEST_RLOG_URL); - corrupt_content.resize(corrupt_content.length() / 2); - corrupt_content = decompressBZ2(corrupt_content); - LogReader log; - REQUIRE(log.load(corrupt_content.data(), corrupt_content.size())); - REQUIRE(log.events.size() > 0); - } -} diff --git a/pyproject.toml b/pyproject.toml index 68c5dd7fe7..678720aa04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,6 @@ dependencies = [ # vendored native dependencies "comma-deps-capnproto", - "comma-deps-catch2", "comma-deps-acados", "comma-deps-ffmpeg", "comma-deps-zstd", diff --git a/uv.lock b/uv.lock index ff155ab3bc..de03b28288 100644 --- a/uv.lock +++ b/uv.lock @@ -142,14 +142,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/80/a9/f61fe62045c4ea867f51f2d74b4edfabd6c272c62a3281a9f1f33825a725/comma_deps_capnproto-1.0.1.post93-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:f98cdba8f8c7f08a7a0c0a4b7cc0bfcf515e29ad8f1b5cb8eda4e253bef5e6e3", size = 2590769, upload-time = "2026-07-08T19:30:49.728Z" }, ] -[[package]] -name = "comma-deps-catch2" -version = "2.13.10.post93" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ee/b4ef7758d04a024775d49558ca930fec19aa37cd691ad9182b7141f4d4d7/comma_deps_catch2-2.13.10.post93-py3-none-any.whl", hash = "sha256:8f23293251b5db48c08885d8816ca252b3fb11b0c1c26775bae36e8b217e865e", size = 137085, upload-time = "2026-07-08T19:30:53.44Z" }, -] - [[package]] name = "comma-deps-eigen" version = "3.4.0.post93" @@ -621,7 +613,6 @@ source = { editable = "msgq_repo" } [package.metadata] requires-dist = [ - { name = "catch2", marker = "extra == 'dev'", git = "https://github.com/commaai/dependencies.git?subdirectory=catch2&rev=release-catch2" }, { name = "codespell", marker = "extra == 'dev'" }, { name = "cppcheck", marker = "extra == 'dev'" }, { name = "cpplint", marker = "extra == 'dev'" }, @@ -699,10 +690,8 @@ name = "openpilot" version = "0.1.0" source = { editable = "." } dependencies = [ - { name = "cffi" }, { name = "comma-deps-acados" }, { name = "comma-deps-capnproto" }, - { name = "comma-deps-catch2" }, { name = "comma-deps-ffmpeg" }, { name = "comma-deps-gcc-arm-none-eabi" }, { name = "comma-deps-git-lfs" }, @@ -768,13 +757,11 @@ standalone = [ [package.metadata] requires-dist = [ - { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, { name = "comma-deps-acados" }, { name = "comma-deps-bootstrap-icons", marker = "extra == 'tools'" }, { name = "comma-deps-bzip2", marker = "extra == 'tools'" }, { name = "comma-deps-capnproto" }, - { name = "comma-deps-catch2" }, { name = "comma-deps-ffmpeg" }, { name = "comma-deps-gcc-arm-none-eabi" }, { name = "comma-deps-git-lfs" }, From 08fd571272513ce792b4882857b21b1731f1252b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Jul 2026 22:35:27 -0700 Subject: [PATCH 012/170] add -pipe to build (#38410) --- SConstruct | 1 + 1 file changed, 1 insertion(+) diff --git a/SConstruct b/SConstruct index 4538bee69c..7b28ff39fb 100644 --- a/SConstruct +++ b/SConstruct @@ -129,6 +129,7 @@ env = Environment( CCFLAGS=[ "-g", "-fPIC", + "-pipe", "-O2", "-Wunused", "-Werror", From aac9d9ecf427afcfe727d569e561a29ee3d9a1f3 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Wed, 22 Jul 2026 10:25:58 -0700 Subject: [PATCH 013/170] ui: show eGPU icon when enumerated (#38407) * ui: react to egpu hotplug instead of waiting for modeld params * use chestnutPresent --- openpilot/common/params_keys.h | 2 -- openpilot/selfdrive/modeld/helpers.py | 5 +++++ openpilot/selfdrive/modeld/modeld.py | 10 +++------- openpilot/selfdrive/ui/ui_state.py | 10 ++++++---- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/openpilot/common/params_keys.h b/openpilot/common/params_keys.h index 7ecac1c729..4565f3d5b2 100644 --- a/openpilot/common/params_keys.h +++ b/openpilot/common/params_keys.h @@ -126,7 +126,5 @@ inline static std::unordered_map keys = { {"UpdaterLastFetchTime", {PERSISTENT, TIME}}, {"UptimeOffroad", {PERSISTENT, FLOAT, "0.0"}}, {"UptimeOnroad", {PERSISTENT, FLOAT, "0.0"}}, - {"UsbGpuPresent", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, - {"UsbGpuCompiled", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"Version", {PERSISTENT, STRING}}, }; diff --git a/openpilot/selfdrive/modeld/helpers.py b/openpilot/selfdrive/modeld/helpers.py index 7c322c5993..8516325140 100644 --- a/openpilot/selfdrive/modeld/helpers.py +++ b/openpilot/selfdrive/modeld/helpers.py @@ -6,6 +6,8 @@ import struct import tempfile from pathlib import Path +from openpilot.common.file_chunker import get_manifest_path + MODELS_DIR = Path(__file__).resolve().parent / 'models' TG_INPUT_DEVICES_PATH = MODELS_DIR / 'tg_input_devices.json' USBGPU_VID = 0xADD1 @@ -53,3 +55,6 @@ def usbgpu_present() -> bool: except Exception: pass return False + +def usbgpu_compiled() -> bool: + return Path(get_manifest_path(modeld_pkl_path(usbgpu=True))).is_file() diff --git a/openpilot/selfdrive/modeld/modeld.py b/openpilot/selfdrive/modeld/modeld.py index 41a1f44e5f..cd8740b462 100755 --- a/openpilot/selfdrive/modeld/modeld.py +++ b/openpilot/selfdrive/modeld/modeld.py @@ -22,9 +22,9 @@ from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.compile_modeld import make_input_queues, WARP_INPUTS, POLICY_INPUTS from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_driving_model_data, fill_pose_msg, PublishState -from openpilot.common.file_chunker import open_file_chunked, get_manifest_path +from openpilot.common.file_chunker import open_file_chunked from openpilot.selfdrive.modeld.constants import ModelConstants, Plan -from openpilot.selfdrive.modeld.helpers import usbgpu_present, modeld_pkl_path, get_tg_input_devices, load_oob +from openpilot.selfdrive.modeld.helpers import usbgpu_present, usbgpu_compiled, modeld_pkl_path, get_tg_input_devices, load_oob from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link PROCESS_NAME = "openpilot.selfdrive.modeld.modeld" @@ -137,12 +137,8 @@ class ModelState: def main(demo=False): cloudlog.warning("modeld init") - _present = usbgpu_present() - _compiled = os.path.isfile(get_manifest_path(modeld_pkl_path(usbgpu=True))) - USBGPU = _present and _compiled + USBGPU = usbgpu_present() and usbgpu_compiled() params = Params() - params.put_bool("UsbGpuPresent", _present) - params.put_bool("UsbGpuCompiled", _compiled) config_realtime_process(7, 54) diff --git a/openpilot/selfdrive/ui/ui_state.py b/openpilot/selfdrive/ui/ui_state.py index 78388593c8..7efcf2a7c9 100644 --- a/openpilot/selfdrive/ui/ui_state.py +++ b/openpilot/selfdrive/ui/ui_state.py @@ -12,6 +12,7 @@ from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.lib.prime_state import PrimeState from openpilot.system.ui.lib.application import gui_app from openpilot.common.hardware import HARDWARE, PC +from openpilot.selfdrive.modeld.helpers import usbgpu_compiled BACKLIGHT_OFFROAD = 65 if HARDWARE.get_device_type() == "mici" else 50 PARAM_UPDATE_TIME = 1 / 5.0 @@ -75,8 +76,8 @@ class UIState: self.is_release = self.params.get_bool("IsReleaseBranch") self.always_on_dm: bool = self.params.get_bool("AlwaysOnDM") self.experimental_mode: bool = self.params.get_bool("ExperimentalMode") - self.usbgpu: bool = self.params.get_bool("UsbGpuPresent") - self.usbgpu_compiled: bool = self.params.get_bool("UsbGpuCompiled") + self.usbgpu: bool = False + self.usbgpu_compiled: bool = usbgpu_compiled() self.started: bool = False self.ignition: bool = False self.recording_audio: bool = False @@ -203,8 +204,9 @@ class UIState: self.is_metric = self.params.get_bool("IsMetric") self.always_on_dm = self.params.get_bool("AlwaysOnDM") self.experimental_mode = self.params.get_bool("ExperimentalMode") - self.usbgpu = self.params.get_bool("UsbGpuPresent") - self.usbgpu_compiled = self.params.get_bool("UsbGpuCompiled") + self.usbgpu = self.sm["deviceState"].chestnutPresent + if not self.usbgpu_compiled: + self.usbgpu_compiled = usbgpu_compiled() class Device: From 74ac5ef9a01362bb09512b4a522f10a31199d4eb Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 22 Jul 2026 13:26:28 -0700 Subject: [PATCH 014/170] try ctypes params (#38411) * try cffi params * ctypes * lil more * rm cython * lil more * that was fine * lil more * Drop unrelated Params concurrency changes * Clean up ctypes Params build integration * just c * Clarify Params exception translation * Expand Params C wrappers * lil more --- openpilot/common/SConscript | 13 +- openpilot/common/params.py | 208 +++++++++++++++++- openpilot/common/params_c.cc | 163 ++++++++++++++ openpilot/common/params_pyx.pyx | 191 ---------------- openpilot/common/tests/test_params.py | 5 + openpilot/selfdrive/ui/mici/widgets/button.py | 11 +- openpilot/system/manager/process.py | 2 +- openpilot/system/ui/lib/multilang.py | 10 +- openpilot/system/ui/lib/wifi_manager.py | 11 +- 9 files changed, 396 insertions(+), 218 deletions(-) create mode 100644 openpilot/common/params_c.cc delete mode 100644 openpilot/common/params_pyx.pyx diff --git a/openpilot/common/SConscript b/openpilot/common/SConscript index 600cbec2e5..4e06ae88a1 100644 --- a/openpilot/common/SConscript +++ b/openpilot/common/SConscript @@ -1,4 +1,4 @@ -Import('env', 'envCython') +Import('env') common_libs = [ 'params.cc', @@ -11,12 +11,9 @@ common_libs = [ _common = env.Library('common', common_libs, LIBS="json11") Export('_common') +params_python = env.SharedLibrary('params_c', 'params_c.cc', LIBS=[_common, 'zmq', 'json11']) +common_python = [params_python] +Export('common_python') + if GetOption('extras'): env.Program('tests/test_swaglog', 'tests/test_swaglog.cc', LIBS=[_common, 'json11', 'zmq', 'pthread']) - -# Cython bindings -params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11']) - -common_python = [params_python] - -Export('common_python') diff --git a/openpilot/common/params.py b/openpilot/common/params.py index 8af80c04bb..fc292b56dd 100644 --- a/openpilot/common/params.py +++ b/openpilot/common/params.py @@ -1,16 +1,210 @@ -from openpilot.common.params_pyx import Params, ParamKeyFlag, ParamKeyType, UnknownKeyName -assert Params -assert ParamKeyFlag -assert ParamKeyType -assert UnknownKeyName +import sys +import json +import ctypes +import weakref +import builtins +import datetime +from pathlib import Path +from enum import IntEnum, IntFlag + +from openpilot.common.swaglog import cloudlog + + +class ParamKeyFlag(IntFlag): + PERSISTENT = 0x02 + CLEAR_ON_MANAGER_START = 0x04 + CLEAR_ON_ONROAD_TRANSITION = 0x08 + CLEAR_ON_OFFROAD_TRANSITION = 0x10 + DEVELOPMENT_ONLY = 0x40 + CLEAR_ON_IGNITION_ON = 0x80 + ALL = 0xFFFFFFFF + + +class ParamKeyType(IntEnum): + STRING = 0 + BOOL = 1 + INT = 2 + FLOAT = 3 + TIME = 4 + JSON = 5 + BYTES = 6 + + +_suffix = ".dylib" if sys.platform == "darwin" else ".so" +lib = ctypes.CDLL(Path(__file__).with_name(f"libparams_c{_suffix}")) + +ParamsHandle = ctypes.c_void_p + + +class ParamsBuffer(ctypes.Structure): + _fields_ = [("data", ctypes.c_void_p), ("size", ctypes.c_size_t)] + + +def _bind_raw(name, args, result=None): + function = getattr(lib, name) + function.argtypes = args + function.restype = result + return function + + +params_last_error = _bind_raw("params_last_error", [], ctypes.c_char_p) + + +def _bind(name, args, result=None): + function = _bind_raw(name, args, result) + + def checked(*call_args): + value = function(*call_args) + if error := params_last_error(): + raise RuntimeError(error.decode()) + return value + + return checked + + +params_create = _bind("params_create", [ctypes.c_char_p, ctypes.c_size_t], ParamsHandle) +params_destroy = _bind("params_destroy", [ParamsHandle]) +params_clear_all = _bind("params_clear_all", [ParamsHandle, ctypes.c_uint]) +params_check_key = _bind("params_check_key", [ParamsHandle, ctypes.c_char_p], ctypes.c_bool) +params_get_key_type = _bind("params_get_key_type", [ParamsHandle, ctypes.c_char_p], ctypes.c_int) +params_get_default = _bind("params_get_default", [ParamsHandle, ctypes.c_char_p], ParamsBuffer) +params_get = _bind("params_get", [ParamsHandle, ctypes.c_char_p, ctypes.c_bool], ParamsBuffer) +params_get_bool = _bind("params_get_bool", [ParamsHandle, ctypes.c_char_p, ctypes.c_bool], ctypes.c_bool) +params_put = _bind("params_put", [ParamsHandle, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_size_t, ctypes.c_bool], ctypes.c_int) +params_put_bool = _bind("params_put_bool", [ParamsHandle, ctypes.c_char_p, ctypes.c_bool, ctypes.c_bool], ctypes.c_int) +params_remove = _bind("params_remove", [ParamsHandle, ctypes.c_char_p], ctypes.c_int) +params_get_path = _bind("params_get_path", [ParamsHandle, ctypes.c_char_p, ctypes.c_size_t], ParamsBuffer) +params_keys_size = _bind("params_keys_size", [ParamsHandle], ctypes.c_size_t) +params_key_at = _bind("params_key_at", [ParamsHandle, ctypes.c_size_t], ParamsBuffer) + +PYTHON_2_CPP = { + (str, ParamKeyType.STRING): lambda v: v, + (builtins.bool, ParamKeyType.BOOL): lambda v: "1" if v else "0", + (int, ParamKeyType.INT): str, + (float, ParamKeyType.FLOAT): str, + (datetime.datetime, ParamKeyType.TIME): lambda v: v.isoformat(), + (dict, ParamKeyType.JSON): json.dumps, + (list, ParamKeyType.JSON): json.dumps, + (bytes, ParamKeyType.BYTES): lambda v: v, +} +CPP_2_PYTHON = { + ParamKeyType.STRING: lambda v: v.decode("utf-8"), + ParamKeyType.BOOL: lambda v: v == b"1", + ParamKeyType.INT: int, + ParamKeyType.FLOAT: float, + ParamKeyType.TIME: lambda v: datetime.datetime.fromisoformat(v.decode("utf-8")), + ParamKeyType.JSON: json.loads, + ParamKeyType.BYTES: lambda v: v, +} + + +def ensure_bytes(v): + return v.encode() if isinstance(v, str) else v + + +def _copy_string(value): + if value.data is None: + return None + return ctypes.string_at(value.data, value.size) + + +class UnknownKeyName(Exception): + pass + + +class Params: + def __init__(self, d=""): + path = ensure_bytes(d) + self.p = params_create(path, len(path)) + self._finalizer = weakref.finalize(self, params_destroy, self.p) + self.d = d + + def __reduce__(self): + return (type(self), (self.d,)) + + def clear_all(self, tx_flag=ParamKeyFlag.ALL): + params_clear_all(self.p, int(tx_flag)) + + def check_key(self, key): + key = ensure_bytes(key) + if b"\0" in key or not params_check_key(self.p, key): + raise UnknownKeyName(key) + return key + + def python2cpp(self, proposed_type, expected_type, value, key): + cast = PYTHON_2_CPP.get((proposed_type, expected_type)) + if cast: + return cast(value) + raise TypeError(f"Type mismatch while writing param {key}: {proposed_type=} {expected_type=} {value=}") + + def _cpp2python(self, t, value, default, key): + if value is None: + return None + try: + return CPP_2_PYTHON[t](value) + except (KeyError, TypeError, ValueError): + cloudlog.warning(f"Failed to cast param {key} with {value=} from type {t=}") + return self._cpp2python(t, default, None, key) + + def _default(self, key): + return _copy_string(params_get_default(self.p, key)) + + def get(self, key, block=False, return_default=False): + k = self.check_key(key) + t = self.get_type(k) + default = self._default(k) if return_default else None + value = _copy_string(params_get(self.p, k, block)) + if value == b"": + if block: + raise KeyboardInterrupt + return self._cpp2python(t, default, None, key) + return self._cpp2python(t, value, default, key) + + def get_bool(self, key, block=False): + return bool(params_get_bool(self.p, self.check_key(key), block)) + + def _put_cast(self, key, dat): + return ensure_bytes(self.python2cpp(type(dat), self.get_type(key), dat, key)) + + def put(self, key, dat, block=False): + """Write a parameter. block=True waits until it is persisted to disk.""" + k = self.check_key(key) + value = self._put_cast(k, dat) + params_put(self.p, k, value, len(value), block) + + def put_bool(self, key, val, block=False): + params_put_bool(self.p, self.check_key(key), val, block) + + def remove(self, key): + params_remove(self.p, self.check_key(key)) + + def get_param_path(self, key=""): + key = ensure_bytes(key) + return _copy_string(params_get_path(self.p, key, len(key))).decode() + + def get_type(self, key): + return ParamKeyType(params_get_key_type(self.p, self.check_key(key))) + + def all_keys(self): + keys = [] + for i in range(params_keys_size(self.p)): + keys.append(_copy_string(params_key_at(self.p, i))) + return keys + + def get_default_value(self, key): + k = self.check_key(key) + return self._cpp2python(self.get_type(k), self._default(k), None, key) + + def cpp2python(self, key, value): + return self._cpp2python(self.get_type(key), value, None, key) + if __name__ == "__main__": import sys params = Params() key = sys.argv[1] - assert params.check_key(key), f"unknown param: {key}" - + params.check_key(key) if len(sys.argv) == 3: val = sys.argv[2] print(f"SET: {key} = {val}") diff --git a/openpilot/common/params_c.cc b/openpilot/common/params_c.cc new file mode 100644 index 0000000000..ddc9d893a9 --- /dev/null +++ b/openpilot/common/params_c.cc @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include + +#include "common/params.h" + +typedef struct { + const char *data; + size_t size; +} ParamsBuffer; + +struct ParamsHandle { + ParamsHandle(const char *path, size_t path_size) : params(std::string(path, path_size)), keys(params.allKeys()) { + } + + Params params; + const std::vector keys; +}; + +namespace { +thread_local char last_error[512] = {}; +thread_local std::string result; + +void set_error(const char *error) { + snprintf(last_error, sizeof(last_error), "%s", error); +} + +ParamsBuffer return_string(std::string value) { + result = std::move(value); + return {result.data(), result.size()}; +} + +template +Result translate_exceptions(Result failure, Callable &&callable) noexcept { + last_error[0] = '\0'; + try { + return callable(); + } catch (const std::exception &e) { + set_error(e.what()); + } catch (...) { + set_error("unknown C++ exception"); + } + return failure; +} + +template +void translate_exceptions(Callable &&callable) noexcept { + translate_exceptions(false, [&]() { + callable(); + return true; + }); +} +} // namespace + +extern "C" { + +ParamsHandle *params_create(const char *path, size_t path_size) noexcept { + return translate_exceptions(static_cast(nullptr), [&]() { + return new ParamsHandle(path, path_size); + }); +} + +void params_destroy(ParamsHandle *handle) noexcept { + translate_exceptions([&]() { + delete handle; + }); +} + +const char *params_last_error() noexcept { + return last_error; +} + +void params_clear_all(ParamsHandle *handle, unsigned int flag) noexcept { + translate_exceptions([&]() { + handle->params.clearAll(static_cast(flag)); + }); +} + +bool params_check_key(ParamsHandle *handle, const char *key) noexcept { + return translate_exceptions(false, [&]() { + return handle->params.checkKey(key); + }); +} + +int params_get_key_type(ParamsHandle *handle, const char *key) noexcept { + return translate_exceptions(-1, [&]() { + return static_cast(handle->params.getKeyType(key)); + }); +} + +ParamsBuffer params_get_default(ParamsHandle *handle, const char *key) noexcept { + return translate_exceptions(ParamsBuffer{nullptr, 0}, [&]() { + auto value = handle->params.getKeyDefaultValue(key); + if (!value.has_value()) { + return ParamsBuffer{nullptr, 0}; + } + return return_string(*value); + }); +} + +ParamsBuffer params_get(ParamsHandle *handle, const char *key, bool block) noexcept { + return translate_exceptions(ParamsBuffer{nullptr, 0}, [&]() { + return return_string(handle->params.get(key, block)); + }); +} + +bool params_get_bool(ParamsHandle *handle, const char *key, bool block) noexcept { + return translate_exceptions(false, [&]() { + return handle->params.getBool(key, block); + }); +} + +int params_put(ParamsHandle *handle, const char *key, const char *value, size_t size, bool block) noexcept { + return translate_exceptions(-1, [&]() { + if (block) { + return handle->params.put(key, value, size); + } + handle->params.putNonBlocking(key, std::string(value, size)); + return 0; + }); +} + +int params_put_bool(ParamsHandle *handle, const char *key, bool value, bool block) noexcept { + return translate_exceptions(-1, [&]() { + if (block) { + return handle->params.putBool(key, value); + } + handle->params.putBoolNonBlocking(key, value); + return 0; + }); +} + +int params_remove(ParamsHandle *handle, const char *key) noexcept { + return translate_exceptions(-1, [&]() { + return handle->params.remove(key); + }); +} + +ParamsBuffer params_get_path(ParamsHandle *handle, const char *key, size_t key_size) noexcept { + return translate_exceptions(ParamsBuffer{nullptr, 0}, [&]() { + return return_string(handle->params.getParamPath(std::string(key, key_size))); + }); +} + +size_t params_keys_size(ParamsHandle *handle) noexcept { + return translate_exceptions(size_t{0}, [&]() { + return handle->keys.size(); + }); +} + +ParamsBuffer params_key_at(ParamsHandle *handle, size_t index) noexcept { + return translate_exceptions(ParamsBuffer{nullptr, 0}, [&]() { + if (index >= handle->keys.size()) { + return ParamsBuffer{nullptr, 0}; + } + return return_string(handle->keys[index]); + }); +} + +} // extern "C" diff --git a/openpilot/common/params_pyx.pyx b/openpilot/common/params_pyx.pyx deleted file mode 100644 index c919c85f0c..0000000000 --- a/openpilot/common/params_pyx.pyx +++ /dev/null @@ -1,191 +0,0 @@ -# distutils: language = c++ -# cython: language_level = 3 -import builtins -import datetime -import json -from libcpp cimport bool -from libcpp.string cimport string -from libcpp.vector cimport vector -from libcpp.optional cimport optional - -from openpilot.common.swaglog import cloudlog - -cdef extern from "common/params.h": - cpdef enum ParamKeyFlag: - PERSISTENT - CLEAR_ON_MANAGER_START - CLEAR_ON_ONROAD_TRANSITION - CLEAR_ON_OFFROAD_TRANSITION - DEVELOPMENT_ONLY - CLEAR_ON_IGNITION_ON - ALL - - cpdef enum ParamKeyType: - STRING - BOOL - INT - FLOAT - TIME - JSON - BYTES - - cdef cppclass c_Params "Params": - c_Params(string) except + nogil - string get(string, bool) nogil - bool getBool(string, bool) nogil - int remove(string) nogil - int put(string, string) nogil - void putNonBlocking(string, string) nogil - void putBoolNonBlocking(string, bool) nogil - int putBool(string, bool) nogil - bool checkKey(string) nogil - ParamKeyType getKeyType(string) nogil - optional[string] getKeyDefaultValue(string) nogil - string getParamPath(string) nogil - void clearAll(ParamKeyFlag) - vector[string] allKeys() - -PYTHON_2_CPP = { - (str, STRING): lambda v: v, - (builtins.bool, BOOL): lambda v: "1" if v else "0", - (int, INT): str, - (float, FLOAT): str, - (datetime.datetime, TIME): lambda v: v.isoformat(), - (dict, JSON): json.dumps, - (list, JSON): json.dumps, - (bytes, BYTES): lambda v: v, -} -CPP_2_PYTHON = { - STRING: lambda v: v.decode("utf-8"), - BOOL: lambda v: v == b"1", - INT: int, - FLOAT: float, - TIME: lambda v: datetime.datetime.fromisoformat(v.decode("utf-8")), - JSON: json.loads, - BYTES: lambda v: v, -} - -def ensure_bytes(v): - return v.encode() if isinstance(v, str) else v - -class UnknownKeyName(Exception): - pass - -cdef class Params: - cdef c_Params* p - cdef str d - - def __cinit__(self, d=""): - cdef string path = d.encode() - with nogil: - self.p = new c_Params(path) - self.d = d - - def __reduce__(self): - return (type(self), (self.d,)) - - def __dealloc__(self): - del self.p - - def clear_all(self, tx_flag=ParamKeyFlag.ALL): - self.p.clearAll(tx_flag) - - def check_key(self, key): - key = ensure_bytes(key) - if not self.p.checkKey(key): - raise UnknownKeyName(key) - return key - - def python2cpp(self, proposed_type, expected_type, value, key): - cast = PYTHON_2_CPP.get((proposed_type, expected_type)) - if cast: - return cast(value) - raise TypeError(f"Type mismatch while writing param {key}: {proposed_type=} {expected_type=} {value=}") - - def _cpp2python(self, t, value, default, key): - if value is None: - return None - try: - return CPP_2_PYTHON[t](value) - except (KeyError, TypeError, ValueError): - cloudlog.warning(f"Failed to cast param {key} with {value=} from type {t=}") - return self._cpp2python(t, default, None, key) - - def get(self, key, bool block=False, bool return_default=False): - cdef string k = self.check_key(key) - cdef ParamKeyType t = self.p.getKeyType(k) - cdef optional[string] default = self.p.getKeyDefaultValue(k) - cdef string val - with nogil: - val = self.p.get(k, block) - - default_val = (default.value() if default.has_value() else None) if return_default else None - if val == b"": - if block: - # If we got no value while running in blocked mode - # it means we got an interrupt while waiting - raise KeyboardInterrupt - else: - return self._cpp2python(t, default_val, None, key) - return self._cpp2python(t, val, default_val, key) - - def get_bool(self, key, bool block=False): - cdef string k = self.check_key(key) - cdef bool r - with nogil: - r = self.p.getBool(k, block) - return r - - def _put_cast(self, key, dat): - cdef string k = self.check_key(key) - cdef ParamKeyType t = self.p.getKeyType(k) - return ensure_bytes(self.python2cpp(type(dat), t, dat, key)) - - def put(self, key, dat, bool block = False): - """ - Warning: block=True blocks until the param is written to disk! - In very rare cases this can take over a second, and your code will hang. - Use block=False in time sensitive code, but in general try to avoid - writing params as much as possible. - """ - cdef string k = self.check_key(key) - cdef string dat_bytes = self._put_cast(key, dat) - with nogil: - if block: - self.p.put(k, dat_bytes) - else: - self.p.putNonBlocking(k, dat_bytes) - - def put_bool(self, key, bool val, bool block = False): - cdef string k = self.check_key(key) - with nogil: - if block: - self.p.putBool(k, val) - else: - self.p.putBoolNonBlocking(k, val) - - def remove(self, key): - cdef string k = self.check_key(key) - with nogil: - self.p.remove(k) - - def get_param_path(self, key=""): - cdef string key_bytes = ensure_bytes(key) - return self.p.getParamPath(key_bytes).decode("utf-8") - - def get_type(self, key): - return self.p.getKeyType(self.check_key(key)) - - def all_keys(self): - return self.p.allKeys() - - def get_default_value(self, key): - cdef string k = self.check_key(key) - cdef ParamKeyType t = self.p.getKeyType(k) - cdef optional[string] default = self.p.getKeyDefaultValue(k) - return self._cpp2python(t, default.value(), None, key) if default.has_value() else None - - def cpp2python(self, key, value): - cdef string k = self.check_key(key) - cdef ParamKeyType t = self.p.getKeyType(k) - return self._cpp2python(t, value, None, key) diff --git a/openpilot/common/tests/test_params.py b/openpilot/common/tests/test_params.py index 4fcc5c7f58..a81d346b06 100644 --- a/openpilot/common/tests/test_params.py +++ b/openpilot/common/tests/test_params.py @@ -62,6 +62,11 @@ class TestParams(OpenpilotTestCase): with self.assertRaises(UnknownKeyName): self.params.put_bool("swag", True, block=True) + with self.assertRaises(UnknownKeyName): + self.params.put(b"DongleId\0suffix", "abc", block=True) + + assert self.params.get_param_path(b"key\0suffix").endswith("/key\0suffix") + def test_remove_not_there(self): assert self.params.get("CarParams") is None self.params.remove("CarParams") diff --git a/openpilot/selfdrive/ui/mici/widgets/button.py b/openpilot/selfdrive/ui/mici/widgets/button.py index 3dd7ad9a8a..dcb1dc2fd0 100644 --- a/openpilot/selfdrive/ui/mici/widgets/button.py +++ b/openpilot/selfdrive/ui/mici/widgets/button.py @@ -1,6 +1,6 @@ import math import pyray as rl -from typing import Union +from typing import TYPE_CHECKING, Union from enum import Enum from collections.abc import Callable from openpilot.system.ui.widgets import Widget @@ -9,10 +9,13 @@ from openpilot.system.ui.widgets.scroller import DO_ZOOM from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.common.filter_simple import BounceFilter -try: +if TYPE_CHECKING: from openpilot.common.params import Params -except ImportError: - Params = None +else: + try: + from openpilot.common.params import Params + except (ImportError, OSError): + Params = None SCROLLING_SPEED_PX_S = 50 COMPLICATION_SIZE = 36 diff --git a/openpilot/system/manager/process.py b/openpilot/system/manager/process.py index 7f7eabd7be..418e16fa8f 100644 --- a/openpilot/system/manager/process.py +++ b/openpilot/system/manager/process.py @@ -228,7 +228,7 @@ class DaemonProcess(ManagerProcess): pass -def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None, CP: car.CarParams=None, +def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params: Params, CP: car.CarParams, not_run: list[str] | None=None) -> list[ManagerProcess]: if not_run is None: not_run = [] diff --git a/openpilot/system/ui/lib/multilang.py b/openpilot/system/ui/lib/multilang.py index 2305404679..3d786a7248 100644 --- a/openpilot/system/ui/lib/multilang.py +++ b/openpilot/system/ui/lib/multilang.py @@ -2,13 +2,17 @@ from importlib.resources import files import json import os import re +from typing import TYPE_CHECKING from openpilot.common.basedir import BASEDIR from openpilot.common.swaglog import cloudlog -try: +if TYPE_CHECKING: from openpilot.common.params import Params -except ImportError: - Params = None +else: + try: + from openpilot.common.params import Params + except (ImportError, OSError): + Params = None SYSTEM_UI_DIR = os.path.join(BASEDIR, "openpilot/system", "ui") UI_DIR = files("openpilot.selfdrive.ui") diff --git a/openpilot/system/ui/lib/wifi_manager.py b/openpilot/system/ui/lib/wifi_manager.py index c52e5a2ddf..edf91ae2ea 100644 --- a/openpilot/system/ui/lib/wifi_manager.py +++ b/openpilot/system/ui/lib/wifi_manager.py @@ -6,7 +6,7 @@ import subprocess from collections.abc import Callable from dataclasses import dataclass, replace from enum import IntEnum -from typing import Any +from typing import TYPE_CHECKING, Any from jeepney import DBusAddress, new_method_call from jeepney.bus_messages import MatchRule, message_bus @@ -26,10 +26,13 @@ from openpilot.system.ui.lib.networkmanager import (NM, NM_WIRELESS_IFACE, NM_80 NM_DEVICE_TYPE_WIFI, NM_ACTIVE_CONNECTION_IFACE, NM_IP4_CONFIG_IFACE, NM_PROPERTIES_IFACE, NMDeviceState, NMDeviceStateReason) -try: +if TYPE_CHECKING: from openpilot.common.params import Params -except Exception: - Params = None +else: + try: + from openpilot.common.params import Params + except (ImportError, OSError): + Params = None TETHERING_IP_ADDRESS = "192.168.43.1" DEFAULT_TETHERING_PASSWORD = "swagswagcomma" From 2a1c44c904862f8d65c0f1605037754903d38381 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 22 Jul 2026 13:43:23 -0700 Subject: [PATCH 015/170] rm scons cython tool (#38417) --- SConstruct | 2 +- site_scons/site_tools/cython.py | 75 --------------------------------- 2 files changed, 1 insertion(+), 76 deletions(-) delete mode 100644 site_scons/site_tools/cython.py diff --git a/SConstruct b/SConstruct index 7b28ff39fb..630cfe1c0d 100644 --- a/SConstruct +++ b/SConstruct @@ -165,7 +165,7 @@ env = Environment( COMPILATIONDB_USE_ABSPATH=True, REDNOSE_ROOT="#rednose_repo", tools=["default", "cython", "compilation_db", "rednose_filter"], - toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"], + toolpath=["#msgq_repo/site_scons/site_tools", "#rednose_repo/site_scons/site_tools"], ) # SCons' Darwin linker tool doesn't define the variables used to expand RPATH. if arch == "Darwin": diff --git a/site_scons/site_tools/cython.py b/site_scons/site_tools/cython.py deleted file mode 100644 index f11db1d71b..0000000000 --- a/site_scons/site_tools/cython.py +++ /dev/null @@ -1,75 +0,0 @@ -import re -import SCons -from SCons.Action import Action -from SCons.Scanner import Scanner -import numpy as np - -pyx_from_import_re = re.compile(r'^from\s+(\S+)\s+cimport', re.M) -pyx_import_re = re.compile(r'^cimport\s+(\S+)', re.M) -cdef_import_re = re.compile(r'^cdef extern from\s+.(\S+).:', re.M) - -np_version = SCons.Script.Value(np.__version__) - -def pyx_scan(node, env, path, arg=None): - contents = node.get_text_contents() - env.Depends(str(node).split('.')[0] + env['CYTHONCFILESUFFIX'], np_version) - - # from cimport ... - matches = pyx_from_import_re.findall(contents) - # cimport - matches += pyx_import_re.findall(contents) - - # Modules can be either .pxd or .pyx files - files = [m.replace('.', '/') + '.pxd' for m in matches] - files += [m.replace('.', '/') + '.pyx' for m in matches] - - # cdef extern from - files += cdef_import_re.findall(contents) - - # Handle relative imports - cur_dir = str(node.get_dir()) - files = [cur_dir + f if f.startswith('/') else f for f in files] - - # Filter out non-existing files (probably system imports) - files = [f for f in files if env.File(f).exists()] - return env.File(files) - - -pyxscanner = Scanner(function=pyx_scan, skeys=['.pyx', '.pxd'], recursive=True) -cythonAction = Action("$CYTHONCOM") - - -def create_builder(env): - try: - cython = env['BUILDERS']['Cython'] - except KeyError: - cython = SCons.Builder.Builder( - action=cythonAction, - emitter={}, - suffix=cython_suffix_emitter, - single_source=1 - ) - env.Append(SCANNERS=pyxscanner) - env['BUILDERS']['Cython'] = cython - return cython - -def cython_suffix_emitter(env, source): - return "$CYTHONCFILESUFFIX" - -def generate(env): - env["CYTHON"] = "cythonize" - env["CYTHONCOM"] = "$CYTHON $CYTHONFLAGS $SOURCE" - env["CYTHONCFILESUFFIX"] = ".cpp" - - c_file, _ = SCons.Tool.createCFileBuilders(env) - - c_file.suffix['.pyx'] = cython_suffix_emitter - c_file.add_action('.pyx', cythonAction) - - c_file.suffix['.py'] = cython_suffix_emitter - c_file.add_action('.py', cythonAction) - - create_builder(env) - -def exists(env): - return True From 736f3b1a00c23d286465eb14976c25068c82d025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Wed, 22 Jul 2026 14:53:56 -0700 Subject: [PATCH 016/170] longitudinal: move cruise speed limit out of the MPC (#38367) * --- .../lib/longitudinal_mpc_lib/long_mpc.py | 17 +-- .../controls/lib/longitudinal_planner.py | 108 +++++++++--------- .../test/longitudinal_maneuvers/maneuver.py | 10 +- .../test/longitudinal_maneuvers/plant.py | 2 +- .../test_longitudinal.py | 6 +- 5 files changed, 70 insertions(+), 73 deletions(-) diff --git a/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index de1ba0c278..725491eed2 100755 --- a/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -23,7 +23,7 @@ EXPORT_DIR = os.path.join(LONG_MPC_DIR, "c_generated_code") JSON_FILE = os.path.join(LONG_MPC_DIR, "acados_ocp_long.json") LongitudinalPlanSource = log.LongitudinalPlan.LongitudinalPlanSource -MPC_SOURCES = (LongitudinalPlanSource.lead0, LongitudinalPlanSource.lead1, LongitudinalPlanSource.cruise) +MPC_SOURCES = (LongitudinalPlanSource.lead0, LongitudinalPlanSource.lead1) X_DIM = 3 U_DIM = 1 @@ -55,8 +55,6 @@ FCW_IDXS = T_IDXS < 5.0 T_DIFFS = np.diff(T_IDXS, prepend=[0.]) COMFORT_BRAKE = 2.5 STOP_DISTANCE = 6.0 -CRUISE_MIN_ACCEL = -1.2 -CRUISE_MAX_ACCEL = 1.6 MIN_X_LEAD_FACTOR = 0.5 def get_jerk_factor(personality=log.LongitudinalPersonality.standard): @@ -309,9 +307,8 @@ class LongitudinalMpc: lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau) return lead_xv - def update(self, radarstate, v_cruise, personality=log.LongitudinalPersonality.standard): + def update(self, radarstate, personality=log.LongitudinalPersonality.standard): t_follow = get_T_FOLLOW(personality) - v_ego = self.x0[1] lead_xv_0 = self.process_lead(radarstate.leadOne) lead_xv_1 = self.process_lead(radarstate.leadTwo) @@ -322,15 +319,7 @@ class LongitudinalMpc: lead_0_obstacle = lead_xv_0[:,0] + get_stopped_equivalence_factor(lead_xv_0[:,1]) lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1]) - # Fake an obstacle for cruise, this ensures smooth acceleration to set speed - # when the leads are no factor. - v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05) - # TODO does this make sense when max_a is negative? - v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05) - v_cruise_clipped = np.clip(v_cruise * np.ones(N+1), v_lower, v_upper) - cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) - - x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) + x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle]) self.source = MPC_SOURCES[np.argmin(x_obstacles[0])] self.yref[:,:] = 0.0 diff --git a/openpilot/selfdrive/controls/lib/longitudinal_planner.py b/openpilot/selfdrive/controls/lib/longitudinal_planner.py index 116de14e0e..0b1fee329d 100755 --- a/openpilot/selfdrive/controls/lib/longitudinal_planner.py +++ b/openpilot/selfdrive/controls/lib/longitudinal_planner.py @@ -17,6 +17,8 @@ from openpilot.common.swaglog import cloudlog A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6] A_CRUISE_MAX_BP = [0., 10.0, 25., 40.] +J_CRUISE_VALS = [1.6, 1.2, 0.8, 0.6] +A_CRUISE_MIN = -1.2 CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N] ALLOW_THROTTLE_THRESHOLD = 0.4 MIN_ALLOW_THROTTLE_SPEED = 2.5 @@ -31,18 +33,26 @@ def get_max_accel(v_ego): def get_coast_accel(pitch): return np.sin(pitch) * -5.65 - 0.3 # fitted from data using xx/projects/allow_throttle/compute_coast_accel.py -def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): - """ - This function returns a limited long acceleration allowed, depending on the existing lateral acceleration - this should avoid accelerating when losing the target in turns - """ - # FIXME: This function to calculate lateral accel is incorrect and should use the VehicleModel - # The lookup table for turns should also be updated if we do this - a_total_max = np.interp(v_ego, _A_TOTAL_MAX_BP, _A_TOTAL_MAX_V) - a_y = v_ego ** 2 * angle_steers * CV.DEG_TO_RAD / (CP.steerRatio * CP.wheelbase) - a_x_allowed = math.sqrt(max(a_total_max ** 2 - a_y ** 2, 0.)) +def get_cruise_accel(e2e, v_cruise, v_ego, a_cruise_prev, angle_steers, CP, dt, accel_coast, allow_throttle): + max_accel = ACCEL_MAX if e2e else get_max_accel(v_ego) - return [a_target[0], min(a_target[1], a_x_allowed)] + if not e2e: + a_total_max = np.interp(v_ego, _A_TOTAL_MAX_BP, _A_TOTAL_MAX_V) + a_y = v_ego ** 2 * angle_steers * CV.DEG_TO_RAD / (CP.steerRatio * CP.wheelbase) + a_x_allowed = math.sqrt(max(a_total_max ** 2 - a_y ** 2, 0.)) + max_accel = min(max_accel, a_x_allowed) + if not allow_throttle: + clipped_accel_coast = max(accel_coast, ACCEL_MIN) + coast_limit = np.interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [max_accel, clipped_accel_coast]) + max_accel = min(max_accel, coast_limit) + + target_accel = np.clip(v_cruise - v_ego, A_CRUISE_MIN, max_accel) + if not e2e: + j_cruise = np.interp(v_ego, A_CRUISE_MAX_BP, J_CRUISE_VALS) + target_accel = float(np.clip(target_accel, a_cruise_prev - j_cruise * dt, a_cruise_prev + j_cruise * dt)) + + cruise_should_stop = v_cruise == 0.0 + return target_accel, cruise_should_stop class LongitudinalPlanner: @@ -55,7 +65,7 @@ class LongitudinalPlanner: self.a_desired = init_a self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt) - self.prev_accel_clip = [ACCEL_MIN, ACCEL_MAX] + self.a_cruise = 0.0 self.output_a_target = 0.0 self.output_should_stop = False @@ -72,46 +82,36 @@ class LongitudinalPlanner: v_ego = sm['carState'].vEgo v_cruise_kph = min(sm['carState'].vCruise, V_CRUISE_MAX) v_cruise = v_cruise_kph * CV.KPH_TO_MS - v_cruise_initialized = sm['carState'].vCruise != V_CRUISE_UNSET + if sm['controlsState'].forceDecel: + v_cruise = 0.0 long_control_off = sm['controlsState'].longControlState == LongCtrlState.off - force_slow_decel = sm['controlsState'].forceDecel # Reset current state when not engaged, or user is controlling the speed reset_state = long_control_off if self.CP.openpilotLongitudinalControl else not sm['selfdriveState'].enabled # PCM cruise speed may be updated a few cycles later, check if initialized + v_cruise_initialized = sm['carState'].vCruise != V_CRUISE_UNSET reset_state = reset_state or not v_cruise_initialized + throttle_probs = sm['modelV2'].meta.disengagePredictions.gasPressProbs + throttle_prob = throttle_probs[1] if len(throttle_probs) > 1 else 1.0 + self.allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD or v_ego <= MIN_ALLOW_THROTTLE_SPEED + + steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg + + if reset_state: + self.v_desired_filter.x = v_ego + self.a_desired = np.clip(sm['carState'].aEgo, ACCEL_MIN, ACCEL_MAX) + + # Prevent divergence, smooth in current v_ego + self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) + # No change cost when user is controlling the speed, or when standstill prev_accel_constraint = not (reset_state or sm['carState'].standstill) - accel_clip = [ACCEL_MIN, get_max_accel(v_ego)] - steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg - accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP) - - if reset_state: - self.v_desired_filter.x = v_ego - # Clip aEgo to cruise limits to prevent large accelerations when becoming active - self.a_desired = np.clip(sm['carState'].aEgo, accel_clip[0], accel_clip[1]) - - # Prevent divergence, smooth in current v_ego - self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) - throttle_probs = sm['modelV2'].meta.disengagePredictions.gasPressProbs - throttle_prob = throttle_probs[1] if len(throttle_probs) > 1 else 1.0 - # Don't clip at low speeds since throttle_prob doesn't account for creep - self.allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD or v_ego <= MIN_ALLOW_THROTTLE_SPEED - - if not self.allow_throttle: - clipped_accel_coast = max(accel_coast, accel_clip[0]) - clipped_accel_coast_interp = np.interp(v_ego, [MIN_ALLOW_THROTTLE_SPEED, MIN_ALLOW_THROTTLE_SPEED*2], [accel_clip[1], clipped_accel_coast]) - accel_clip[1] = min(accel_clip[1], clipped_accel_coast_interp) - - if force_slow_decel: - v_cruise = 0.0 - self.mpc.set_weights(prev_accel_constraint, personality=sm['selfdriveState'].personality) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) - self.mpc.update(sm['radarState'], v_cruise, personality=sm['selfdriveState'].personality) + self.mpc.update(sm['radarState'], personality=sm['selfdriveState'].personality) self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution) @@ -122,10 +122,8 @@ class LongitudinalPlanner: if self.fcw: cloudlog.info("FCW triggered") - # Interpolate 0.05 seconds and save as starting point for next iteration + # Save starting point for next iteration a_prev = self.a_desired - self.a_desired = float(np.interp(self.dt, CONTROL_N_T_IDX, self.a_desired_trajectory)) - self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.a_desired + a_prev) / 2.0 action_t = self.CP.longitudinalActuatorDelay + DT_MDL output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX, @@ -133,19 +131,21 @@ class LongitudinalPlanner: output_a_target_e2e = sm['modelV2'].action.desiredAcceleration output_should_stop_e2e = sm['modelV2'].action.shouldStop - if sm['selfdriveState'].experimentalMode: - output_a_target = min(output_a_target_e2e, output_a_target_mpc) - self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc - if output_a_target < output_a_target_mpc: - self.mpc.source = LongitudinalPlanSource.e2e - else: - output_a_target = output_a_target_mpc - self.output_should_stop = output_should_stop_mpc + self.a_cruise, cruise_should_stop = get_cruise_accel(sm['selfdriveState'].experimentalMode, v_cruise, v_ego, + self.a_cruise, steer_angle_without_offset, self.CP, self.dt, + accel_coast, self.allow_throttle) - for idx in range(2): - accel_clip[idx] = np.clip(accel_clip[idx], self.prev_accel_clip[idx] - 0.05, self.prev_accel_clip[idx] + 0.05) - self.output_a_target = np.clip(output_a_target, accel_clip[0], accel_clip[1]) - self.prev_accel_clip = accel_clip + candidates = [(output_a_target_mpc, self.mpc.source, output_should_stop_mpc), + (self.a_cruise, LongitudinalPlanSource.cruise, cruise_should_stop)] + if sm['selfdriveState'].experimentalMode: + candidates.append((output_a_target_e2e, LongitudinalPlanSource.e2e, output_should_stop_e2e)) + + output_a_target, self.mpc.source, _ = min(candidates, key=lambda c: c[0]) + self.output_should_stop = any(should_stop for _, _, should_stop in candidates) + self.output_a_target = np.clip(output_a_target, ACCEL_MIN, ACCEL_MAX) + + self.a_desired = float(self.output_a_target) + self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.output_a_target + a_prev) / 2.0 def publish(self, sm, pm): plan_send = messaging.new_message('longitudinalPlan') diff --git a/openpilot/selfdrive/test/longitudinal_maneuvers/maneuver.py b/openpilot/selfdrive/test/longitudinal_maneuvers/maneuver.py index ba0379f2d7..99a7d8d690 100644 --- a/openpilot/selfdrive/test/longitudinal_maneuvers/maneuver.py +++ b/openpilot/selfdrive/test/longitudinal_maneuvers/maneuver.py @@ -43,6 +43,7 @@ class Maneuver: valid = True logs = [] + not_starting_t = 0.0 while plant.current_time < self.duration: speed_lead = np.interp(plant.current_time, self.breakpoints, self.speed_lead_values) prob_lead = np.interp(plant.current_time, self.breakpoints, self.prob_lead_values) @@ -68,8 +69,13 @@ class Maneuver: valid = False if self.ensure_start and log['v_rel'] > 0 and log['acceleration'] < 1e-3: - print('LongitudinalPlanner not starting!') - valid = False + if not_starting_t == 0.0: + not_starting_t = plant.current_time + elif plant.current_time - not_starting_t > 0.5: + print('LongitudinalPlanner not starting!') + valid = False + else: + not_starting_t = 0.0 if self.ensure_slowdown and log['speed'] > 5.5: print('LongitudinalPlanner not slowing down!') diff --git a/openpilot/selfdrive/test/longitudinal_maneuvers/plant.py b/openpilot/selfdrive/test/longitudinal_maneuvers/plant.py index 23ccbdb85d..bdd5c51ee4 100755 --- a/openpilot/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/openpilot/selfdrive/test/longitudinal_maneuvers/plant.py @@ -107,7 +107,7 @@ class Plant: position = log.XYZTData.new_message() position.x = [float(x) for x in (self.speed + 0.5) * np.array(ModelConstants.T_IDXS)] model.modelV2.position = position - model.modelV2.action.desiredAcceleration = float(self.acceleration + 0.1) + model.modelV2.action.desiredAcceleration = float(self.acceleration + 0.5) velocity = log.XYZTData.new_message() velocity.x = [float(x) for x in (self.speed + 0.5) * np.ones_like(ModelConstants.T_IDXS)] velocity.x[0] = float(self.speed) # always start at current speed diff --git a/openpilot/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/openpilot/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py index 9bd22902d3..c8694b6ac2 100644 --- a/openpilot/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/openpilot/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -151,7 +151,9 @@ def create_maneuvers(kwargs): enabled=False, **kwargs, ), - Maneuver( + ] + if not kwargs['e2e']: + maneuvers.append(Maneuver( "slow to 5m/s with allow_throttle = False and pitch = +0.1", duration=30., initial_speed=20., @@ -162,7 +164,7 @@ def create_maneuvers(kwargs): breakpoints=[0.0, 2., 2.01], ensure_slowdown=True, **kwargs, - )] + )) if not kwargs['force_decel']: # controls relies on planner commanding to move for stock-ACC resume spamming maneuvers.append(Maneuver( From 1791057369d85385420379ef026fc7ec21d285fd Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Wed, 22 Jul 2026 15:13:46 -0700 Subject: [PATCH 017/170] add big model fallback (#38303) * big model fallback * reorder --- openpilot/cereal/log.capnp | 1 + openpilot/common/params_keys.h | 2 + openpilot/selfdrive/modeld/modeld.py | 51 ++++++++++++++++++-- openpilot/selfdrive/selfdrived/events.py | 4 ++ openpilot/selfdrive/selfdrived/selfdrived.py | 25 +++++++++- 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp index 9f0baf3594..00fccd43a6 100644 --- a/openpilot/cereal/log.capnp +++ b/openpilot/cereal/log.capnp @@ -128,6 +128,7 @@ struct OnroadEvent @0xc4fa6047f024e718 { aeb @92; userBookmark @95; excessiveActuation @96; + bigModelLoading @100; lowBatteryDEPRECATED @40; soundsUnavailableDEPRECATED @47; diff --git a/openpilot/common/params_keys.h b/openpilot/common/params_keys.h index 4565f3d5b2..c0c5bebb2c 100644 --- a/openpilot/common/params_keys.h +++ b/openpilot/common/params_keys.h @@ -126,5 +126,7 @@ inline static std::unordered_map keys = { {"UpdaterLastFetchTime", {PERSISTENT, TIME}}, {"UptimeOffroad", {PERSISTENT, FLOAT, "0.0"}}, {"UptimeOnroad", {PERSISTENT, FLOAT, "0.0"}}, + {"UsbGpuActive", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, + {"UsbGpuLoading", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"Version", {PERSISTENT, STRING}}, }; diff --git a/openpilot/selfdrive/modeld/modeld.py b/openpilot/selfdrive/modeld/modeld.py index cd8740b462..9fc72eb74b 100755 --- a/openpilot/selfdrive/modeld/modeld.py +++ b/openpilot/selfdrive/modeld/modeld.py @@ -2,6 +2,7 @@ import os os.environ['GMMU'] = '0' # for usbgpu fast loading, noop for qcom from tinygrad.tensor import Tensor +import threading import time import numpy as np import openpilot.cereal.messaging as messaging @@ -33,6 +34,7 @@ SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 +BIG_MODEL_TIMEOUT = 60 def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, @@ -133,12 +135,24 @@ class ModelState: outputs_dict['raw_pred'] = model_output.copy() return outputs_dict + def warmup(self) -> None: + dummy_frames = {k: np.zeros(self.frame_buf_params[k][3], dtype=np.uint8) for k in self.vision_input_names} + eye = np.eye(3, dtype=np.float32) + dims = {'desire_pulse': ModelConstants.DESIRE_LEN, 'traffic_convention': 2, 'action_t': 2} + self.run(dummy_frames, dict.fromkeys(self.vision_input_names, eye), {k: np.zeros(v, dtype=np.float32) for k, v in dims.items()}) + self.input_queues, self.npy = make_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV) + self.prev_desire[:] = 0 + self.full_frames.clear() + self._blob_cache.clear() + def main(demo=False): cloudlog.warning("modeld init") USBGPU = usbgpu_present() and usbgpu_compiled() params = Params() + params.put_bool("UsbGpuLoading", USBGPU) + params.put_bool("UsbGpuActive", False) config_realtime_process(7, 54) @@ -165,11 +179,30 @@ def main(demo=False): if use_extra_client: cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})") - if USBGPU: - wait_usbgpu_link() st = time.monotonic() cloudlog.warning("loading model") - model = ModelState(vipc_client_main.width, vipc_client_main.height, USBGPU) + model = None + if USBGPU: + big_model = None + def load_big(): + nonlocal big_model + try: + wait_usbgpu_link() + m = ModelState(vipc_client_main.width, vipc_client_main.height, True) + m.warmup() + big_model = m + except Exception: + cloudlog.exception("big model load failed") + loader = threading.Thread(target=load_big, daemon=True) + loader.start() + loader.join(BIG_MODEL_TIMEOUT) + model = big_model + params.put_bool("UsbGpuActive", model is not None) + + small_model = ModelState(vipc_client_main.width, vipc_client_main.height, False) if model is None or USBGPU else None + if model is None: + model = small_model + params.put_bool("UsbGpuLoading", False) cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") # messaging @@ -282,7 +315,17 @@ def main(demo=False): } mt1 = time.perf_counter() - model_output = model.run(bufs, transforms, inputs) + try: + model_output = model.run(bufs, transforms, inputs) + except Exception: + if not params.get_bool("UsbGpuActive"): + raise + # fallback to small model + cloudlog.exception("big model failed, fall back to small") + params.put_bool("UsbGpuActive", False) + model = small_model + run_count = 0 + model_output = None mt2 = time.perf_counter() model_execution_time = mt2 - mt1 diff --git a/openpilot/selfdrive/selfdrived/events.py b/openpilot/selfdrive/selfdrived/events.py index 220efd9c18..96a316118b 100755 --- a/openpilot/selfdrive/selfdrived/events.py +++ b/openpilot/selfdrive/selfdrived/events.py @@ -409,6 +409,10 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { "Ensure road ahead is clear"), }, + EventName.bigModelLoading: { + ET.NO_ENTRY: NoEntryAlert("Big Model Loading"), + }, + EventName.lateralManeuver: { ET.WARNING: longitudinal_maneuver_alert, ET.PERMANENT: NormalPermanentAlert("Lateral Maneuver Mode"), diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index dd94439b95..04b5de25ea 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -65,6 +65,9 @@ class SelfdriveD: self.calibrated_pose: Pose | None = None self.excessive_actuation_check = ExcessiveActuationCheck() self.excessive_actuation = self.params.get("Offroad_ExcessiveActuation") is not None + self.big_model_loading = False + self.big_model_active = False + self.big_model_ready_t = 0. # Setup sockets self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents']) @@ -154,6 +157,22 @@ class SelfdriveD: self.events.add(EventName.joystickDebug) self.startup_event = None + loading = self.params.get_bool("UsbGpuLoading") + if self.big_model_loading and not loading: + self.big_model_ready_t = time.monotonic() + self.big_model_loading = loading + if self.big_model_loading: + self.events.add(EventName.bigModelLoading) + + # soft disable if the big model fails + big_active = self.params.get_bool("UsbGpuActive") + if big_active: + self.big_model_active = True + if self.enabled and self.big_model_active and not big_active: + self.events.add(EventName.modeldLagging) + if not self.enabled: + self.big_model_active = False + if self.sm.recv_frame['lateralManeuverPlan'] > 0: self.events.add(EventName.lateralManeuver) self.startup_event = None @@ -347,7 +366,9 @@ class SelfdriveD: # generic catch-all. ideally, a more specific event should be added above instead has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE)) no_system_errors = (not has_disable_events) or (len(self.events) == num_events) - if not self.sm.all_checks() and no_system_errors: + warmup_sec = 5. + big_model_settling = self.big_model_loading or time.monotonic() < self.big_model_ready_t + warmup_sec + if not self.sm.all_checks() and no_system_errors and not big_model_settling: # the load holds modelV2 and friends back on purpose if not self.sm.all_alive(): self.events.add(EventName.commIssue) elif not self.sm.all_freq_ok(): @@ -366,7 +387,7 @@ class SelfdriveD: else: self.logged_comm_issue = None - if not self.CP.notCar: + if not self.CP.notCar and not big_model_settling: # localization has nothing to work with during the load if not self.sm['livePose'].posenetOK: self.events.add(EventName.posenetInvalid) if not self.sm['livePose'].inputsOK: From 05f42f752bb9b3f86f096c5ef0b560dbd926cfef Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Jul 2026 16:07:43 -0700 Subject: [PATCH 018/170] joystickd: use should_stop (#38421) * param * not really needed now --- openpilot/tools/joystick/joystickd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpilot/tools/joystick/joystickd.py b/openpilot/tools/joystick/joystickd.py index ac2f99f67e..ce8b581848 100755 --- a/openpilot/tools/joystick/joystickd.py +++ b/openpilot/tools/joystick/joystickd.py @@ -9,6 +9,7 @@ from opendbc.car.vehicle_model import VehicleModel from openpilot.common.realtime import DT_CTRL, Ratekeeper from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog +from openpilot.selfdrive.controls.lib.drive_helpers import should_stop LongCtrlState = car.CarControl.Actuators.LongControlState MAX_LAT_ACCEL = 3.0 @@ -48,7 +49,7 @@ def joystickd_thread(): if CC.longActive: actuators.accel = 4.0 * float(np.clip(joystick_axes[0], -1, 1)) - actuators.longControlState = LongCtrlState.pid if sm['carState'].vEgo > 0.1 else LongCtrlState.stopping + actuators.longControlState = LongCtrlState.stopping if should_stop(sm['carState'].vEgo, actuators.accel) else LongCtrlState.pid CC.cruiseControl.resume = actuators.accel > 0.0 if CC.latActive: From 6335db69bd08201478e6857282eb6a96a8938dd0 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Wed, 22 Jul 2026 16:23:32 -0700 Subject: [PATCH 019/170] eGPU UI (#38419) --- .../selfdrive/ui/mici/onroad/hud_renderer.py | 16 ++++++++++++++++ openpilot/selfdrive/ui/ui_state.py | 7 ++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/openpilot/selfdrive/ui/mici/onroad/hud_renderer.py b/openpilot/selfdrive/ui/mici/onroad/hud_renderer.py index 27bb815590..0225bc8d57 100644 --- a/openpilot/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/openpilot/selfdrive/ui/mici/onroad/hud_renderer.py @@ -177,8 +177,24 @@ class HudRenderer(Widget): if self.is_cruise_set: self._draw_set_speed(rect) + if ui_state.usbgpu and ui_state.usbgpu_compiled: + self._draw_model_source(rect) + self._draw_steering_wheel(rect) + def _draw_model_source(self, rect: rl.Rectangle) -> None: + if ui_state.sm.recv_frame['selfdriveState'] < ui_state.started_frame: + return + small_drives = not ui_state.usbgpu_loading and not ui_state.usbgpu_active and ui_state.sm.recv_frame['modelV2'] > ui_state.started_frame + big_color = rl.GREEN if ui_state.usbgpu_active else rl.RED if small_drives else rl.GRAY + small_color = rl.GREEN if small_drives else rl.WHITE if ui_state.usbgpu_active else rl.GRAY + big_size = measure_text_cached(self._font_semi_bold, "BIG", FONT_SIZES.max_speed) + small_size = measure_text_cached(self._font_semi_bold, "SM", FONT_SIZES.max_speed) + big_pos = rl.Vector2(rect.x + rect.width - 12 - big_size.x, rect.y + rect.height - 14 - FONT_SIZES.max_speed) + small_pos = rl.Vector2(big_pos.x + (big_size.x - small_size.x) / 2, big_pos.y - FONT_SIZES.max_speed - 2) + rl.draw_text_ex(self._font_semi_bold, "BIG", big_pos, FONT_SIZES.max_speed, 0, big_color) + rl.draw_text_ex(self._font_semi_bold, "SM", small_pos, FONT_SIZES.max_speed, 0, small_color) + def _draw_steering_wheel(self, rect: rl.Rectangle) -> None: wheel_txt = self._txt_wheel_critical if self._show_wheel_critical else self._txt_wheel diff --git a/openpilot/selfdrive/ui/ui_state.py b/openpilot/selfdrive/ui/ui_state.py index 7efcf2a7c9..a12df6906d 100644 --- a/openpilot/selfdrive/ui/ui_state.py +++ b/openpilot/selfdrive/ui/ui_state.py @@ -78,6 +78,8 @@ class UIState: self.experimental_mode: bool = self.params.get_bool("ExperimentalMode") self.usbgpu: bool = False self.usbgpu_compiled: bool = usbgpu_compiled() + self.usbgpu_active: bool = self.params.get_bool("UsbGpuActive") + self.usbgpu_loading: bool = self.params.get_bool("UsbGpuLoading") self.started: bool = False self.ignition: bool = False self.recording_audio: bool = False @@ -204,9 +206,12 @@ class UIState: self.is_metric = self.params.get_bool("IsMetric") self.always_on_dm = self.params.get_bool("AlwaysOnDM") self.experimental_mode = self.params.get_bool("ExperimentalMode") - self.usbgpu = self.sm["deviceState"].chestnutPresent + # keep usbgpu UI active until offroad transition when gpu disappears + self.usbgpu = self.sm["deviceState"].chestnutPresent or (self.usbgpu and self.started) if not self.usbgpu_compiled: self.usbgpu_compiled = usbgpu_compiled() + self.usbgpu_active = self.params.get_bool("UsbGpuActive") + self.usbgpu_loading = self.params.get_bool("UsbGpuLoading") class Device: From 0bac3c903909f9a33e8bd8c4cee1ff4c1564cbe0 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Wed, 22 Jul 2026 16:44:16 -0700 Subject: [PATCH 020/170] big model loaded chime (#38420) play chime when big model is loaded --- openpilot/cereal/log.capnp | 1 + openpilot/selfdrive/selfdrived/events.py | 4 ++++ openpilot/selfdrive/selfdrived/selfdrived.py | 2 ++ 3 files changed, 7 insertions(+) diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp index 00fccd43a6..342e4a03d4 100644 --- a/openpilot/cereal/log.capnp +++ b/openpilot/cereal/log.capnp @@ -129,6 +129,7 @@ struct OnroadEvent @0xc4fa6047f024e718 { userBookmark @95; excessiveActuation @96; bigModelLoading @100; + bigModelReady @101; lowBatteryDEPRECATED @40; soundsUnavailableDEPRECATED @47; diff --git a/openpilot/selfdrive/selfdrived/events.py b/openpilot/selfdrive/selfdrived/events.py index 96a316118b..76ffea1111 100755 --- a/openpilot/selfdrive/selfdrived/events.py +++ b/openpilot/selfdrive/selfdrived/events.py @@ -413,6 +413,10 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { ET.NO_ENTRY: NoEntryAlert("Big Model Loading"), }, + EventName.bigModelReady: { + ET.PERMANENT: EngagementAlert(AudibleAlert.complete), + }, + EventName.lateralManeuver: { ET.WARNING: longitudinal_maneuver_alert, ET.PERMANENT: NormalPermanentAlert("Lateral Maneuver Mode"), diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index 04b5de25ea..3f84708eeb 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -160,6 +160,8 @@ class SelfdriveD: loading = self.params.get_bool("UsbGpuLoading") if self.big_model_loading and not loading: self.big_model_ready_t = time.monotonic() + if self.params.get_bool("UsbGpuActive"): + self.events.add(EventName.bigModelReady) self.big_model_loading = loading if self.big_model_loading: self.events.add(EventName.bigModelLoading) From 49c3b8fc341a7fb1968bd00ba86a7559feed17bf Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Jul 2026 17:24:38 -0700 Subject: [PATCH 021/170] joystick: add override state (#38423) fix js joverride --- openpilot/tools/joystick/joystickd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpilot/tools/joystick/joystickd.py b/openpilot/tools/joystick/joystickd.py index ce8b581848..2b84683863 100755 --- a/openpilot/tools/joystick/joystickd.py +++ b/openpilot/tools/joystick/joystickd.py @@ -34,6 +34,7 @@ def joystickd_thread(): CC.enabled = sm['selfdriveState'].enabled CC.latActive = sm['selfdriveState'].active and not sm['carState'].steerFaultTemporary and not sm['carState'].steerFaultPermanent CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in sm['onroadEvents']) and CP.openpilotLongitudinalControl + CC.cruiseControl.override = CC.enabled and not CC.longActive and CP.openpilotLongitudinalControl CC.cruiseControl.cancel = sm['carState'].cruiseState.enabled and (not CC.enabled or not CP.pcmCruise) CC.hudControl.leadDistanceBars = 2 From 2895346746634d7eec0ee749f946c87039948a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Wed, 22 Jul 2026 18:12:13 -0700 Subject: [PATCH 022/170] Rebel Legion model (#38164) * 3e5b7d32-e114-4517-96b4-b3a2448fd939/400 * 9ffd502c-01d0-40f3-93c2-07a4d3e4445c/400 * 1afc2042-8761-4663-ae6a-d593b13defd0/400 * d4b183da-3b8c-4ff7-8927-e3e7216ddb86/400 * 60a46520-f02f-49c5-8eae-e0f8de92af07/400 * ca63f597-de00-478f-970c-302069a59130/400 * 0322768e-5d7e-46e6-9712-d52de312e1da/400 * d25f38c8-8689-42a7-8326-f0c7ce00625f * f14f0be4-bf1e-4dc3-914d-3d9a23b5a05a/400 * lil smooth * 03dfde49-54e6-4129-b05f-24b0b0c7e5f0/400 * 6d9d6f8a-5c82-41f6-92aa-4c1a11eb5645/400 * ci: fix model review tinygrad import * ci: skip unfetched big model in review --------- Co-authored-by: Toby Penner --- .github/workflows/model_review.yaml | 2 +- openpilot/selfdrive/modeld/modeld.py | 4 ++-- openpilot/selfdrive/modeld/models/driving_supercombo.onnx | 4 ++-- scripts/reporter.py | 2 ++ 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/model_review.yaml b/.github/workflows/model_review.yaml index d491b6397a..bdcc2e8977 100644 --- a/.github/workflows/model_review.yaml +++ b/.github/workflows/model_review.yaml @@ -37,7 +37,7 @@ jobs: run: | echo "content<> $GITHUB_OUTPUT echo "## Model Review" >> $GITHUB_OUTPUT - PYTHONPATH=${{ github.workspace }} MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT + PYTHONPATH=${{ github.workspace }}:${{ github.workspace }}/tinygrad_repo MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Post model report comment diff --git a/openpilot/selfdrive/modeld/modeld.py b/openpilot/selfdrive/modeld/modeld.py index 9fc72eb74b..3ba4dfa71a 100755 --- a/openpilot/selfdrive/modeld/modeld.py +++ b/openpilot/selfdrive/modeld/modeld.py @@ -31,8 +31,8 @@ from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link PROCESS_NAME = "openpilot.selfdrive.modeld.modeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') -LAT_SMOOTH_SECONDS = 0.0 -LONG_SMOOTH_SECONDS = 0.3 +LAT_SMOOTH_SECONDS = 0.1 +LONG_SMOOTH_SECONDS = 0.1 MIN_LAT_CONTROL_SPEED = 0.3 BIG_MODEL_TIMEOUT = 60 diff --git a/openpilot/selfdrive/modeld/models/driving_supercombo.onnx b/openpilot/selfdrive/modeld/models/driving_supercombo.onnx index f0672eab48..944eebb017 100644 --- a/openpilot/selfdrive/modeld/models/driving_supercombo.onnx +++ b/openpilot/selfdrive/modeld/models/driving_supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:659727c4d4839adc4992a254409a54259a8756a743f2d567bf5fdc6579f8009b -size 60881999 +oid sha256:15e563da34889318e41321a2559682d417416602de9b6f5093c3be0e6766419d +size 96599486 diff --git a/scripts/reporter.py b/scripts/reporter.py index 5de5521835..175a06fa67 100755 --- a/scripts/reporter.py +++ b/scripts/reporter.py @@ -36,6 +36,8 @@ if __name__ == "__main__": for f in glob.glob(BASEDIR + MODEL_PATH + "/*.onnx"): fn = os.path.basename(f) + if fn == "big_driving_supercombo.onnx": + continue master_path = MASTER_PATH + MODEL_PATH + fn if os.path.exists(master_path): master = get_checkpoint(master_path) From 66300306f40ee7a907d3ae911c47e45f62ab19a8 Mon Sep 17 00:00:00 2001 From: stef <19478336+stefpi@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:03:25 -0700 Subject: [PATCH 023/170] ui(body): hide body layout if no car fingerprint (#38427) * hide body layout if car params is none * reposition * refactor --- openpilot/selfdrive/ui/mici/layouts/main.py | 3 +++ openpilot/selfdrive/ui/ui_state.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/openpilot/selfdrive/ui/mici/layouts/main.py b/openpilot/selfdrive/ui/mici/layouts/main.py index 20925ab63c..7b37747ef7 100644 --- a/openpilot/selfdrive/ui/mici/layouts/main.py +++ b/openpilot/selfdrive/ui/mici/layouts/main.py @@ -61,6 +61,9 @@ class MiciMainLayout(Scroller): if not self._onboarding_window.completed: gui_app.push_widget(self._onboarding_window) + # initialize correct onroad layout + self._on_body_changed() + @property def _onroad_layout(self) -> Widget: # For scroll_to diff --git a/openpilot/selfdrive/ui/ui_state.py b/openpilot/selfdrive/ui/ui_state.py index a12df6906d..9425133e3e 100644 --- a/openpilot/selfdrive/ui/ui_state.py +++ b/openpilot/selfdrive/ui/ui_state.py @@ -86,7 +86,7 @@ class UIState: self.panda_type: log.PandaState.PandaType = log.PandaState.PandaType.unknown self.personality: log.LongitudinalPersonality = log.LongitudinalPersonality.standard self.has_longitudinal_control: bool = False - self.is_body: bool | None = None + self.is_body: bool | None = False self.CP: car.CarParams | None = None self.light_sensor: float = -1.0 From 87fde8a058346fb7d35626fb7fdc37f51f6c54b4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 23 Jul 2026 08:31:54 -0700 Subject: [PATCH 024/170] pyproject cleanup --- pyproject.toml | 27 +++++++++++++-------------- uv.lock | 2 -- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 678720aa04..70ccee02d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,6 @@ dependencies = [ # core "scons", "pycapnp==2.1.0", # 2.2 introduces a memory leak due to cyclic references - "Cython", "numpy >=2.0", # vendored native dependencies @@ -30,10 +29,6 @@ dependencies = [ "comma-deps-git-lfs", "comma-deps-gcc-arm-none-eabi", - # logging - "pyzmq", - "sentry-sdk", - # athena "PyJWT[crypto]", "websocket_client", @@ -42,16 +37,16 @@ dependencies = [ "inputs", # these should be removed + "pyzmq", + "sentry-sdk", "setproctitle", - - # logreader - "zstandard", + "jeepney", + "pillow", + "zstandard", # this can go once we're on Python 3.14+ # ui "comma-deps-raylib", "qrcode", - "jeepney", - "pillow", ] [project.optional-dependencies] @@ -60,13 +55,17 @@ docs = [ ] testing = [ - "coverage", + "coverage", # line coverage + "ty", # type checking + "ruff", # linting + "codespell", # spellcheck + + # TODO: replace this with our own implementation "hypothesis ==6.47.*", - "ty", + + # TODO: replace these with our own nice simple test runner "pytest", "pytest-xdist", - "ruff", - "codespell", ] dev = [ diff --git a/uv.lock b/uv.lock index de03b28288..8b6c7c2f57 100644 --- a/uv.lock +++ b/uv.lock @@ -699,7 +699,6 @@ dependencies = [ { name = "comma-deps-raylib" }, { name = "comma-deps-zeromq" }, { name = "comma-deps-zstd" }, - { name = "cython" }, { name = "inputs" }, { name = "jeepney" }, { name = "numpy" }, @@ -773,7 +772,6 @@ requires-dist = [ { name = "comma-deps-zeromq" }, { name = "comma-deps-zstd" }, { name = "coverage", marker = "extra == 'testing'" }, - { name = "cython" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, { name = "jeepney" }, From a8bb6b5aeedc74ad5929a0af52749f5103825a03 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 23 Jul 2026 09:17:18 -0700 Subject: [PATCH 025/170] rm hypothesis (#38428) --- openpilot/common/fuzzy.py | 240 ++++++++++++++++++ .../car/tests/test_car_interfaces.py | 39 +-- openpilot/selfdrive/car/tests/test_models.py | 15 +- openpilot/selfdrive/test/fuzzy_generation.py | 81 ------ .../test/process_replay/test_fuzzy.py | 16 +- pyproject.toml | 3 - uv.lock | 33 --- 7 files changed, 274 insertions(+), 153 deletions(-) create mode 100644 openpilot/common/fuzzy.py delete mode 100644 openpilot/selfdrive/test/fuzzy_generation.py diff --git a/openpilot/common/fuzzy.py b/openpilot/common/fuzzy.py new file mode 100644 index 0000000000..b355115191 --- /dev/null +++ b/openpilot/common/fuzzy.py @@ -0,0 +1,240 @@ +import math +import os +import random +import secrets +import struct +from collections.abc import Callable, Sequence +from functools import wraps +from typing import Any, TypeVar + +import capnp + + +T = TypeVar("T") + +_EDGE_SLOTS = 16 +_MINIMAL_EXAMPLES = 10 +_INTEGER_RANGES = { + "int8": (-2**7, 2**7 - 1), + "int16": (-2**15, 2**15 - 1), + "int32": (-2**31, 2**31 - 1), + "int64": (-2**63, 2**63 - 1), + "uint8": (0, 2**8 - 1), + "uint16": (0, 2**16 - 1), + "uint32": (0, 2**32 - 1), + "uint64": (0, 2**64 - 1), +} + +# One seed is shared by the whole test process. Individual tests derive their seed +# from their unittest ID, so FUZZ_SEED is reproducible under pytest-xdist too. +FUZZ_SEED = int(os.environ.get("FUZZ_SEED", secrets.randbits(64))) + + +class Fuzzy: + """Fast, deterministic data generator with systematic boundary coverage.""" + + def __init__(self, seed: int | str, example_index: int): + self.example_index = example_index + self._random = random.Random(seed) + self._draw_index = 0 + + def _draw(self, edges: Sequence[T], random_value: Callable[[], T]) -> T: + draw_index = self._draw_index + self._draw_index += 1 + + # Preserve the cheap minimal prefix Hypothesis produced, then interleave + # systematic boundaries and random values at every draw site. + if self.example_index < _MINIMAL_EXAMPLES: + return edges[0] + search_example = self.example_index - _MINIMAL_EXAMPLES + if search_example < _EDGE_SLOTS * 2 and search_example % 2 == 0: + return edges[(search_example // 2 + draw_index) % len(edges)] + if self._random.randrange(4) == 0: + return self._random.choice(edges) + return random_value() + + def boolean(self) -> bool: + return self._draw((False, True), lambda: bool(self._random.getrandbits(1))) + + def choice(self, values: Sequence[T]) -> T: + if not values: + raise ValueError("cannot choose from an empty sequence") + return self._draw(values, lambda: self._random.choice(values)) + + def integer(self, min_value: int, max_value: int) -> int: + if min_value > max_value: + raise ValueError(f"{min_value=} must not exceed {max_value=}") + + edges = [ + 0, 1, -1, min_value, max_value, + min_value + 1, max_value - 1, + ] + edges.extend(1 << bit for bit in range(max_value.bit_length())) + edges.extend(-(1 << bit) for bit in range((-min_value).bit_length())) + valid_edges = tuple(dict.fromkeys(v for v in edges if min_value <= v <= max_value)) + return self._draw(valid_edges, lambda: self._random.randint(min_value, max_value)) + + def floating(self, width: int = 64, *, allow_nan: bool = True, allow_infinity: bool = True) -> float: + if width not in (32, 64): + raise ValueError("float width must be 32 or 64") + + if width == 32: + unpack_format = "!f" + finite_edges = ( + 0.0, -0.0, 1.0, -1.0, + struct.unpack(unpack_format, b"\x00\x00\x00\x01")[0], + struct.unpack(unpack_format, b"\x80\x00\x00\x01")[0], + struct.unpack(unpack_format, b"\x7f\x7f\xff\xff")[0], + struct.unpack(unpack_format, b"\xff\x7f\xff\xff")[0], + struct.unpack(unpack_format, b"\x00\x80\x00\x00")[0], + struct.unpack(unpack_format, b"\x80\x80\x00\x00")[0], + ) + else: + unpack_format = "!d" + finite_edges = ( + 0.0, -0.0, 1.0, -1.0, + math.ulp(0.0), -math.ulp(0.0), + float.fromhex("0x1.fffffffffffffp+1023"), -float.fromhex("0x1.fffffffffffffp+1023"), + float.fromhex("0x1p-1022"), -float.fromhex("0x1p-1022"), + ) + + edges = list(finite_edges) + if allow_infinity: + edges.extend((math.inf, -math.inf)) + if allow_nan: + edges.append(math.nan) + + def random_float() -> float: + while True: + value = struct.unpack(unpack_format, self._random.randbytes(width // 8))[0] + if (allow_nan or not math.isnan(value)) and (allow_infinity or not math.isinf(value)): + return value + + return self._draw(tuple(edges), random_float) + + def _length(self, min_length: int, max_length: int | None) -> int: + if min_length < 0: + raise ValueError("minimum length must be non-negative") + if max_length is not None and min_length > max_length: + raise ValueError(f"{min_length=} must not exceed {max_length=}") + if max_length == min_length: + return min_length + + offsets = (0, 1, 2, 4, 8, 16, 32) + edges = tuple(min_length + offset for offset in offsets if max_length is None or min_length + offset <= max_length) + + def random_length() -> int: + # A geometric tail keeps ordinary examples small without placing an + # artificial ceiling on an unbounded list. + length = min_length + while max_length is None or length < max_length: + if self._random.randrange(8) == 0: + break + length += 1 + return length + + return self._draw(edges, random_length) + + def binary(self, min_size: int = 0, max_size: int | None = None) -> bytes: + size = self._length(min_size, max_size) + patterns = ( + bytes(size), + b"\xff" * size, + (b"\xaa\x55" * ((size + 1) // 2))[:size], + bytes(i & 0xff for i in range(size)), + ) + return self._draw(patterns, lambda: self._random.randbytes(size)) + + def text(self, min_size: int = 0, max_size: int | None = None) -> str: + size = self._length(min_size, max_size) + + def scalar() -> str: + value = self._random.randrange(0x110000 - 0x800) + if value >= 0xd800: + value += 0x800 + return chr(value) + + patterns = ( + "", + "a" * size, + "\0" * size, + "\U0010ffff" * size, + ) + valid_patterns = tuple(value for value in patterns if len(value) == size) + return self._draw(valid_patterns, lambda: "".join(scalar() for _ in range(size))) + + def list(self, generate: Callable[[], T], min_size: int = 0, max_size: int | None = None) -> list[T]: + return [generate() for _ in range(self._length(min_size, max_size))] + + +def fuzzy_test(max_examples: int) -> Callable[[Callable[..., None]], Callable[..., None]]: + """Run a unittest method repeatedly with independent, reproducible fuzzy data.""" + max_examples = int(os.environ.get("MAX_EXAMPLES", max_examples)) + assert max_examples >= 1 + + def decorator(fn: Callable[..., None]) -> Callable[..., None]: + @wraps(fn) + def wrapper(*args: Any, **kwargs: Any) -> None: + test_seed = f"{FUZZ_SEED}:{args[0].id()}" + selected_example = os.environ.get("FUZZ_EXAMPLE") + examples = [int(selected_example, 0)] if selected_example is not None else range(max_examples) + + for example_index in examples: + if not 0 <= example_index < max_examples: + raise ValueError(f"FUZZ_EXAMPLE={example_index} is outside [0, {max_examples})") + try: + fn(*args, **kwargs, fuzzy=Fuzzy(f"{test_seed}:{example_index}", example_index)) + except Exception as exc: + exc.add_note(f"reproduce with FUZZ_SEED={FUZZ_SEED} FUZZ_EXAMPLE={example_index}") + raise + + return wrapper + return decorator + + +def capnp_random_dict(fuzzy: Fuzzy, schema: Any, event: str | None = None, *, real_floats: bool = False) -> dict[str, Any]: + """Generate a dictionary accepted by a pycapnp struct constructor.""" + + def native(type_name: str) -> bool | int | float | str | bytes: + if type_name == "bool": + return fuzzy.boolean() + if type_name in _INTEGER_RANGES: + return fuzzy.integer(*_INTEGER_RANGES[type_name]) + if type_name in ("float32", "float64"): + return fuzzy.floating(width=int(type_name[-2:]), allow_nan=not real_floats, allow_infinity=not real_floats) + if type_name == "text": + return fuzzy.text(max_size=1000) + if type_name == "anyPointer": + return fuzzy.text() + if type_name == "data": + return fuzzy.binary(max_size=1000) + raise NotImplementedError(f"invalid Cap'n Proto type: {type_name}") + + def generate_field(field: Any) -> Any: + def rec(field_type: Any, base_type: str) -> Any: + type_name = field_type.which() + if type_name == "struct": + struct_schema = field.schema.elementType if base_type == "list" else field.schema + return capnp_random_dict(fuzzy, struct_schema, real_floats=real_floats) + if type_name == "list": + return fuzzy.list(lambda: rec(field_type.list.elementType, "list")) + if type_name == "enum": + enum_schema = field.schema.elementType if base_type == "list" else field.schema + return fuzzy.choice(tuple(enum_schema.enumerants)) + return native(type_name) + + try: + if hasattr(field.proto, "slot"): + slot_type = field.proto.slot.type + return rec(slot_type, slot_type.which()) + return capnp_random_dict(fuzzy, field.schema, real_floats=real_floats) + except capnp.lib.capnp.KjException: + return capnp_random_dict(fuzzy, field.schema, real_floats=real_floats) + + union_field = event or (fuzzy.choice(tuple(schema.union_fields)) if schema.union_fields else None) + fields = schema.non_union_fields + ((union_field,) if union_field else ()) + return { + field_name: generate_field(schema.fields[field_name]) + for field_name in fields + if not field_name.endswith("DEPRECATED") and field_name != "deprecated" + } diff --git a/openpilot/selfdrive/car/tests/test_car_interfaces.py b/openpilot/selfdrive/car/tests/test_car_interfaces.py index 1990b25981..f006d00722 100644 --- a/openpilot/selfdrive/car/tests/test_car_interfaces.py +++ b/openpilot/selfdrive/car/tests/test_car_interfaces.py @@ -1,36 +1,44 @@ -import os -import hypothesis.strategies as st -from hypothesis import Phase, given, settings from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized +from openpilot.common.fuzzy import capnp_random_dict, fuzzy_test from opendbc.car.structs import car from opendbc.car import DT_CTRL +from opendbc.car.car_helpers import interfaces +from opendbc.car.fingerprints import FW_VERSIONS +from opendbc.car.fw_versions import FW_QUERY_CONFIGS from opendbc.car.structs import CarParams -from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface from opendbc.car.mock.values import CAR as MOCK from opendbc.car.values import PLATFORMS from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque from openpilot.selfdrive.controls.lib.longcontrol import LongControl -from openpilot.selfdrive.test.fuzzy_generation import FuzzyGenerator -MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '60')) +ALL_ECUS = tuple(sorted({ecu for ecus in FW_VERSIONS.values() for ecu in ecus} | + {ecu for config in FW_QUERY_CONFIGS.values() for ecu in config.extra_ecus})) +ALL_REQUESTS = tuple(sorted({tuple(request.request) for config in FW_QUERY_CONFIGS.values() for request in config.requests})) +DLC_TO_LEN = (0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64) class TestCarInterfaces(OpenpilotTestCase): - # FIXME: Due to the lists used in carParams, Phase.target is very slow and will cause - # many generated examples to overrun when max_examples > ~20, don't use it @parameterized.expand([(car,) for car in sorted(PLATFORMS)] + [MOCK.MOCK]) - @settings(max_examples=MAX_EXAMPLES, deadline=None, - phases=(Phase.reuse, Phase.generate, Phase.shrink)) - @given(data=st.data()) - def test_car_interfaces(self, car_name, data): - car_interface = get_fuzzy_car_interface(car_name, data.draw) + @fuzzy_test(max_examples=60) + def test_car_interfaces(self, car_name, fuzzy): + fingerprint = dict(fuzzy.list(lambda: (fuzzy.integer(0, 0x800), fuzzy.choice(DLC_TO_LEN)))) + fingerprints = dict.fromkeys(range(7), fingerprint) + + def generate_car_fw(): + ecu, address, sub_address = fuzzy.choice(ALL_ECUS) + return CarParams.CarFw(ecu=ecu, address=address, subAddress=sub_address or 0, request=fuzzy.choice(ALL_REQUESTS)) + + CarInterface = interfaces[car_name] + car_params = CarInterface.get_params(car_name, fingerprints, fuzzy.list(generate_car_fw), + alpha_long=fuzzy.boolean(), is_release=False, docs=False) + car_interface = CarInterface(car_params) car_params = car_interface.CP.as_reader() - cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True) + cc_msg = capnp_random_dict(fuzzy, car.CarControl.schema, real_floats=True) # Run car interface now_nanos = 0 CC = car.CarControl.new_message(**cc_msg) @@ -51,8 +59,7 @@ class TestCarInterfaces(OpenpilotTestCase): now_nanos += DT_CTRL * 1e9 # 10ms # Test controller initialization - # TODO: wait until card refactor is merged to run controller a few times, - # hypothesis also slows down significantly with just one more message draw + # TODO: wait until card refactor is merged to run controller a few times LongControl(car_params) if car_params.steerControlType == CarParams.SteerControlType.angle: LatControlAngle(car_params, car_interface, DT_CTRL) diff --git a/openpilot/selfdrive/car/tests/test_models.py b/openpilot/selfdrive/car/tests/test_models.py index e8d9c6cb42..29d4eabcc0 100644 --- a/openpilot/selfdrive/car/tests/test_models.py +++ b/openpilot/selfdrive/car/tests/test_models.py @@ -3,8 +3,7 @@ import os import random import unittest from collections import defaultdict, Counter -import hypothesis.strategies as st -from hypothesis import Phase, given, settings +from openpilot.common.fuzzy import fuzzy_test from openpilot.common.parameterized import parameterized_class from openpilot.common.test import OpenpilotTestCase from opendbc.car import DT_CTRL, gen_empty_fingerprint, structs @@ -39,7 +38,6 @@ NUM_JOBS = int(os.environ.get("NUM_JOBS", "1")) JOB_ID = int(os.environ.get("JOB_ID", "0")) INTERNAL_SEG_LIST = os.environ.get("INTERNAL_SEG_LIST", "") INTERNAL_SEG_CNT = int(os.environ.get("INTERNAL_SEG_CNT", "0")) -MAX_EXAMPLES = int(os.environ.get("MAX_EXAMPLES", "300")) CI = os.environ.get("CI", None) is not None @@ -303,10 +301,8 @@ class TestCarModelBase(OpenpilotTestCase): test_car_controller(CC.as_reader()) # Capturing stdout/stderr here causes elevated memory usage. - @settings(max_examples=MAX_EXAMPLES, deadline=None, - phases=(Phase.reuse, Phase.generate, Phase.shrink)) - @given(data=st.data()) - def test_panda_safety_carstate_fuzzy(self, data): + @fuzzy_test(max_examples=300) + def test_panda_safety_carstate_fuzzy(self, fuzzy): """ For each example, pick a random CAN message on the bus and fuzz its data, checking for panda state mismatches. @@ -316,10 +312,9 @@ class TestCarModelBase(OpenpilotTestCase): self.skipTest("no need to check panda safety for dashcamOnly") valid_addrs = [(addr, bus, size) for bus, addrs in self.fingerprint.items() for addr, size in addrs.items()] - address, bus, size = data.draw(st.sampled_from(valid_addrs)) + address, bus, size = fuzzy.choice(valid_addrs) - msg_strategy = st.binary(min_size=size, max_size=size) - msgs = data.draw(st.lists(msg_strategy, min_size=20)) + msgs = fuzzy.list(lambda: fuzzy.binary(min_size=size, max_size=size), min_size=20) vehicle_speed_seen = self.CP.steerControlType == SteerControlType.angle and not self.CP.notCar diff --git a/openpilot/selfdrive/test/fuzzy_generation.py b/openpilot/selfdrive/test/fuzzy_generation.py deleted file mode 100644 index c97221ae9e..0000000000 --- a/openpilot/selfdrive/test/fuzzy_generation.py +++ /dev/null @@ -1,81 +0,0 @@ -import capnp -import hypothesis.strategies as st -from typing import Any -from collections.abc import Callable -from functools import cache - -from openpilot.cereal import log - -DrawType = Callable[[st.SearchStrategy], Any] - - -class FuzzyGenerator: - def __init__(self, draw: DrawType, real_floats: bool): - self.draw = draw - self.native_type_map = FuzzyGenerator._get_native_type_map(real_floats) - - def generate_native_type(self, field: str) -> st.SearchStrategy[bool | int | float | str | bytes]: - value_func = self.native_type_map.get(field) - if value_func is not None: - return value_func - else: - raise NotImplementedError(f'Invalid type: {field}') - - def generate_field(self, field: capnp.lib.capnp._StructSchemaField) -> st.SearchStrategy: - def rec(field_type: capnp.lib.capnp._DynamicStructReader) -> st.SearchStrategy: - type_which = field_type.which() - if type_which == 'struct': - return self.generate_struct(field.schema.elementType if base_type == 'list' else field.schema) - elif type_which == 'list': - return st.lists(rec(field_type.list.elementType)) - elif type_which == 'enum': - schema = field.schema.elementType if base_type == 'list' else field.schema - return st.sampled_from(list(schema.enumerants.keys())) - else: - return self.generate_native_type(type_which) - - try: - if hasattr(field.proto, 'slot'): - slot_type = field.proto.slot.type - base_type = slot_type.which() - return rec(slot_type) - else: - return self.generate_struct(field.schema) - except capnp.lib.capnp.KjException: - return self.generate_struct(field.schema) - - def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str | None = None) -> st.SearchStrategy[dict[str, Any]]: - single_fill: tuple[str, ...] = (event,) if event else (self.draw(st.sampled_from(schema.union_fields)),) if schema.union_fields else () - fields_to_generate = [f for f in schema.non_union_fields + single_fill if not f.endswith('DEPRECATED') and f != 'deprecated'] - return st.fixed_dictionaries({field: self.generate_field(schema.fields[field]) for field in fields_to_generate}) - - @staticmethod - @cache - def _get_native_type_map(real_floats: bool) -> dict[str, st.SearchStrategy]: - return { - 'bool': st.booleans(), - 'int8': st.integers(min_value=-2**7, max_value=2**7-1), - 'int16': st.integers(min_value=-2**15, max_value=2**15-1), - 'int32': st.integers(min_value=-2**31, max_value=2**31-1), - 'int64': st.integers(min_value=-2**63, max_value=2**63-1), - 'uint8': st.integers(min_value=0, max_value=2**8-1), - 'uint16': st.integers(min_value=0, max_value=2**16-1), - 'uint32': st.integers(min_value=0, max_value=2**32-1), - 'uint64': st.integers(min_value=0, max_value=2**64-1), - 'float32': st.floats(width=32, allow_nan=not real_floats, allow_infinity=not real_floats), - 'float64': st.floats(width=64, allow_nan=not real_floats, allow_infinity=not real_floats), - 'text': st.text(max_size=1000), - 'data': st.binary(max_size=1000), - 'anyPointer': st.text(), # Note: No need to define a separate function for anyPointer - } - - @classmethod - def get_random_msg(cls, draw: DrawType, struct: capnp.lib.capnp._StructModule, real_floats: bool = False) -> dict[str, Any]: - fg = cls(draw, real_floats=real_floats) - data: dict[str, Any] = draw(fg.generate_struct(struct.schema)) - return data - - @classmethod - def get_random_event_msg(cls, draw: DrawType, events: list[str], real_floats: bool = False) -> list[dict[str, Any]]: - fg = cls(draw, real_floats=real_floats) - return [draw(fg.generate_struct(log.Event.schema, e)) for e in sorted(events)] diff --git a/openpilot/selfdrive/test/process_replay/test_fuzzy.py b/openpilot/selfdrive/test/process_replay/test_fuzzy.py index 9a5bb41d48..3a87f3a7f8 100644 --- a/openpilot/selfdrive/test/process_replay/test_fuzzy.py +++ b/openpilot/selfdrive/test/process_replay/test_fuzzy.py @@ -1,13 +1,10 @@ import copy -import os -from hypothesis import given, HealthCheck, Phase, settings -import hypothesis.strategies as st from openpilot.common.test import OpenpilotTestCase from openpilot.common.parameterized import parameterized +from openpilot.common.fuzzy import capnp_random_dict, fuzzy_test from openpilot.cereal import log from opendbc.car.toyota.values import CAR as TOYOTA -from openpilot.selfdrive.test.fuzzy_generation import FuzzyGenerator import openpilot.selfdrive.test.process_replay.process_replay as pr # These processes currently fail because of unrealistic data breaking assumptions @@ -16,17 +13,16 @@ import openpilot.selfdrive.test.process_replay.process_replay as pr NOT_TESTED = ['selfdrived', 'controlsd', 'card', 'plannerd', 'calibrationd', 'dmonitoringd', 'paramsd', 'dmonitoringmodeld', 'modeld'] TEST_CASES = [(cfg.proc_name, copy.deepcopy(cfg)) for cfg in pr.CONFIGS if cfg.proc_name not in NOT_TESTED] -MAX_EXAMPLES = int(os.environ.get("MAX_EXAMPLES", "10")) class TestFuzzProcesses(OpenpilotTestCase): # TODO: make this faster and increase examples @parameterized.expand(TEST_CASES) - @given(st.data()) - @settings(phases=[Phase.generate, Phase.target], max_examples=MAX_EXAMPLES, deadline=1000, - suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large]) - def test_fuzz_process(self, proc_name, cfg, data): - msgs = FuzzyGenerator.get_random_event_msg(data.draw, events=cfg.pubs, real_floats=True) + @fuzzy_test(max_examples=10) + def test_fuzz_process(self, proc_name, cfg, fuzzy): + msgs = [capnp_random_dict(fuzzy, log.Event.schema, event, real_floats=True) for event in sorted(cfg.pubs)] + for i, msg in enumerate(msgs): + msg["logMonoTime"] = i * 1_000_000_000 lr = [log.Event.new_message(**m).as_reader() for m in msgs] cfg.timeout = 5 pr.replay_process(cfg, lr, fingerprint=TOYOTA.TOYOTA_COROLLA_TSS2, disable_progress=True) diff --git a/pyproject.toml b/pyproject.toml index 70ccee02d2..4b63b38005 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,9 +60,6 @@ testing = [ "ruff", # linting "codespell", # spellcheck - # TODO: replace this with our own implementation - "hypothesis ==6.47.*", - # TODO: replace these with our own nice simple test runner "pytest", "pytest-xdist", diff --git a/uv.lock b/uv.lock index 8b6c7c2f57..a98fcc3af5 100644 --- a/uv.lock +++ b/uv.lock @@ -5,15 +5,6 @@ requires-python = ">=3.12.3, <3.13" [manifest] overrides = [{ name = "opendbc", editable = "opendbc_repo" }] -[[package]] -name = "attrs" -version = "26.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, -] - [[package]] name = "certifi" version = "2026.6.17" @@ -404,19 +395,6 @@ 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 = "hypothesis" -version = "6.47.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "sortedcontainers" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/45/f2/f77da8271b1abb630cb2090ead2f5aa4acc9639d632e8e68187f52527e4b/hypothesis-6.47.5.tar.gz", hash = "sha256:e0c1e253fc97e7ecdb9e2bbff2cf815d8739e0d1d3d093d67c3af5bb6a7211b0", size = 326641, upload-time = "2022-06-25T20:58:48.926Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/a7/389bbaade2cbbb2534cb2715986041ed01c6d792152c527e71f7f68e93b5/hypothesis-6.47.5-py3-none-any.whl", hash = "sha256:87049b781ee11ec1c7948565b889ab02e428a1e32d427ab4de8fdb3649242d06", size = 387311, upload-time = "2022-06-25T20:58:45.281Z" }, -] - [[package]] name = "idna" version = "3.18" @@ -735,7 +713,6 @@ submodules = [ testing = [ { name = "codespell" }, { name = "coverage" }, - { name = "hypothesis" }, { name = "pytest" }, { name = "pytest-xdist" }, { name = "ruff" }, @@ -772,7 +749,6 @@ requires-dist = [ { name = "comma-deps-zeromq" }, { name = "comma-deps-zstd" }, { name = "coverage", marker = "extra == 'testing'" }, - { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, { name = "jeepney" }, { name = "matplotlib", marker = "extra == 'dev'" }, @@ -1186,15 +1162,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] -[[package]] -name = "sortedcontainers" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, -] - [[package]] name = "sounddevice" version = "0.5.5" From e1c69719c6714b1c1f479dba1e6a4bf5d137ec07 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 23 Jul 2026 09:35:05 -0700 Subject: [PATCH 026/170] bump panda (#38429) --- panda | 2 +- uv.lock | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/panda b/panda index 9da8467a7c..d9ed70b850 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 9da8467a7c3789733ba2afe751e558f6a0ea750c +Subproject commit d9ed70b850513f65e2a69707835f818409fe6052 diff --git a/uv.lock b/uv.lock index a98fcc3af5..2617d11f7a 100644 --- a/uv.lock +++ b/uv.lock @@ -812,7 +812,6 @@ requires-dist = [ { name = "libusb-package" }, { name = "libusb1" }, { name = "opendbc", git = "https://github.com/commaai/opendbc.git?rev=master" }, - { name = "pycryptodome", marker = "extra == 'dev'", specifier = ">=3.9.8" }, { name = "pytest", marker = "extra == 'dev'" }, { name = "pytest-mock", marker = "extra == 'dev'" }, { name = "pytest-timeout", marker = "extra == 'dev'" }, From cfa8e5bf030b75c4de5fd5419a1358461960e4c5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 23 Jul 2026 09:42:17 -0700 Subject: [PATCH 027/170] gitignore test_swaglog --- openpilot/common/tests/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/openpilot/common/tests/.gitignore b/openpilot/common/tests/.gitignore index 6cddfc7bdf..74a3bbc977 100644 --- a/openpilot/common/tests/.gitignore +++ b/openpilot/common/tests/.gitignore @@ -1 +1,2 @@ test_common +test_swaglog From 3601b850c2cbf8addc244b019d5f8854b4a4bba9 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Thu, 23 Jul 2026 09:57:47 -0700 Subject: [PATCH 028/170] new sounds for 3x (#38404) rm tizi sounds --- openpilot/selfdrive/assets/sounds/disengage_tizi.wav | 3 --- openpilot/selfdrive/assets/sounds/engage_tizi.wav | 3 --- openpilot/selfdrive/ui/soundd.py | 5 ----- 3 files changed, 11 deletions(-) delete mode 100644 openpilot/selfdrive/assets/sounds/disengage_tizi.wav delete mode 100644 openpilot/selfdrive/assets/sounds/engage_tizi.wav diff --git a/openpilot/selfdrive/assets/sounds/disengage_tizi.wav b/openpilot/selfdrive/assets/sounds/disengage_tizi.wav deleted file mode 100644 index f3b5f21a27..0000000000 --- a/openpilot/selfdrive/assets/sounds/disengage_tizi.wav +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f061777d66d8d856a5fcb17378416f959088885ed770181355195ad15de881b -size 68628 diff --git a/openpilot/selfdrive/assets/sounds/engage_tizi.wav b/openpilot/selfdrive/assets/sounds/engage_tizi.wav deleted file mode 100644 index fc24a23c2f..0000000000 --- a/openpilot/selfdrive/assets/sounds/engage_tizi.wav +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6b54a85cc09b8ee79fce23d48209205d2e70510eed4c5a86fa400fe3f7caebfd -size 63120 diff --git a/openpilot/selfdrive/ui/soundd.py b/openpilot/selfdrive/ui/soundd.py index 5cbb1298f9..b87585a041 100644 --- a/openpilot/selfdrive/ui/soundd.py +++ b/openpilot/selfdrive/ui/soundd.py @@ -49,11 +49,6 @@ sound_list: dict[int, tuple[str, int | None, float]] = { AudibleAlert.warningSoft: ("critical.wav", None, MAX_VOLUME), AudibleAlert.warningImmediate: ("dm_critical.wav", None, MAX_VOLUME), } -if HARDWARE.get_device_type() == "tizi": - sound_list.update({ - AudibleAlert.engage: ("engage_tizi.wav", 1, MAX_VOLUME), - AudibleAlert.disengage: ("disengage_tizi.wav", 1, MAX_VOLUME), - }) def check_selfdrive_timeout_alert(sm): ss_missing = time.monotonic() - sm.recv_time['selfdriveState'] From 6b47a5b6b770e1b532ff8dbc465a69b02971af62 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 23 Jul 2026 10:06:44 -0700 Subject: [PATCH 029/170] tools: decompress in the python downloader (#38430) * tools: decompress in the python downloader * and zstd --- SConstruct | 2 +- openpilot/tools/cabana/SConscript | 2 +- openpilot/tools/jotpluggler/SConscript | 2 +- openpilot/tools/lib/file_downloader.py | 112 ++++++++++++++++++++++-- openpilot/tools/replay/SConscript | 2 +- openpilot/tools/replay/filereader.cc | 15 ++++ openpilot/tools/replay/logreader.cc | 10 --- openpilot/tools/replay/py_downloader.cc | 4 + openpilot/tools/replay/py_downloader.h | 3 + openpilot/tools/replay/util.cc | 88 ------------------- openpilot/tools/replay/util.h | 4 - pyproject.toml | 1 - uv.lock | 12 --- 13 files changed, 133 insertions(+), 124 deletions(-) diff --git a/SConstruct b/SConstruct index 630cfe1c0d..18efb9f8a1 100644 --- a/SConstruct +++ b/SConstruct @@ -55,7 +55,7 @@ assert arch in [ "Darwin", # macOS arm64 (x86 not supported) ] -pkg_names = ['acados', 'bzip2', 'capnproto', 'ffmpeg', 'json11', 'ncurses', 'zeromq', 'zstd'] +pkg_names = ['acados', 'capnproto', 'ffmpeg', 'json11', 'ncurses', 'zeromq', 'zstd'] pkgs = [importlib.import_module(name) for name in pkg_names] acados = pkgs[pkg_names.index('acados')] ffmpeg = pkgs[pkg_names.index('ffmpeg')] diff --git a/openpilot/tools/cabana/SConscript b/openpilot/tools/cabana/SConscript index 805bc41876..387129709e 100644 --- a/openpilot/tools/cabana/SConscript +++ b/openpilot/tools/cabana/SConscript @@ -73,7 +73,7 @@ cabana_env = qt_env.Clone() cabana_env['CPPPATH'] += [libusb.INCLUDE_DIR] cabana_env['LIBPATH'] += [libusb.LIB_DIR] -cabana_libs = [cereal, messaging, visionipc, replay_lib] + ffmpeg_libs + ['bz2', 'zstd', 'usb-1.0'] + base_libs +cabana_libs = [cereal, messaging, visionipc, replay_lib] + ffmpeg_libs + ['usb-1.0'] + base_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../../opendbc_repo/opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] diff --git a/openpilot/tools/jotpluggler/SConscript b/openpilot/tools/jotpluggler/SConscript index d5ebaffb98..bfe6e90742 100644 --- a/openpilot/tools/jotpluggler/SConscript +++ b/openpilot/tools/jotpluggler/SConscript @@ -102,7 +102,7 @@ event_extractors = jot_env.Command("generated_event_extractors.h", [ ) libs = [replay_lib, common, messaging, visionipc, cereal, File(f"{imgui.LIB_DIR}/libimgui.a"), File(f"{imgui.LIB_DIR}/libglfw3.a")] + \ - ffmpeg_libs + ["bz2", "zstd", "m", "pthread", "usb-1.0"] + ffmpeg_libs + ["zstd", "m", "pthread", "usb-1.0"] if arch == "Darwin": jot_env["FRAMEWORKS"] = ["OpenGL", "Cocoa", "IOKit", "CoreFoundation", "CoreVideo", "CoreMedia", "VideoToolbox"] else: diff --git a/openpilot/tools/lib/file_downloader.py b/openpilot/tools/lib/file_downloader.py index 68061b201e..efb06095be 100755 --- a/openpilot/tools/lib/file_downloader.py +++ b/openpilot/tools/lib/file_downloader.py @@ -5,17 +5,21 @@ Called by C++ replay/cabana via subprocess. Subcommands: route-files - Get route file URLs as JSON - download - Download URL to local cache, print local path + download - Download/decompress URL to local cache, print local path + decompress - Decompress a local log file, print temporary path devices - List user's devices as JSON device-routes - List routes for a device as JSON """ import argparse +import bz2 import hashlib import json import os +import shutil import sys import tempfile -import shutil + +import zstandard as zstd from openpilot.common.hardware.hw import Paths from openpilot.tools.lib.api import CommaApi, UnauthorizedError, APIError @@ -39,11 +43,60 @@ def api_call(func): sys.stdout.flush() -def cache_file_path(url): +def cache_file_path(url, compression=None): url_without_query = url.split("?")[0] + if compression: + url_without_query = f"decompressed-{compression}:{url_without_query}" return os.path.join(Paths.download_cache_root(), hashlib.sha256(url_without_query.encode()).hexdigest()) +def compression_type(data): + if data.startswith(b'BZh'): + return 'bz2' + if data.startswith(b'\x28\xb5\x2f\xfd'): + return 'zst' + return None + + +def make_decompressor(compression): + if compression == 'bz2': + return bz2.BZ2Decompressor() + if compression == 'zst': + return zstd.ZstdDecompressor().decompressobj() + raise ValueError(f"Unsupported compression type: {compression}") + + +def decompress_file(source, destination, compression=None): + with open(source, 'rb') as src, open(destination, 'wb') as dst: + header = src.read(4) + compression = compression or compression_type(header) + decompressor = make_decompressor(compression) + dst.write(decompressor.decompress(header)) + while data := src.read(1024 * 1024): + dst.write(decompressor.decompress(data)) + if not decompressor.eof: + raise EOFError(f"Compressed {compression} file ended before the end-of-stream marker") + + +def materialize_cached_file(source, url, compression): + local_path = cache_file_path(url, compression) + if os.path.exists(local_path): + return local_path + + tmp_fd, tmp_path = tempfile.mkstemp(dir=Paths.download_cache_root()) + os.close(tmp_fd) + try: + decompress_file(source, tmp_path, compression) + shutil.move(tmp_path, local_path) + except Exception: + try: + os.unlink(tmp_path) + except OSError: + pass + raise + return local_path + + def cmd_route_files(args): api_call(lambda api: api.get(f"v1/route/{args.route}/files")) @@ -53,8 +106,19 @@ def cmd_download(args): use_cache = not args.no_cache if use_cache: + for compression in ('bz2', 'zst'): + decompressed_path = cache_file_path(url, compression) + if os.path.exists(decompressed_path): + sys.stdout.write(decompressed_path + "\n") + sys.stdout.flush() + return + local_path = cache_file_path(url) if os.path.exists(local_path): + with open(local_path, 'rb') as f: + compression = compression_type(f.read(4)) + if compression: + local_path = materialize_cached_file(local_path, url, compression) sys.stdout.write(local_path + "\n") sys.stdout.flush() return @@ -80,14 +144,30 @@ def cmd_download(args): try: downloaded = 0 chunk_size = 1024 * 1024 + compression = None + decompressor = None with os.fdopen(tmp_fd, 'wb') as f: for data in r.stream(chunk_size): - f.write(data) + if downloaded == 0: + compression = compression_type(data) + if compression: + decompressor = make_decompressor(compression) + f.write(decompressor.decompress(data) if decompressor else data) downloaded += len(data) sys.stderr.write(f"PROGRESS:{downloaded}:{total}\n") sys.stderr.flush() - if use_cache: + if decompressor and not decompressor.eof: + raise EOFError(f"Compressed {compression} file ended before the end-of-stream marker") + + if decompressor: + if use_cache: + output_path = cache_file_path(url, compression) + shutil.move(tmp_path, output_path) + else: + output_path = tmp_path + sys.stdout.write(output_path + "\n") + elif use_cache: shutil.move(tmp_path, local_path) sys.stdout.write(local_path + "\n") else: @@ -109,6 +189,24 @@ def cmd_download(args): sys.stdout.flush() +def cmd_decompress(args): + os.makedirs(Paths.download_cache_root(), exist_ok=True) + output_fd, output_path = tempfile.mkstemp(dir=Paths.download_cache_root()) + os.close(output_fd) + try: + decompress_file(args.path, output_path) + except Exception as e: + try: + os.unlink(output_path) + except OSError: + pass + sys.stderr.write(f"ERROR:{e}\n") + sys.stderr.flush() + sys.exit(1) + sys.stdout.write(output_path + "\n") + sys.stdout.flush() + + def cmd_devices(args): api_call(lambda api: api.get("v1/me/devices/")) @@ -139,6 +237,10 @@ def main(): p_dl.add_argument("--no-cache", action="store_true") p_dl.set_defaults(func=cmd_download) + p_dc = subparsers.add_parser("decompress") + p_dc.add_argument("path") + p_dc.set_defaults(func=cmd_decompress) + p_dev = subparsers.add_parser("devices") p_dev.set_defaults(func=cmd_devices) diff --git a/openpilot/tools/replay/SConscript b/openpilot/tools/replay/SConscript index c5abae502c..bd48ecb4d9 100644 --- a/openpilot/tools/replay/SConscript +++ b/openpilot/tools/replay/SConscript @@ -12,5 +12,5 @@ if arch != "Darwin": replay_lib_src.append("qcom_decoder.cc") replay_lib = replay_env.Library("replay", replay_lib_src, LIBS=base_libs, FRAMEWORKS=base_frameworks) Export('replay_lib') -replay_libs = [replay_lib] + ffmpeg_libs + ['bz2', 'zstd', 'ncurses'] + base_libs +replay_libs = [replay_lib] + ffmpeg_libs + ['ncurses'] + base_libs replay_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) diff --git a/openpilot/tools/replay/filereader.cc b/openpilot/tools/replay/filereader.cc index 93a6a1193f..6ad56bbfc6 100644 --- a/openpilot/tools/replay/filereader.cc +++ b/openpilot/tools/replay/filereader.cc @@ -1,5 +1,8 @@ #include "tools/replay/filereader.h" +#include +#include + #include "common/util.h" #include "tools/replay/py_downloader.h" @@ -10,5 +13,17 @@ std::string FileReader::read(const std::string &file, std::atomic *abort) if (local_path.empty()) return {}; return util::read_file(local_path); } + char header[4] = {}; + std::ifstream stream(file, std::ios::binary); + stream.read(header, sizeof(header)); + const std::string magic(header, stream.gcount()); + if (util::ends_with(file, ".bz2") || util::ends_with(file, ".zst") || + util::starts_with(magic, "BZh") || magic == "\x28\xB5\x2F\xFD") { + std::string local_path = PyDownloader::decompress(file, abort); + if (local_path.empty()) return {}; + std::string data = util::read_file(local_path); + unlink(local_path.c_str()); + return data; + } return util::read_file(file); } diff --git a/openpilot/tools/replay/logreader.cc b/openpilot/tools/replay/logreader.cc index 54b69dc168..0416413b4c 100644 --- a/openpilot/tools/replay/logreader.cc +++ b/openpilot/tools/replay/logreader.cc @@ -32,16 +32,6 @@ bool LogReader::load(const std::string &url, std::atomic *abort, bool loca } compressed_size_ = data.size(); download_seconds_ = std::chrono::duration(download_end - download_start).count(); - if (!data.empty()) { - const auto decompress_start = Clock::now(); - if (url.find(".bz2") != std::string::npos || util::starts_with(data, "BZh9")) { - data = decompressBZ2(data, abort); - } else if (url.find(".zst") != std::string::npos || util::starts_with(data, "\x28\xB5\x2F\xFD")) { - data = decompressZST(data, abort); - } - const auto decompress_end = Clock::now(); - decompress_seconds_ = std::chrono::duration(decompress_end - decompress_start).count(); - } decompressed_size_ = data.size(); bool success = !data.empty() && load(data.data(), data.size(), abort, progress); diff --git a/openpilot/tools/replay/py_downloader.cc b/openpilot/tools/replay/py_downloader.cc index d27a77e6ee..a265dfd6a3 100644 --- a/openpilot/tools/replay/py_downloader.cc +++ b/openpilot/tools/replay/py_downloader.cc @@ -152,6 +152,10 @@ std::string download(const std::string &url, bool use_cache, std::atomic * return runPython(args, abort); } +std::string decompress(const std::string &path, std::atomic *abort) { + return runPython({"decompress", path}, abort); +} + std::string getRouteFiles(const std::string &route) { return runPython({"route-files", route}); } diff --git a/openpilot/tools/replay/py_downloader.h b/openpilot/tools/replay/py_downloader.h index 535189784c..80fab6ab00 100644 --- a/openpilot/tools/replay/py_downloader.h +++ b/openpilot/tools/replay/py_downloader.h @@ -12,6 +12,9 @@ namespace PyDownloader { // Downloads url to local cache, returns local file path. Reports progress via installDownloadProgressHandler. std::string download(const std::string &url, bool use_cache = true, std::atomic *abort = nullptr); +// Decompresses a local log file and returns the temporary output path. +std::string decompress(const std::string &path, std::atomic *abort = nullptr); + // Returns JSON string of route files (same format as /v1/route/.../files API) std::string getRouteFiles(const std::string &route); diff --git a/openpilot/tools/replay/util.cc b/openpilot/tools/replay/util.cc index 7b308b5c3d..44e144443a 100644 --- a/openpilot/tools/replay/util.cc +++ b/openpilot/tools/replay/util.cc @@ -1,14 +1,10 @@ #include "tools/replay/util.h" -#include - #include #include #include #include #include -#include - #include "common/timing.h" #include "common/util.h" @@ -58,90 +54,6 @@ std::string getUrlWithoutQuery(const std::string &url) { return (idx == std::string::npos ? url : url.substr(0, idx)); } -std::string decompressBZ2(const std::string &in, std::atomic *abort) { - return decompressBZ2((std::byte *)in.data(), in.size(), abort); -} - -std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic *abort) { - if (in_size == 0) return {}; - - bz_stream strm = {}; - int bzerror = BZ2_bzDecompressInit(&strm, 0, 0); - assert(bzerror == BZ_OK); - - strm.next_in = (char *)in; - strm.avail_in = in_size; - std::string out(in_size * 5, '\0'); - do { - strm.next_out = (char *)(&out[strm.total_out_lo32]); - strm.avail_out = out.size() - strm.total_out_lo32; - - const char *prev_write_pos = strm.next_out; - bzerror = BZ2_bzDecompress(&strm); - if (bzerror == BZ_OK && prev_write_pos == strm.next_out) { - // content is corrupt - bzerror = BZ_STREAM_END; - rWarning("decompressBZ2 error: content is corrupt"); - break; - } - - if (bzerror == BZ_OK && strm.avail_in > 0 && strm.avail_out == 0) { - out.resize(out.size() * 2); - } - } while (bzerror == BZ_OK && !(abort && *abort)); - - BZ2_bzDecompressEnd(&strm); - if (bzerror == BZ_STREAM_END && !(abort && *abort)) { - out.resize(strm.total_out_lo32); - out.shrink_to_fit(); - return out; - } - return {}; -} - -std::string decompressZST(const std::string &in, std::atomic *abort) { - return decompressZST((std::byte *)in.data(), in.size(), abort); -} - -std::string decompressZST(const std::byte *in, size_t in_size, std::atomic *abort) { - ZSTD_DCtx *dctx = ZSTD_createDCtx(); - assert(dctx != nullptr); - - // Initialize input and output buffers - ZSTD_inBuffer input = {in, in_size, 0}; - - // Estimate and reserve memory for decompressed data - size_t estimatedDecompressedSize = ZSTD_getFrameContentSize(in, in_size); - if (estimatedDecompressedSize == ZSTD_CONTENTSIZE_ERROR || estimatedDecompressedSize == ZSTD_CONTENTSIZE_UNKNOWN) { - estimatedDecompressedSize = in_size * 2; // Use a fallback size - } - - std::string decompressedData; - decompressedData.reserve(estimatedDecompressedSize); - - const size_t bufferSize = ZSTD_DStreamOutSize(); // Recommended output buffer size - std::string outputBuffer(bufferSize, '\0'); - - while (input.pos < input.size && !(abort && *abort)) { - ZSTD_outBuffer output = {outputBuffer.data(), bufferSize, 0}; - - size_t result = ZSTD_decompressStream(dctx, &output, &input); - if (ZSTD_isError(result)) { - rWarning("decompressZST error: content is corrupt"); - break; - } - - decompressedData.append(outputBuffer.data(), output.pos); - } - - ZSTD_freeDCtx(dctx); - if (!(abort && *abort)) { - decompressedData.shrink_to_fit(); - return decompressedData; - } - return {}; -} - void precise_nano_sleep(int64_t nanoseconds, std::atomic &interrupt_requested) { struct timespec req, rem; req.tv_sec = nanoseconds / 1000000000; diff --git a/openpilot/tools/replay/util.h b/openpilot/tools/replay/util.h index 03a15787b2..45d6f7cd0b 100644 --- a/openpilot/tools/replay/util.h +++ b/openpilot/tools/replay/util.h @@ -47,10 +47,6 @@ private: }; void precise_nano_sleep(int64_t nanoseconds, std::atomic &interrupt_requested); -std::string decompressBZ2(const std::string &in, std::atomic *abort = nullptr); -std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); -std::string decompressZST(const std::string &in, std::atomic *abort = nullptr); -std::string decompressZST(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); std::string getUrlWithoutQuery(const std::string &url); std::string formattedDataSize(size_t size); std::string extractFileName(const std::string& file); diff --git a/pyproject.toml b/pyproject.toml index 4b63b38005..c310d7d871 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,6 @@ dev = [ tools = [ "comma-deps-imgui", - "comma-deps-bzip2", "comma-deps-bootstrap-icons", "comma-deps-libusb", "comma-deps-ncurses", diff --git a/uv.lock b/uv.lock index 2617d11f7a..8fe08f2ed4 100644 --- a/uv.lock +++ b/uv.lock @@ -113,16 +113,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/0b/bb713dd4bed94b0b2b21657b7337e264953bd785a2b2f5c1b0706cdcde29/comma_deps_bootstrap_icons-1.10.5.0.post95-py3-none-any.whl", hash = "sha256:d59fc8d3e642e00f83d7a4854164f1dee21c3d17c726d5537b1b72b149f5788b", size = 386001, upload-time = "2026-06-24T23:58:37.84Z" }, ] -[[package]] -name = "comma-deps-bzip2" -version = "1.0.8.post95" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/ac/539d76e9bcebbc53d62274a19c47522b118dce3261fc6baea045f93c98b6/comma_deps_bzip2-1.0.8.post95-py3-none-macosx_11_0_arm64.whl", hash = "sha256:be466743b7fc56fa18c2c389a46bdeb0fc885cf3cf39b779d748ce540609ebd5", size = 42832, upload-time = "2026-06-24T23:58:39.592Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fe/5206bd6d716022cea259c4878c6a1927ab3555f4caa48a74efaa83d8f418/comma_deps_bzip2-1.0.8.post95-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:7c015d720cc5462f87062b4482c54684de6a237b4edb60612c2f58721413682f", size = 38629, upload-time = "2026-06-24T23:58:40.496Z" }, - { url = "https://files.pythonhosted.org/packages/39/0e/78218f9a645ad9d27000153795d6819b3b52ca62d882aa46a413e40887be/comma_deps_bzip2-1.0.8.post95-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:3188a197aaf3efbfac975193887c59600b91a3b4eb7f62cc975377133cd48834", size = 36271, upload-time = "2026-06-24T23:58:41.2Z" }, -] - [[package]] name = "comma-deps-capnproto" version = "1.0.1.post93" @@ -720,7 +710,6 @@ testing = [ ] tools = [ { name = "comma-deps-bootstrap-icons" }, - { name = "comma-deps-bzip2" }, { name = "comma-deps-imgui" }, { name = "comma-deps-libusb" }, { name = "comma-deps-ncurses" }, @@ -736,7 +725,6 @@ requires-dist = [ { name = "codespell", marker = "extra == 'testing'" }, { name = "comma-deps-acados" }, { name = "comma-deps-bootstrap-icons", marker = "extra == 'tools'" }, - { name = "comma-deps-bzip2", marker = "extra == 'tools'" }, { name = "comma-deps-capnproto" }, { name = "comma-deps-ffmpeg" }, { name = "comma-deps-gcc-arm-none-eabi" }, From e04fd3c7247c721e76aae2d02884ffa6c1790788 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 23 Jul 2026 10:23:08 -0700 Subject: [PATCH 030/170] rm private reporter comment (#38431) --- .github/workflows/model_review.yaml | 47 ---------------------------- scripts/reporter.py | 48 ----------------------------- 2 files changed, 95 deletions(-) delete mode 100644 .github/workflows/model_review.yaml delete mode 100755 scripts/reporter.py diff --git a/.github/workflows/model_review.yaml b/.github/workflows/model_review.yaml deleted file mode 100644 index bdcc2e8977..0000000000 --- a/.github/workflows/model_review.yaml +++ /dev/null @@ -1,47 +0,0 @@ -name: "model review" - -on: - pull_request: - types: [opened, reopened, synchronize] - paths: - - 'openpilot/selfdrive/modeld/models/*.onnx' - workflow_dispatch: - -env: - GIT_CONFIG_COUNT: 1 - GIT_CONFIG_KEY_0: lfs.fetchexclude - GIT_CONFIG_VALUE_0: openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx - -jobs: - comment: - permissions: - contents: read - pull-requests: write - runs-on: ubuntu-latest - if: github.repository == 'commaai/openpilot' - steps: - - name: Checkout - uses: actions/checkout@v7 - with: - submodules: true - - name: Checkout master - uses: actions/checkout@v7 - with: - ref: master - path: base - - run: git lfs pull - - run: cd base && git lfs pull - - - name: scripts/reporter.py - id: report - run: | - echo "content<> $GITHUB_OUTPUT - echo "## Model Review" >> $GITHUB_OUTPUT - PYTHONPATH=${{ github.workspace }}:${{ github.workspace }}/tinygrad_repo MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Post model report comment - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 - with: - header: model-review - message: ${{ steps.report.outputs.content }} diff --git a/scripts/reporter.py b/scripts/reporter.py deleted file mode 100755 index 175a06fa67..0000000000 --- a/scripts/reporter.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -import os -import glob - -from tinygrad.nn.onnx import OnnxPBParser - -BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) - -MASTER_PATH = os.getenv("MASTER_PATH", BASEDIR) -MODEL_PATH = "/openpilot/selfdrive/modeld/models/" - - -class MetadataOnnxPBParser(OnnxPBParser): - def _parse_ModelProto(self) -> dict: - obj = {"metadata_props": []} - for fid, wire_type in self._parse_message(self.reader.len): - match fid: - case 14: - obj["metadata_props"].append(self._parse_StringStringEntryProto()) - case _: - self.reader.skip_field(wire_type) - return obj - - -def get_checkpoint(f): - model = MetadataOnnxPBParser(f).parse() - metadata = {prop["key"]: prop["value"] for prop in model["metadata_props"]} - # "" or "...//"; combined models list vision then policy - parts = metadata['model_checkpoint'].split('/') - return parts[-2] if len(parts) > 1 else parts[0] - - -if __name__ == "__main__": - print("| | master | PR branch |") - print("|-| ----- | --------- |") - - for f in glob.glob(BASEDIR + MODEL_PATH + "/*.onnx"): - fn = os.path.basename(f) - if fn == "big_driving_supercombo.onnx": - continue - master_path = MASTER_PATH + MODEL_PATH + fn - if os.path.exists(master_path): - master = get_checkpoint(master_path) - master_col = f"[{master}](https://reporter.comma.life/{master})" - else: - master_col = "N/A (new model)" - pr = get_checkpoint(BASEDIR + MODEL_PATH + fn) - print("|", fn, "|", master_col, "|", f"[{pr}](https://reporter.comma.life/{pr})", "|") From ddf782f08a119d585066fab0bbca6994d129d58f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 23 Jul 2026 13:36:12 -0700 Subject: [PATCH 031/170] big release --- RELEASES.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 770a7c40fe..87b1a3c54f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,15 @@ -Version 0.11.2 (2026-06-15) -======================== - +Version 0.11.2 (2026-07-24) +======================= +* New driving model + * Big model with 880M parameters +* New driver monitoring model + * Reduced false positives + * Improved face detection on comma four +* Support for big models running on an external GPU +* Live stream cameras from comma connect +* Remote control for comma body +* New alert sounds +* Volkswagen ID.4 2021-2025 support thanks to DaHansi! Version 0.11.1 (2026-05-18) ======================== From b392328c35dfbe231d8d7c0c43f359f46d0f8281 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Thu, 23 Jul 2026 15:00:25 -0700 Subject: [PATCH 032/170] big model loading permanent alert (#38436) show big model loading as permanent alert --- openpilot/selfdrive/selfdrived/events.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpilot/selfdrive/selfdrived/events.py b/openpilot/selfdrive/selfdrived/events.py index 76ffea1111..c5b2a87089 100755 --- a/openpilot/selfdrive/selfdrived/events.py +++ b/openpilot/selfdrive/selfdrived/events.py @@ -411,6 +411,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { EventName.bigModelLoading: { ET.NO_ENTRY: NoEntryAlert("Big Model Loading"), + ET.PERMANENT: NormalPermanentAlert("Big Model Loading"), }, EventName.bigModelReady: { From 554d8d21143b92a8b584c13863af7bba025e60cd Mon Sep 17 00:00:00 2001 From: stef <19478336+stefpi@users.noreply.github.com> Date: Thu, 23 Jul 2026 15:11:22 -0700 Subject: [PATCH 033/170] webrtcd: timeout after 5 minutes (#38437) * webrtc timeout after 5 mins * omit timeout for body --- openpilot/system/webrtc/webrtcd.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/openpilot/system/webrtc/webrtcd.py b/openpilot/system/webrtc/webrtcd.py index 7901f9847f..7979fbe8e3 100755 --- a/openpilot/system/webrtc/webrtcd.py +++ b/openpilot/system/webrtc/webrtcd.py @@ -23,6 +23,7 @@ from openpilot.system.webrtc.schema import generate_field from openpilot.common.params import Params from openpilot.cereal import messaging, log +SESSION_TIMEOUT_SECONDS = 300 # socket trick: route lookup for 8.8.8.8 (nothing is sent or actually connected to) # return the source interfaces IP which is the default interface of the device @@ -233,6 +234,8 @@ class StreamSession: builder.add_video_stream(body.init_camera, self.video_track) self.stream = builder.stream() + self.is_body = "testJoystick" in body.bridge_services_in + self.incoming_bridge: CerealIncomingMessageProxy | None = None self.incoming_bridge_services = body.bridge_services_in self.outgoing_bridge: CerealOutgoingMessageProxy | None = None @@ -297,13 +300,26 @@ class StreamSession: if hasattr(self.video_track, 'timing_sei_enabled'): self.video_track.timing_sei_enabled = bool(payload["data"]["enabled"]) case _: - if payload.get("type") not in self.incoming_bridge_services: + if msg_type not in self.incoming_bridge_services: return if self.incoming_bridge is not None: self.incoming_bridge.send(message) except Exception: self.logger.exception("Cereal incoming proxy failure") + async def run_normal_session(self): + try: + await asyncio.wait_for(self.stream.wait_for_disconnection(), timeout=SESSION_TIMEOUT_SECONDS) + except TimeoutError: + self.logger.warning("Stream session (%s) timed out after %d s", self.identifier, SESSION_TIMEOUT_SECONDS) + try: + self.stream.get_messaging_channel().send(json.dumps({"type": "disconnect", "data": "Session timed out"})) + except Exception: + pass + + async def run_body_session(self): + await self.stream.wait_for_disconnection() + async def run(self): try: self.params.put("LivestreamRequestKeyframe", True) @@ -320,7 +336,10 @@ class StreamSession: self.bitrate_controller.start() self.logger.info("Stream session (%s) connected", self.identifier) - await self.stream.wait_for_disconnection() + if self.is_body: + await self.run_body_session() + else: + await self.run_normal_session() self.logger.info("Stream session (%s) ended", self.identifier) except Exception: self.logger.exception("Stream session failure") @@ -547,7 +566,7 @@ def prewarm_stream_session_imports(debug_mode: bool = False) -> None: def webrtcd_thread(host: str, port: int, debug: bool): - logging.basicConfig(level=logging.CRITICAL, handlers=[logging.StreamHandler()]) + logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler()]) prewarm_start = time.monotonic() prewarm_stream_session_imports(debug) prewarm_end = time.monotonic() From ab7284fc8a09dfd94884efdcb75707330c1f16d5 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Thu, 23 Jul 2026 15:51:34 -0700 Subject: [PATCH 034/170] AGNOS 18.7 (#38416) * bump version * update AGNOS 18.7 * update to production --- launch_env.sh | 2 +- openpilot/common/hardware/tici/agnos.json | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 696d0fe78d..d348a6e2ac 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="18.6" + export AGNOS_VERSION="18.7" fi export STAGING_ROOT="/data/safe_staging" diff --git a/openpilot/common/hardware/tici/agnos.json b/openpilot/common/hardware/tici/agnos.json index a1202ef7a4..7456b6cbfa 100644 --- a/openpilot/common/hardware/tici/agnos.json +++ b/openpilot/common/hardware/tici/agnos.json @@ -56,29 +56,29 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-e230f456b4849fc7c54ead79cb5ea1c8be91b7ea3431403797e23614834a6190.img.xz", - "hash": "e230f456b4849fc7c54ead79cb5ea1c8be91b7ea3431403797e23614834a6190", - "hash_raw": "e230f456b4849fc7c54ead79cb5ea1c8be91b7ea3431403797e23614834a6190", + "url": "https://commadist.azureedge.net/agnosupdate/boot-4305a9e2061b695eeb955e76a178977d3aaccb220b2474248d3e986b386a788d.img.xz", + "hash": "4305a9e2061b695eeb955e76a178977d3aaccb220b2474248d3e986b386a788d", + "hash_raw": "4305a9e2061b695eeb955e76a178977d3aaccb220b2474248d3e986b386a788d", "size": 17487872, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "34b160f05881d6b4cd7d9a85ad84ac8289d60d9854ccfc00fa31a4534a2c63fc" + "ondevice_hash": "c8c772c7e40923a4f9c47a66a5e9659927e6084fcf2db6bb51a7a73d4d2e1561" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-ef79d27902c9a432609c028e2f17c0609bab554a8644f0ecfdcfe4f3c936ea95.img.xz", - "hash": "e34ea7ee73ce85ac357ca2a0ade022fc56c11e9c5af3989cd0c83896a4f9266b", - "hash_raw": "ef79d27902c9a432609c028e2f17c0609bab554a8644f0ecfdcfe4f3c936ea95", + "url": "https://commadist.azureedge.net/agnosupdate/system-27882a3e9ca2c5340bef5a51cc995d480bb854ee7b7d8f9de711f79811ac8310.img.xz", + "hash": "d32eeb4c7652163ca77e484ec22ebbd366a7e4954f57dde8c2b7b1e728fa67bd", + "hash_raw": "27882a3e9ca2c5340bef5a51cc995d480bb854ee7b7d8f9de711f79811ac8310", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "7a2bc0374a2719a48b5c27012b16ae9654a473eb3a37c96d5d669d2a1cf45184", + "ondevice_hash": "7fb7931e0edae32ec17e97657f8dfc8bb85aa5370b0180c7965ff2bbe5900692", "alt": { - "hash": "ef79d27902c9a432609c028e2f17c0609bab554a8644f0ecfdcfe4f3c936ea95", - "url": "https://commadist.azureedge.net/agnosupdate/system-ef79d27902c9a432609c028e2f17c0609bab554a8644f0ecfdcfe4f3c936ea95.img", + "hash": "27882a3e9ca2c5340bef5a51cc995d480bb854ee7b7d8f9de711f79811ac8310", + "url": "https://commadist.azureedge.net/agnosupdate/system-27882a3e9ca2c5340bef5a51cc995d480bb854ee7b7d8f9de711f79811ac8310.img", "size": 4718592000 } } -] \ No newline at end of file +] From 9c21b92c4dc17b1ac6fe4b9536252856da02d716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Thu, 23 Jul 2026 18:36:31 -0700 Subject: [PATCH 035/170] should_stop: use shared helper (#38438) --- .../selfdrive/controls/lib/drive_helpers.py | 6 ++---- .../controls/lib/longitudinal_planner.py | 17 +++++++++-------- openpilot/selfdrive/modeld/modeld.py | 14 +++++++------- .../selfdrive/test/process_replay/migration.py | 8 +++++--- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/openpilot/selfdrive/controls/lib/drive_helpers.py b/openpilot/selfdrive/controls/lib/drive_helpers.py index 497f8d1bdf..7ac2f50de0 100644 --- a/openpilot/selfdrive/controls/lib/drive_helpers.py +++ b/openpilot/selfdrive/controls/lib/drive_helpers.py @@ -16,7 +16,7 @@ MAX_LATERAL_ACCEL_NO_ROLL = 3.0 # m/s^2 def should_stop(v_ego: float, a_target: float) -> bool: - return bool(v_ego < 0.25 and a_target < 0.1) + return bool(v_ego < 0.3 and a_target < 0.1) def clamp(val, min_val, max_val): clamped_val = float(np.clip(val, min_val, max_val)) @@ -53,10 +53,8 @@ def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL): v_target = np.interp(action_t, t_idxs, speeds) a_target = 2 * (v_target - v_now) / (action_t) - a_now else: - v_now = 0.0 - v_target = 0.0 a_target = 0.0 - return a_target, should_stop(v_now, a_target) + return a_target def curv_from_psis(psi_target, psi_rate, vego, action_t): vego = np.clip(vego, MIN_SPEED, np.inf) diff --git a/openpilot/selfdrive/controls/lib/longitudinal_planner.py b/openpilot/selfdrive/controls/lib/longitudinal_planner.py index 0b1fee329d..8855d85af4 100755 --- a/openpilot/selfdrive/controls/lib/longitudinal_planner.py +++ b/openpilot/selfdrive/controls/lib/longitudinal_planner.py @@ -11,7 +11,7 @@ from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, LongitudinalPlanSource from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC -from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan +from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan, should_stop from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET from openpilot.common.swaglog import cloudlog @@ -51,8 +51,7 @@ def get_cruise_accel(e2e, v_cruise, v_ego, a_cruise_prev, angle_steers, CP, dt, j_cruise = np.interp(v_ego, A_CRUISE_MAX_BP, J_CRUISE_VALS) target_accel = float(np.clip(target_accel, a_cruise_prev - j_cruise * dt, a_cruise_prev + j_cruise * dt)) - cruise_should_stop = v_cruise == 0.0 - return target_accel, cruise_should_stop + return target_accel class LongitudinalPlanner: @@ -126,14 +125,16 @@ class LongitudinalPlanner: a_prev = self.a_desired action_t = self.CP.longitudinalActuatorDelay + DT_MDL - output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX, - action_t=action_t) + output_a_target_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX, + action_t=action_t) + output_should_stop_mpc = should_stop(v_ego, output_a_target_mpc) output_a_target_e2e = sm['modelV2'].action.desiredAcceleration output_should_stop_e2e = sm['modelV2'].action.shouldStop - self.a_cruise, cruise_should_stop = get_cruise_accel(sm['selfdriveState'].experimentalMode, v_cruise, v_ego, - self.a_cruise, steer_angle_without_offset, self.CP, self.dt, - accel_coast, self.allow_throttle) + self.a_cruise = get_cruise_accel(sm['selfdriveState'].experimentalMode, v_cruise, v_ego, + self.a_cruise, steer_angle_without_offset, self.CP, self.dt, + accel_coast, self.allow_throttle) + cruise_should_stop = should_stop(v_ego, self.a_cruise) candidates = [(output_a_target_mpc, self.mpc.source, output_should_stop_mpc), (self.a_cruise, LongitudinalPlanSource.cruise, cruise_should_stop)] diff --git a/openpilot/selfdrive/modeld/modeld.py b/openpilot/selfdrive/modeld/modeld.py index 3ba4dfa71a..d7d6d38c75 100755 --- a/openpilot/selfdrive/modeld/modeld.py +++ b/openpilot/selfdrive/modeld/modeld.py @@ -19,7 +19,7 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.common.transformations.model import get_warp_matrix from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper -from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan +from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, should_stop, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.compile_modeld import make_input_queues, WARP_INPUTS, POLICY_INPUTS from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_driving_model_data, fill_pose_msg, PublishState @@ -41,10 +41,10 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log. lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: if 'action' not in model_output: plan = model_output['plan'][0] - desired_accel, should_stop = get_accel_from_plan(plan[:,Plan.VELOCITY][:,0], - plan[:,Plan.ACCELERATION][:,0], - ModelConstants.T_IDXS, - action_t=long_action_t) + desired_accel = get_accel_from_plan(plan[:,Plan.VELOCITY][:,0], + plan[:,Plan.ACCELERATION][:,0], + ModelConstants.T_IDXS, + action_t=long_action_t) desired_curvature = get_curvature_from_plan(plan[:,Plan.T_FROM_CURRENT_EULER][:,2], plan[:,Plan.ORIENTATION_RATE][:,2], ModelConstants.T_IDXS, @@ -53,7 +53,7 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log. else: desired_accel = model_output['action'][0,1] desired_curvature = model_output['action'][0,0] / (max(1.0, v_ego))**2 - should_stop = (v_ego < 0.3 and desired_accel < 0.1) + stop = should_stop(v_ego, desired_accel) desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS) if v_ego > MIN_LAT_CONTROL_SPEED: desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS) @@ -62,7 +62,7 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log. return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature), desiredAcceleration=float(desired_accel), - shouldStop=bool(should_stop)) + shouldStop=bool(stop)) class FrameMeta: diff --git a/openpilot/selfdrive/test/process_replay/migration.py b/openpilot/selfdrive/test/process_replay/migration.py index 87064f5017..e38d1e1042 100644 --- a/openpilot/selfdrive/test/process_replay/migration.py +++ b/openpilot/selfdrive/test/process_replay/migration.py @@ -15,7 +15,7 @@ from opendbc.car.gm.values import GMSafetyFlags from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.modeld.fill_model_msg import fill_xyz_poly, fill_lane_line_meta from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_encode_index -from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan +from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan, should_stop from openpilot.system.manager.process_config import managed_processes from openpilot.tools.lib.logreader import LogIterable @@ -127,8 +127,10 @@ def migrate_longitudinalPlan(msgs): if msg.which() != 'longitudinalPlan': continue new_msg = msg.as_builder() - a_target, should_stop = get_accel_from_plan(msg.longitudinalPlan.speeds, msg.longitudinalPlan.accels, ModelConstants.T_IDXS[:CONTROL_N]) - new_msg.longitudinalPlan.aTarget, new_msg.longitudinalPlan.shouldStop = float(a_target), bool(should_stop) + a_target = get_accel_from_plan(msg.longitudinalPlan.speeds, msg.longitudinalPlan.accels, ModelConstants.T_IDXS[:CONTROL_N]) + v_now = msg.longitudinalPlan.speeds[0] if len(msg.longitudinalPlan.speeds) == CONTROL_N else 0.0 + stop = should_stop(v_now, a_target) + new_msg.longitudinalPlan.aTarget, new_msg.longitudinalPlan.shouldStop = float(a_target), bool(stop) ops.append((index, as_reader(new_msg))) return ops, [], [] From 4d4d6803e59a2c01eb9096dcd447d1afd0d832ea Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 23 Jul 2026 19:11:23 -0700 Subject: [PATCH 036/170] modeld: remove tinygrad probe on build (#38439) --- openpilot/selfdrive/modeld/SConscript | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/openpilot/selfdrive/modeld/SConscript b/openpilot/selfdrive/modeld/SConscript index 5f6281556f..d9af91af4a 100644 --- a/openpilot/selfdrive/modeld/SConscript +++ b/openpilot/selfdrive/modeld/SConscript @@ -26,18 +26,7 @@ tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + " def estimate_pickle_max_size(onnx_size): return 1.2 * onnx_size + 10 * 1024 * 1024 # 20% + 10MB is plenty -# get fastest TG config -# probe in subprocess so usbgpu locks gets released on process exit -def probe_devices(): - return set(subprocess.run( - [sys.executable, '-c', 'from tinygrad import Device\nprint("\\n".join(Device.get_available_devices()))'], - capture_output=True, text=True, check=True).stdout.strip().splitlines()) - -available = probe_devices() -if 'CUDA' in available: - tg_backend = 'CUDA' - tg_flags = f'DEV={tg_backend}' -elif 'QCOM' in available: +if arch == 'larch64': tg_backend = 'QCOM' tg_flags = f'DEV={tg_backend} IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1' else: @@ -54,7 +43,7 @@ tg_devices = { # which device to put jit inputs to at runtime }, } -USBGPU = usbgpu_present() # or release # TODO always build big model on release +USBGPU = usbgpu_present() if USBGPU: usbgpu_tg_flags = f'DEBUG=2 DEV=USB+AMD:LLVM WARP_DEV={tg_backend} FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0' # the USB+AMD GPU takes an exclusive flock; serialize all targets that touch it From 9c312e91e7440321d1c9d1f85e90d456931b14b6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 23 Jul 2026 19:49:31 -0700 Subject: [PATCH 037/170] release: include nested packages in PYTHONPATH (#38442) --- tools/release/build_release.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/release/build_release.sh b/tools/release/build_release.sh index 0b6ed16ba9..3ece925a05 100755 --- a/tools/release/build_release.sh +++ b/tools/release/build_release.sh @@ -46,8 +46,9 @@ echo "[-] committing version $VERSION T=$SECONDS" git add -f . git commit -a -m "openpilot v$VERSION release" -# Build -export PYTHONPATH="$BUILD_DIR" +# Build and test before launch_chffrplus.sh creates the on-device package +# symlinks. SConstruct uses the same package roots for build subprocesses. +export PYTHONPATH="$BUILD_DIR:$BUILD_DIR/msgq_repo:$BUILD_DIR/opendbc_repo:$BUILD_DIR/rednose_repo:$BUILD_DIR/teleoprtc_repo:$BUILD_DIR/tinygrad_repo" scons if [ -z "$PANDA_DEBUG_BUILD" ]; then From 1a753ad9ae43aed77617272aea6929dd7454ac8d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 23 Jul 2026 19:50:08 -0700 Subject: [PATCH 038/170] test_models moved to opendbc (#38441) * remove test_models * rm that * update test_models docs * restore car model test tool * leave dependencies unchanged --- opendbc_repo | 2 +- .../selfdrive/car/tests/big_cars_test.sh | 11 - openpilot/selfdrive/car/tests/test_models.py | 473 ------------------ .../selfdrive/car/tests/test_models_segs.txt | 3 - tools/car_porting/README.md | 6 +- tools/car_porting/test_car_model.py | 6 +- 6 files changed, 7 insertions(+), 494 deletions(-) delete mode 100755 openpilot/selfdrive/car/tests/big_cars_test.sh delete mode 100644 openpilot/selfdrive/car/tests/test_models.py delete mode 100644 openpilot/selfdrive/car/tests/test_models_segs.txt diff --git a/opendbc_repo b/opendbc_repo index d4c6f68c39..58e07d4aaa 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit d4c6f68c39dbab1c2663bd3c72a0087f93858213 +Subproject commit 58e07d4aaa82147a631d2f38e23013780442dd4f diff --git a/openpilot/selfdrive/car/tests/big_cars_test.sh b/openpilot/selfdrive/car/tests/big_cars_test.sh deleted file mode 100755 index 8c0ecee41e..0000000000 --- a/openpilot/selfdrive/car/tests/big_cars_test.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -SCRIPT_DIR=$(dirname "$0") -BASEDIR=$(realpath "$SCRIPT_DIR/../../../../") -cd $BASEDIR - -export MAX_EXAMPLES=300 -export INTERNAL_SEG_CNT=300 -export INTERNAL_SEG_LIST=openpilot/selfdrive/car/tests/test_models_segs.txt - -pytest -n logical --dist worksteal openpilot/selfdrive/car/tests/test_models.py openpilot/selfdrive/car/tests/test_car_interfaces.py diff --git a/openpilot/selfdrive/car/tests/test_models.py b/openpilot/selfdrive/car/tests/test_models.py deleted file mode 100644 index 29d4eabcc0..0000000000 --- a/openpilot/selfdrive/car/tests/test_models.py +++ /dev/null @@ -1,473 +0,0 @@ -import time -import os -import random -import unittest -from collections import defaultdict, Counter -from openpilot.common.fuzzy import fuzzy_test -from openpilot.common.parameterized import parameterized_class -from openpilot.common.test import OpenpilotTestCase -from opendbc.car import DT_CTRL, gen_empty_fingerprint, structs -from opendbc.car.can_definitions import CanData -from opendbc.car.car_helpers import FRAME_FINGERPRINT, interfaces -from opendbc.car.fingerprints import MIGRATION -from opendbc.car.honda.values import HondaFlags -from opendbc.car.structs import car -from opendbc.car.tests.routes import non_tested_cars, routes, CarTestRoute -from opendbc.car.values import Platform, PLATFORMS -from opendbc.safety.tests.libsafety import libsafety_py -from openpilot.common.basedir import BASEDIR -from openpilot.selfdrive.pandad import can_capnp_to_list -from openpilot.selfdrive.test.helpers import read_segment_list -from openpilot.common.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT -from openpilot.tools.lib.logreader import LogReader, LogsUnavailable, openpilotci_source, internal_source, comma_api_source -from openpilot.tools.lib.file_sources import Source -from openpilot.tools.lib.route import SegmentName - -SafetyModel = car.CarParams.SafetyModel -SteerControlType = structs.CarParams.SteerControlType - -# panda safety stores angle_meas in brand-specific CAN units (angle_deg_to_can in opendbc/safety/modes/*.h). -ANGLE_DEG_TO_CAN = { - "tesla": -10, - "toyota": 17.452007, - "nissan": 100, - "psa": 10, -} - -NUM_JOBS = int(os.environ.get("NUM_JOBS", "1")) -JOB_ID = int(os.environ.get("JOB_ID", "0")) -INTERNAL_SEG_LIST = os.environ.get("INTERNAL_SEG_LIST", "") -INTERNAL_SEG_CNT = int(os.environ.get("INTERNAL_SEG_CNT", "0")) -CI = os.environ.get("CI", None) is not None - - -def get_test_cases() -> list[tuple[str, CarTestRoute | None]]: - # build list of test cases - test_cases = [] - if not len(INTERNAL_SEG_LIST): - routes_by_car = defaultdict(set) - for r in routes: - routes_by_car[str(r.car_model)].add(r) - - for i, c in enumerate(sorted(PLATFORMS)): - if i % NUM_JOBS == JOB_ID: - test_cases.extend(sorted((c, r) for r in routes_by_car.get(c, (None,)))) - - else: - segment_list = read_segment_list(os.path.join(BASEDIR, INTERNAL_SEG_LIST)) - segment_list = random.sample(segment_list, INTERNAL_SEG_CNT or len(segment_list)) - for platform, segment in segment_list: - platform = MIGRATION.get(platform, platform) - segment_name = SegmentName(segment) - test_cases.append((platform, CarTestRoute(segment_name.route_name.canonical_name, platform, - segment=segment_name.segment_num))) - return test_cases - - -class TestCarModelBase(OpenpilotTestCase): - SLOW_TEST = True - SHARED_DOWNLOAD_CACHE = True - platform: Platform | None = None - test_route: CarTestRoute | None = None - - can_msgs: list[tuple[int, list[CanData]]] - fingerprint: dict[int, dict[int, int]] - elm_frame: int | None - car_safety_mode_frame: int | None - - @classmethod - def get_testing_data_from_logreader(cls, lr): - car_fw = [] - can_msgs = [] - cls.elm_frame = None - cls.car_safety_mode_frame = None - cls.fingerprint = gen_empty_fingerprint() - alpha_long = False - for msg in lr: - if msg.which() == "can": - can = can_capnp_to_list((msg.as_builder().to_bytes(),))[0] - can_msgs.append((can[0], [CanData(*can) for can in can[1]])) - if len(can_msgs) <= FRAME_FINGERPRINT: - for m in msg.can: - if m.src < 64: - cls.fingerprint[m.src][m.address] = len(m.dat) - - elif msg.which() == "carParams": - car_fw = msg.carParams.carFw - if msg.carParams.openpilotLongitudinalControl: - alpha_long = True - if cls.platform is None: - live_fingerprint = msg.carParams.carFingerprint - cls.platform = MIGRATION.get(live_fingerprint, live_fingerprint) - - # Log which can frame the panda safety mode left ELM327, for CAN validity checks - elif msg.which() == 'pandaStates': - for ps in msg.pandaStates: - if cls.elm_frame is None and ps.safetyModel != SafetyModel.elm327: - cls.elm_frame = len(can_msgs) - if cls.car_safety_mode_frame is None and ps.safetyModel not in \ - (SafetyModel.elm327, SafetyModel.noOutput): - cls.car_safety_mode_frame = len(can_msgs) - - elif msg.which() == 'pandaStateDEPRECATED': - if cls.elm_frame is None and msg.pandaStateDEPRECATED.safetyModel != SafetyModel.elm327: - cls.elm_frame = len(can_msgs) - if cls.car_safety_mode_frame is None and msg.pandaStateDEPRECATED.safetyModel not in \ - (SafetyModel.elm327, SafetyModel.noOutput): - cls.car_safety_mode_frame = len(can_msgs) - - assert len(can_msgs) > int(50 / DT_CTRL), "no can data found" - return car_fw, can_msgs, alpha_long - - @classmethod - def get_testing_data(cls): - test_segs = (2, 1, 0) - if cls.test_route.segment is not None: - test_segs = (cls.test_route.segment,) - - for seg in test_segs: - 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] - lr = LogReader(segment_range, sources=sources, sort_by_time=True) - return cls.get_testing_data_from_logreader(lr) - except (LogsUnavailable, AssertionError): - pass - - raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded and public?") - - - @classmethod - def setUpClass(cls): - if cls.__name__ == 'TestCarModel' or cls.__name__.endswith('Base'): - raise unittest.SkipTest - - if cls.test_route is None: - if cls.platform in non_tested_cars: - print(f"Skipping tests for {cls.platform}: missing route") - raise unittest.SkipTest - raise Exception(f"missing test route for {cls.platform}") - - car_fw, cls.can_msgs, alpha_long = cls.get_testing_data() - - # if relay is expected to be open in the route - cls.openpilot_enabled = cls.car_safety_mode_frame is not None - - cls.CarInterface = interfaces[cls.platform] - cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False) - assert cls.CP - assert cls.CP.carFingerprint == cls.platform - - os.environ["COMMA_CACHE"] = DEFAULT_DOWNLOAD_CACHE_ROOT - - @classmethod - def tearDownClass(cls): - del cls.can_msgs - - def setUp(self): - self.CI = self.CarInterface(self.CP.copy()) - assert self.CI - - # TODO: check safetyModel is in release panda build - self.safety = libsafety_py.libsafety - - cfg = self.CP.safetyConfigs[-1] - set_status = self.safety.set_safety_hooks(cfg.safetyModel.raw, cfg.safetyParam) - self.assertEqual(0, set_status, f"failed to set safetyModel {cfg}") - self.safety.init_tests() - - def test_car_params(self): - if self.CP.dashcamOnly: - self.skipTest("no need to check carParams for dashcamOnly") - - # make sure car params are within a valid range - self.assertGreater(self.CP.mass, 1) - - if self.CP.steerControlType not in (SteerControlType.angle, SteerControlType.curvature): - tuning = self.CP.lateralTuning.which() - if tuning == 'pid': - self.assertTrue(len(self.CP.lateralTuning.pid.kpV)) - elif tuning == 'torque': - self.assertTrue(self.CP.lateralTuning.torque.latAccelFactor > 0) - else: - raise Exception("unknown tuning") - - def test_car_interface(self): - # TODO: also check for checksum violations from can parser - can_invalid_cnt = 0 - CC = structs.CarControl().as_reader() - - for i, msg in enumerate(self.can_msgs): - CS = self.CI.update(msg) - self.CI.apply(CC, msg[0]) - - # wait max of 2s for low frequency msgs to be seen - if i > 250: - can_invalid_cnt += not CS.canValid - - self.assertEqual(can_invalid_cnt, 0) - - def test_radar_interface(self): - RI = self.CarInterface.RadarInterface(self.CP) - assert RI - - # Since OBD port is multiplexed to bus 1 (commonly radar bus) while fingerprinting, - # start parsing CAN messages after we've left ELM mode and can expect CAN traffic - error_cnt = 0 - for i, msg in enumerate(self.can_msgs[self.elm_frame:]): - rr: structs.RadarData | None = RI.update(msg) - if rr is not None and i > 50: - error_cnt += rr.errors.canError - self.assertEqual(error_cnt, 0) - - def test_panda_safety_rx_checks(self): - if self.CP.dashcamOnly: - self.skipTest("no need to check panda safety for dashcamOnly") - - start_ts = self.can_msgs[0][0] - - failed_addrs = Counter() - for can in self.can_msgs: - # update panda timer - t = (can[0] - start_ts) / 1e3 - self.safety.set_timer(int(t)) - - # run all msgs through the safety RX hook - for msg in can[1]: - if msg.src >= 64: - continue - - to_send = libsafety_py.make_CANPacket(msg.address, msg.src % 4, msg.dat) - if self.safety.safety_rx_hook(to_send) != 1: - failed_addrs[hex(msg.address)] += 1 - - # ensure all msgs defined in the addr checks are valid - self.safety.safety_tick_current_safety_config() - if t > 1e6: - self.assertTrue(self.safety.safety_config_valid()) - - # Don't check relay malfunction on disabled routes (relay closed), - # or before fingerprinting is done (elm327 and noOutput) - if self.car_safety_mode_frame is not None and t / 1e4 > self.car_safety_mode_frame: - self.assertFalse(self.safety.get_relay_malfunction()) - else: - self.safety.set_relay_malfunction(False) - - self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}") - - # ensure RX checks go invalid after small time with no traffic - self.safety.set_timer(int(t + (2*1e6))) - self.safety.safety_tick_current_safety_config() - self.assertFalse(self.safety.safety_config_valid()) - - def test_panda_safety_tx_cases(self, data=None): - """Asserts we can tx common messages""" - if self.CP.dashcamOnly: - self.skipTest("no need to check panda safety for dashcamOnly") - - if self.CP.notCar: - self.skipTest("Skipping test for notCar") - - def test_car_controller(car_control): - now_nanos = 0 - msgs_sent = 0 - CI = self.CarInterface(self.CP) - for _ in range(round(10.0 / DT_CTRL)): # make sure we hit the slowest messages - CI.update([]) - _, sendcan = CI.apply(car_control, now_nanos) - - now_nanos += DT_CTRL * 1e9 - msgs_sent += len(sendcan) - for addr, dat, bus in sendcan: - to_send = libsafety_py.make_CANPacket(addr, bus % 4, dat) - self.assertTrue(self.safety.safety_tx_hook(to_send), (addr, dat, bus)) - - # Make sure we attempted to send messages - self.assertGreater(msgs_sent, 50) - - # Make sure we can send all messages while inactive - CC = structs.CarControl() - test_car_controller(CC.as_reader()) - - # Test cancel + general messages (controls_allowed=False & cruise_engaged=True) - self.safety.set_cruise_engaged_prev(True) - CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(cancel=True)) - test_car_controller(CC.as_reader()) - - # Test resume + general messages (controls_allowed=True & cruise_engaged=True) - self.safety.set_controls_allowed(True) - CC = structs.CarControl(cruiseControl=structs.CarControl.CruiseControl(resume=True)) - test_car_controller(CC.as_reader()) - - # Capturing stdout/stderr here causes elevated memory usage. - @fuzzy_test(max_examples=300) - def test_panda_safety_carstate_fuzzy(self, fuzzy): - """ - For each example, pick a random CAN message on the bus and fuzz its data, - checking for panda state mismatches. - """ - - if self.CP.dashcamOnly: - self.skipTest("no need to check panda safety for dashcamOnly") - - valid_addrs = [(addr, bus, size) for bus, addrs in self.fingerprint.items() for addr, size in addrs.items()] - address, bus, size = fuzzy.choice(valid_addrs) - - msgs = fuzzy.list(lambda: fuzzy.binary(min_size=size, max_size=size), min_size=20) - - vehicle_speed_seen = self.CP.steerControlType == SteerControlType.angle and not self.CP.notCar - - for n, dat in enumerate(msgs): - # due to panda updating state selectively, only edges are expected to match - # TODO: warm up CarState with real CAN messages to check edge of both sources - # (eg. toyota's gasPressed is the inverse of a signal being set) - prev_panda_gas = self.safety.get_gas_pressed_prev() - prev_panda_brake = self.safety.get_brake_pressed_prev() - prev_panda_regen_braking = self.safety.get_regen_braking_prev() - prev_panda_steering_disengage = self.safety.get_steering_disengage_prev() - prev_panda_vehicle_moving = self.safety.get_vehicle_moving() - prev_panda_vehicle_speed_min = self.safety.get_vehicle_speed_min() - prev_panda_vehicle_speed_max = self.safety.get_vehicle_speed_max() - prev_panda_cruise_engaged = self.safety.get_cruise_engaged_prev() - prev_panda_acc_main_on = self.safety.get_acc_main_on() - - to_send = libsafety_py.make_CANPacket(address, bus, dat) - self.safety.safety_rx_hook(to_send) - - can = [(int(time.monotonic() * 1e9), [CanData(address=address, dat=dat, src=bus)])] - CS = self.CI.update(can) - if n < 5: # CANParser warmup time - continue - - if self.safety.get_gas_pressed_prev() != prev_panda_gas: - self.assertEqual(CS.gasPressed, self.safety.get_gas_pressed_prev()) - - if self.safety.get_brake_pressed_prev() != prev_panda_brake: - self.assertEqual(CS.brakePressed, self.safety.get_brake_pressed_prev()) - - if self.safety.get_regen_braking_prev() != prev_panda_regen_braking: - self.assertEqual(CS.regenBraking, self.safety.get_regen_braking_prev()) - - if self.safety.get_steering_disengage_prev() != prev_panda_steering_disengage: - self.assertEqual(CS.steeringDisengage, self.safety.get_steering_disengage_prev()) - - if self.safety.get_vehicle_moving() != prev_panda_vehicle_moving and not self.CP.notCar: - self.assertEqual(not CS.standstill, self.safety.get_vehicle_moving()) - - # check vehicle speed if angle control car or available - if self.safety.get_vehicle_speed_min() > 0 or self.safety.get_vehicle_speed_max() > 0: - vehicle_speed_seen = True - - if vehicle_speed_seen and (self.safety.get_vehicle_speed_min() != prev_panda_vehicle_speed_min or - self.safety.get_vehicle_speed_max() != prev_panda_vehicle_speed_max): - v_ego_raw = CS.vEgoRaw / self.CP.wheelSpeedFactor - self.assertFalse(v_ego_raw > (self.safety.get_vehicle_speed_max() + 1e-3) or - v_ego_raw < (self.safety.get_vehicle_speed_min() - 1e-3)) - - if not (self.CP.brand == "honda" and not (self.CP.flags & HondaFlags.BOSCH)): - if self.safety.get_cruise_engaged_prev() != prev_panda_cruise_engaged: - self.assertEqual(CS.cruiseState.enabled, self.safety.get_cruise_engaged_prev()) - - if self.CP.brand == "honda": - if self.safety.get_acc_main_on() != prev_panda_acc_main_on: - self.assertEqual(CS.cruiseState.available, self.safety.get_acc_main_on()) - - def test_panda_safety_carstate(self): - """ - Assert that panda safety matches openpilot's carState - """ - if self.CP.dashcamOnly: - self.skipTest("no need to check panda safety for dashcamOnly") - - # warm up pass, as initial states may be different - for can in self.can_msgs[:300]: - self.CI.update(can) - for msg in filter(lambda m: m.src < 64, can[1]): - to_send = libsafety_py.make_CANPacket(msg.address, msg.src % 4, msg.dat) - self.safety.safety_rx_hook(to_send) - - controls_allowed_prev = False - CS_prev = car.CarState.new_message() - checks = defaultdict(int) - vehicle_speed_seen = self.CP.steerControlType == SteerControlType.angle and not self.CP.notCar - for idx, can in enumerate(self.can_msgs): - CS = self.CI.update(can).as_reader() - for msg in filter(lambda m: m.src < 64, can[1]): - to_send = libsafety_py.make_CANPacket(msg.address, msg.src % 4, msg.dat) - ret = self.safety.safety_rx_hook(to_send) - self.assertEqual(1, ret, f"safety rx failed ({ret=}): {(msg.address, msg.src % 4)}") - - # Skip first frame so CS_prev is properly initialized - if idx == 0: - CS_prev = CS - # Button may be left pressed in warm up period - if not self.CP.pcmCruise: - self.safety.set_controls_allowed(0) - continue - - # TODO: check rest of panda's carstate (steering, ACC main on, etc.) - - checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev() - checks['standstill'] += (CS.standstill == self.safety.get_vehicle_moving()) and not self.CP.notCar - - # check vehicle speed if angle control car or available - if self.safety.get_vehicle_speed_min() > 0 or self.safety.get_vehicle_speed_max() > 0: - vehicle_speed_seen = True - - if vehicle_speed_seen: - v_ego_raw = CS.vEgoRaw / self.CP.wheelSpeedFactor - checks['vEgoRaw'] += (v_ego_raw > (self.safety.get_vehicle_speed_max() + 1e-3) or - v_ego_raw < (self.safety.get_vehicle_speed_min() - 1e-3)) - - # check steering angle for angle control cars (panda stores angle_meas in CAN units) - # ford and VW MEB excluded since they track curvature, not steering angle - # TODO: add curvature check, standardize CAN units to rm brand specific ANGLE_DEG_TO_CAN - if self.CP.steerControlType == SteerControlType.angle and not self.CP.notCar and self.CP.brand not in ("ford", "volkswagen"): - angle_can = (CS.steeringAngleDeg + CS.steeringAngleOffsetDeg) * ANGLE_DEG_TO_CAN[self.CP.brand] - checks['steeringAngleDeg'] += (angle_can > (self.safety.get_angle_meas_max() + 1) or - angle_can < (self.safety.get_angle_meas_min() - 1)) - - checks['brakePressed'] += CS.brakePressed != self.safety.get_brake_pressed_prev() - checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev() - checks['steeringDisengage'] += CS.steeringDisengage != self.safety.get_steering_disengage_prev() - - if self.CP.pcmCruise: - # On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state. - # On Honda Nidec, we always engage on the rising edge of the PCM cruise state, but - # openpilot brakes to zero even if the min ACC speed is non-zero (i.e. the PCM disengages). - if self.CP.brand == "honda" and not (self.CP.flags & HondaFlags.BOSCH): - # only the rising edges are expected to match - if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled: - checks['controlsAllowed'] += not self.safety.get_controls_allowed() - else: - checks['controlsAllowed'] += not CS.cruiseState.enabled and self.safety.get_controls_allowed() - - # TODO: fix notCar mismatch - if not self.CP.notCar: - checks['cruiseState'] += CS.cruiseState.enabled != self.safety.get_cruise_engaged_prev() - else: - # Check for user button enable on rising edge of controls allowed - button_enable = CS.buttonEnable and (not CS.brakePressed or CS.standstill) - mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev) - checks['controlsAllowed'] += mismatch - controls_allowed_prev = self.safety.get_controls_allowed() - if button_enable and not mismatch: - self.safety.set_controls_allowed(False) - - if self.CP.brand == "honda": - checks['mainOn'] += CS.cruiseState.available != self.safety.get_acc_main_on() - - CS_prev = CS - - failed_checks = {k: v for k, v in checks.items() if v > 0} - self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}") - - -@parameterized_class(('platform', 'test_route'), get_test_cases()) -class TestCarModel(TestCarModelBase): - pass - - -if __name__ == "__main__": - unittest.main() diff --git a/openpilot/selfdrive/car/tests/test_models_segs.txt b/openpilot/selfdrive/car/tests/test_models_segs.txt deleted file mode 100644 index c983fb08e7..0000000000 --- a/openpilot/selfdrive/car/tests/test_models_segs.txt +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0810a361ec5b5f5f9a2ee73b89ffb2df62ef40e8feff7e97ecb62f80fa53f6f5 -size 124950 diff --git a/tools/car_porting/README.md b/tools/car_porting/README.md index 07ca2d0eba..c4ea80e6a7 100644 --- a/tools/car_porting/README.md +++ b/tools/car_porting/README.md @@ -52,9 +52,9 @@ FAIL: test_panda_safety_carstate (__main__.CarModelTestCase.test_panda_safety_ca Assert that panda safety matches openpilot's carState ---------------------------------------------------------------------- Traceback (most recent call last): - File "/home/batman/xx/openpilot/openpilot/selfdrive/car/tests/test_models.py", line 380, in test_panda_safety_carstate - self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}") -AssertionError: 1 is not false : panda safety doesn't agree with openpilot: {'gasPressed': 116} + File "/home/batman/openpilot/opendbc_repo/opendbc/car/tests/test_models.py", line 440, in test_panda_safety_carstate + self.assertFalse(failed_checks, f"panda safety doesn't agree with CarState: {failed_checks}") +AssertionError: {'gasPressed': 116} is not false : panda safety doesn't agree with CarState: {'gasPressed': 116} ``` ## Jupyter notebooks diff --git a/tools/car_porting/test_car_model.py b/tools/car_porting/test_car_model.py index 61ec2e15ed..3fb7ec9ae6 100755 --- a/tools/car_porting/test_car_model.py +++ b/tools/car_porting/test_car_model.py @@ -3,7 +3,7 @@ import argparse import sys import unittest from opendbc.car.tests.routes import CarTestRoute -from openpilot.selfdrive.car.tests.test_models import TestCarModel +from opendbc.car.tests.test_models import TestCarModelBase from openpilot.tools.lib.route import SegmentRange @@ -12,14 +12,14 @@ def create_test_models_suite(routes: list[CarTestRoute]) -> unittest.TestSuite: for test_route in routes: # create new test case and discover tests test_case_args = {"platform": test_route.car_model, "test_route": test_route} - CarModelTestCase = type("CarModelTestCase", (TestCarModel,), test_case_args) + CarModelTestCase = type("CarModelTestCase", (TestCarModelBase,), test_case_args) test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(CarModelTestCase)) return test_suite if __name__ == "__main__": parser = argparse.ArgumentParser(description="Test any route against common issues with a new car port. " + - "Uses openpilot/selfdrive/car/tests/test_models.py") + "Uses opendbc_repo/opendbc/car/tests/test_models.py") parser.add_argument("route_or_segment_name", help="Specify route to run tests on") parser.add_argument("--car", help="Specify car model for test route") args = parser.parse_args() From 171f271d11cedc40ac14561af41e58858c289e0b Mon Sep 17 00:00:00 2001 From: stef <19478336+stefpi@users.noreply.github.com> Date: Thu, 23 Jul 2026 19:59:21 -0700 Subject: [PATCH 039/170] bump teleoprtc (#38443) bump --- teleoprtc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teleoprtc_repo b/teleoprtc_repo index c0f813f1c4..6118703a4c 160000 --- a/teleoprtc_repo +++ b/teleoprtc_repo @@ -1 +1 @@ -Subproject commit c0f813f1c4f7e2d29bc0ed6a4d2a7b3268511b0f +Subproject commit 6118703a4ceb473c957fcf63c3fcf68d45c9d2a7 From 71e29583cbd290a986178460b87db3bd1e03898d Mon Sep 17 00:00:00 2001 From: stef <19478336+stefpi@users.noreply.github.com> Date: Thu, 23 Jul 2026 20:39:22 -0700 Subject: [PATCH 040/170] Revert "bump teleoprtc" (#38444) Revert "bump teleoprtc (#38443)" This reverts commit 171f271d11cedc40ac14561af41e58858c289e0b. --- teleoprtc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teleoprtc_repo b/teleoprtc_repo index 6118703a4c..c0f813f1c4 160000 --- a/teleoprtc_repo +++ b/teleoprtc_repo @@ -1 +1 @@ -Subproject commit 6118703a4ceb473c957fcf63c3fcf68d45c9d2a7 +Subproject commit c0f813f1c4f7e2d29bc0ed6a4d2a7b3268511b0f From 0fdfaf737ff43306423b30de8e3fe0558921b29e Mon Sep 17 00:00:00 2001 From: stef <19478336+stefpi@users.noreply.github.com> Date: Thu, 23 Jul 2026 20:42:51 -0700 Subject: [PATCH 041/170] ui(body): flip eye direction (#38445) flip eye --- openpilot/selfdrive/ui/body/layouts/onroad.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpilot/selfdrive/ui/body/layouts/onroad.py b/openpilot/selfdrive/ui/body/layouts/onroad.py index d7e9f419cc..a48e525628 100644 --- a/openpilot/selfdrive/ui/body/layouts/onroad.py +++ b/openpilot/selfdrive/ui/body/layouts/onroad.py @@ -67,8 +67,8 @@ class BodyLayout(Widget): self._animator.set_animation(ASLEEP) steer = sm['testJoystick'].axes[1] if len(sm['testJoystick'].axes) > 1 else 0 - self._turning_left = steer <= -0.05 - self._turning_right = steer >= 0.05 + self._turning_left = steer >= 0.05 + self._turning_right = steer <= -0.05 # play animation on screen tap def _handle_mouse_release(self, mouse_pos): From ee6f374621b2ddda303d08bc03be1c40b386bc08 Mon Sep 17 00:00:00 2001 From: stef <19478336+stefpi@users.noreply.github.com> Date: Thu, 23 Jul 2026 20:45:49 -0700 Subject: [PATCH 042/170] bump teleoprtc (#38446) bump --- teleoprtc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teleoprtc_repo b/teleoprtc_repo index c0f813f1c4..3ac144dc9a 160000 --- a/teleoprtc_repo +++ b/teleoprtc_repo @@ -1 +1 @@ -Subproject commit c0f813f1c4f7e2d29bc0ed6a4d2a7b3268511b0f +Subproject commit 3ac144dc9a57ace70172d338690b121c42c5add2 From 9225dac4333215f048a86f29d950deb6d52b7ee4 Mon Sep 17 00:00:00 2001 From: Toby Penner Date: Fri, 24 Jul 2026 10:21:01 -0700 Subject: [PATCH 043/170] Revert "Rebel Legion model (#38164)" (#38449) This reverts commit 2895346746634d7eec0ee749f946c87039948a25. --- openpilot/selfdrive/modeld/modeld.py | 4 ++-- openpilot/selfdrive/modeld/models/driving_supercombo.onnx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpilot/selfdrive/modeld/modeld.py b/openpilot/selfdrive/modeld/modeld.py index d7d6d38c75..7f459abef8 100755 --- a/openpilot/selfdrive/modeld/modeld.py +++ b/openpilot/selfdrive/modeld/modeld.py @@ -31,8 +31,8 @@ from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link PROCESS_NAME = "openpilot.selfdrive.modeld.modeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') -LAT_SMOOTH_SECONDS = 0.1 -LONG_SMOOTH_SECONDS = 0.1 +LAT_SMOOTH_SECONDS = 0.0 +LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 BIG_MODEL_TIMEOUT = 60 diff --git a/openpilot/selfdrive/modeld/models/driving_supercombo.onnx b/openpilot/selfdrive/modeld/models/driving_supercombo.onnx index 944eebb017..f0672eab48 100644 --- a/openpilot/selfdrive/modeld/models/driving_supercombo.onnx +++ b/openpilot/selfdrive/modeld/models/driving_supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15e563da34889318e41321a2559682d417416602de9b6f5093c3be0e6766419d -size 96599486 +oid sha256:659727c4d4839adc4992a254409a54259a8756a743f2d567bf5fdc6579f8009b +size 60881999 From c37855d113494f58ca51e9995adf03c17ba17deb Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 24 Jul 2026 11:03:01 -0700 Subject: [PATCH 044/170] op: merge setup and install (#38450) --- tools/op.sh | 28 ++++++++++++---------------- tools/setup.sh | 1 - 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/tools/op.sh b/tools/op.sh index 6d70310668..f06b543bd2 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -19,18 +19,6 @@ RC_FILE="${HOME}/.$(basename ${SHELL})rc" if [ "$(uname)" == "Darwin" ] && [ $SHELL == "/bin/bash" ]; then RC_FILE="$HOME/.bash_profile" fi -function op_install() { - echo "Installing op system-wide..." - OP_SH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/op.sh" - CMD=$(cat </dev/null || printf '\n%s\n' "$CMD" >> "$RC_FILE" - echo -e " ↳ [${GREEN}✔${NC}] op installed successfully. Open a new shell to use it." -} function retry() { local attempts=$1 @@ -99,7 +87,6 @@ function op_check_openpilot_dir() { echo -e " ↳ [${GREEN}✔${NC}] openpilot found." return 0 fi - echo -e " ↳ [${RED}✗${NC}] openpilot directory not found! Make sure that you are" echo " inside the openpilot directory or specify one with the" echo " --dir option!" @@ -193,6 +180,17 @@ function op_before_cmd() { } function op_setup() { + echo "Installing op system-wide..." + OP_SH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )/op.sh" + CMD=$(cat </dev/null || printf '\n%s\n' "$CMD" >> "$RC_FILE" + echo -e " ↳ [${GREEN}✔${NC}] op installed successfully. Open a new shell to use it." + op_get_openpilot_dir cd $OPENPILOT_ROOT @@ -409,9 +407,8 @@ function op_default() { echo -e " ${BOLD}check${NC} Check the development environment (git, os) to start using openpilot" echo -e " ${BOLD}esim${NC} Manage eSIM profiles on your comma device" echo -e " ${BOLD}venv${NC} Activate the python virtual environment" - echo -e " ${BOLD}setup${NC} Install openpilot dependencies" + echo -e " ${BOLD}setup${NC} Install the 'op' tool and openpilot dependencies" echo -e " ${BOLD}build${NC} Run the openpilot build system in the current working directory" - echo -e " ${BOLD}install${NC} Install the 'op' tool system wide" echo -e " ${BOLD}switch${NC} Switch to a different git branch with a clean slate (nukes any changes)" echo -e " ${BOLD}start${NC} Starts (or restarts) openpilot" echo -e " ${BOLD}stop${NC} Stops openpilot" @@ -477,7 +474,6 @@ function _op() { 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 "$@" ;; start ) shift 1; op_start "$@" ;; stop ) shift 1; op_stop "$@" ;; diff --git a/tools/setup.sh b/tools/setup.sh index dafd466ef9..d4b68d15cb 100755 --- a/tools/setup.sh +++ b/tools/setup.sh @@ -121,7 +121,6 @@ function git_clone() { function install_with_op() { cd $OPENPILOT_ROOT - $OPENPILOT_ROOT/tools/op.sh install $OPENPILOT_ROOT/tools/op.sh post-commit if ! $OPENPILOT_ROOT/tools/op.sh setup; then From 27122bbd281395fc74c796868532a8f0359aaeaa Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 25 Jul 2026 10:29:37 -0700 Subject: [PATCH 045/170] lint: check indentation (#38454) * lint: check indentation * cleanup * happy * that was too much * happy --- .../selfdrive/assets/sounds/make_beeps.py | 10 ++-- openpilot/selfdrive/locationd/helpers.py | 46 +++++++++---------- .../selfdrive/ui/mici/onroad/cameraview.py | 12 ++--- openpilot/system/athena/athenad.py | 2 +- openpilot/system/athena/tests/test_athenad.py | 2 +- openpilot/tools/lib/framereader.py | 2 +- .../sim/bridge/metadrive/metadrive_process.py | 4 +- scripts/lint/check_indentation.py | 38 +++++++++++++++ scripts/lint/lint.sh | 2 + 9 files changed, 79 insertions(+), 39 deletions(-) create mode 100755 scripts/lint/check_indentation.py diff --git a/openpilot/selfdrive/assets/sounds/make_beeps.py b/openpilot/selfdrive/assets/sounds/make_beeps.py index 6161e80e74..e4c10c8d19 100644 --- a/openpilot/selfdrive/assets/sounds/make_beeps.py +++ b/openpilot/selfdrive/assets/sounds/make_beeps.py @@ -6,12 +6,12 @@ sr = 48000 max_int16 = 2**15 - 1 def harmonic_beep(freq, duration_seconds): - n_total = int(sr * duration_seconds) + n_total = int(sr * duration_seconds) - signal = np.sin(2 * np.pi * freq * np.arange(n_total) / sr) - x = np.arange(n_total) - exp_scale = np.exp(-x/5.5e3) - return max_int16 * signal * exp_scale + signal = np.sin(2 * np.pi * freq * np.arange(n_total) / sr) + x = np.arange(n_total) + exp_scale = np.exp(-x/5.5e3) + return max_int16 * signal * exp_scale engage_beep = harmonic_beep(1661.219, 0.5) wavfile.write("engage.wav", sr, engage_beep.astype(np.int16)) diff --git a/openpilot/selfdrive/locationd/helpers.py b/openpilot/selfdrive/locationd/helpers.py index 5e3fa16027..9ea9b6bf4f 100644 --- a/openpilot/selfdrive/locationd/helpers.py +++ b/openpilot/selfdrive/locationd/helpers.py @@ -9,29 +9,29 @@ from openpilot.common.transformations.orientation import rot_from_euler, euler_f @cache def fft_next_good_size(n: int) -> int: - """ - smallest composite of 2, 3, 5, 7, 11 that is >= n - inspired by pocketfft - """ - if n <= 6: - return n - best, f2 = 2 * n, 1 - while f2 < best: - f23 = f2 - while f23 < best: - f235 = f23 - while f235 < best: - f2357 = f235 - while f2357 < best: - f235711 = f2357 - while f235711 < best: - best = f235711 if f235711 >= n else best - f235711 *= 11 - f2357 *= 7 - f235 *= 5 - f23 *= 3 - f2 *= 2 - return best + """ + smallest composite of 2, 3, 5, 7, 11 that is >= n + inspired by pocketfft + """ + if n <= 6: + return n + best, f2 = 2 * n, 1 + while f2 < best: + f23 = f2 + while f23 < best: + f235 = f23 + while f235 < best: + f2357 = f235 + while f2357 < best: + f235711 = f2357 + while f235711 < best: + best = f235711 if f235711 >= n else best + f235711 *= 11 + f2357 *= 7 + f235 *= 5 + f23 *= 3 + f2 *= 2 + return best def parabolic_peak_interp(R, max_index): diff --git a/openpilot/selfdrive/ui/mici/onroad/cameraview.py b/openpilot/selfdrive/ui/mici/onroad/cameraview.py index 82e4865c76..4a31a9e04e 100644 --- a/openpilot/selfdrive/ui/mici/onroad/cameraview.py +++ b/openpilot/selfdrive/ui/mici/onroad/cameraview.py @@ -383,12 +383,12 @@ class CameraView(Widget): self._initialize_textures() def _initialize_textures(self): - self._clear_textures() - if not TICI: - self.texture_y = rl.load_texture_from_image(rl.Image(None, int(self.client.stride), - int(self.client.height), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE)) - self.texture_uv = rl.load_texture_from_image(rl.Image(None, int(self.client.stride // 2), - int(self.client.height // 2), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA)) + self._clear_textures() + if not TICI: + self.texture_y = rl.load_texture_from_image(rl.Image(None, int(self.client.stride), + int(self.client.height), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE)) + self.texture_uv = rl.load_texture_from_image(rl.Image(None, int(self.client.stride // 2), + int(self.client.height // 2), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA)) def _clear_textures(self): if self.texture_y and self.texture_y.id: diff --git a/openpilot/system/athena/athenad.py b/openpilot/system/athena/athenad.py index 91b12fc0a7..e7c318499a 100755 --- a/openpilot/system/athena/athenad.py +++ b/openpilot/system/athena/athenad.py @@ -580,7 +580,7 @@ def startStream(sdp: str, enabled: bool) -> dict: if CP.notCar: bridge_services_in.append("testJoystick") else: - raise Exception("failed to get CarParamsPersistent") + raise Exception("failed to get CarParamsPersistent") if params.get_bool("IsOffroad"): # manager owns camerad/stream_encoderd/webrtcd; flip the param and let it bring them up. diff --git a/openpilot/system/athena/tests/test_athenad.py b/openpilot/system/athena/tests/test_athenad.py index dbc1050db8..c4cd93558c 100644 --- a/openpilot/system/athena/tests/test_athenad.py +++ b/openpilot/system/athena/tests/test_athenad.py @@ -49,7 +49,7 @@ def with_upload_handler(func): return wrapper def mock_create_connection(mocker): - return mocker.patch('openpilot.system.athena.athenad.create_connection') + return mocker.patch('openpilot.system.athena.athenad.create_connection') def host(): with http_server_context(handler=HTTPRequestHandler, setup=seed_athena_server) as (host, port): diff --git a/openpilot/tools/lib/framereader.py b/openpilot/tools/lib/framereader.py index c923651563..62c75b95f7 100644 --- a/openpilot/tools/lib/framereader.py +++ b/openpilot/tools/lib/framereader.py @@ -28,7 +28,7 @@ class LRUCache: def __setitem__(self, key, value): self._cache[key] = value if len(self._cache) > self.capacity: - self._cache.popitem(last=False) + self._cache.popitem(last=False) def __contains__(self, key): return key in self._cache diff --git a/openpilot/tools/sim/bridge/metadrive/metadrive_process.py b/openpilot/tools/sim/bridge/metadrive/metadrive_process.py index 2486d87ff9..01d0473755 100644 --- a/openpilot/tools/sim/bridge/metadrive/metadrive_process.py +++ b/openpilot/tools/sim/bridge/metadrive/metadrive_process.py @@ -27,9 +27,9 @@ def apply_metadrive_patches(arrive_dest_done=True): # By default, metadrive won't try to use cuda images unless it's used as a sensor for vehicles, so patch that in def add_image_sensor_patched(self, name: str, cls, args): if self.global_config["image_on_cuda"]:# and name == self.global_config["vehicle_config"]["image_source"]: - sensor = cls(*args, self, cuda=True) + sensor = cls(*args, self, cuda=True) else: - sensor = cls(*args, self, cuda=False) + sensor = cls(*args, self, cuda=False) assert isinstance(sensor, ImageBuffer), "This API is for adding image sensor" self.sensors[name] = sensor diff --git a/scripts/lint/check_indentation.py b/scripts/lint/check_indentation.py new file mode 100755 index 0000000000..533dd2efa9 --- /dev/null +++ b/scripts/lint/check_indentation.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import argparse +import tokenize + + +# TODO: remove this once https://github.com/astral-sh/ruff/issues/8705 is closed +def check_indentation(filename: str, indent_width: int = 2) -> bool: + failed = False + indent_stack = [0] + + with tokenize.open(filename) as f: + tokens = tokenize.generate_tokens(f.readline) + for token in tokens: + if token.type == tokenize.INDENT: + indentation = token.string + width = len(indentation) + expected = indent_stack[-1] + indent_width + + if indentation != " " * expected: + found = "indentation containing tabs" if "\t" in indentation else f"{width} spaces" + print(f"{filename}:{token.start[0]}:1: expected {expected} spaces, found {found}") + failed = True + indent_stack.append(width) + elif token.type == tokenize.DEDENT: + indent_stack.pop() + + return failed + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Check Python block indentation.") + parser.add_argument("filenames", nargs="+") + args = parser.parse_args() + + failed = False + for filename in args.filenames: + failed |= check_indentation(filename) + raise SystemExit(failed) diff --git a/scripts/lint/lint.sh b/scripts/lint/lint.sh index c678379b42..bfd93f23a9 100755 --- a/scripts/lint/lint.sh +++ b/scripts/lint/lint.sh @@ -46,6 +46,7 @@ function run_tests() { PYTHON_FILES=$2 run "ruff" ruff check openpilot --quiet + run "check_indentation" $DIR/check_indentation.py $PYTHON_FILES run "check_added_large_files" $DIR/check_added_large_files.py --maxkb=120 $ALL_FILES run "check_shebang_scripts_are_executable" $DIR/check_shebang_scripts_are_executable.py $ALL_FILES run "check_shebang_format" $DIR/check_shebang_format.sh $ALL_FILES @@ -66,6 +67,7 @@ function help() { echo "" echo -e "${BOLD}${UNDERLINE}Tests:${NC}" echo -e " ${BOLD}ruff${NC}" + echo -e " ${BOLD}check_indentation${NC}" echo -e " ${BOLD}ty${NC}" echo -e " ${BOLD}codespell${NC}" echo -e " ${BOLD}check_added_large_files${NC}" From 9a39ccc66f92d3e17df1d5e51120434a065e2889 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 26 Jul 2026 08:52:49 -0700 Subject: [PATCH 046/170] tools/auth: use any open port (#38458) --- openpilot/tools/lib/auth.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/openpilot/tools/lib/auth.py b/openpilot/tools/lib/auth.py index 9139d8b42d..6a685e38d9 100755 --- a/openpilot/tools/lib/auth.py +++ b/openpilot/tools/lib/auth.py @@ -32,9 +32,6 @@ from urllib.parse import parse_qs, urlencode from openpilot.tools.lib.api import APIError, CommaApi, UnauthorizedError from openpilot.tools.lib.auth_config import set_token, get_token -PORT = 3000 - - class ClientRedirectServer(HTTPServer): query_params: dict[str, Any] = {} @@ -58,7 +55,7 @@ class ClientRedirectHandler(BaseHTTPRequestHandler): pass # this prevent http server from dumping messages to stdout -def auth_redirect_link(method): +def auth_redirect_link(method, port): provider_id = { 'google': 'g', 'apple': 'a', @@ -67,7 +64,7 @@ def auth_redirect_link(method): params = { 'redirect_uri': f"https://api.comma.ai/v2/auth/{provider_id}/redirect/", - 'state': f'service,localhost:{PORT}', + 'state': f'service,localhost:{port}', } if method == 'google': @@ -98,9 +95,9 @@ def auth_redirect_link(method): def login(method): - oauth_uri = auth_redirect_link(method) - - web_server = ClientRedirectServer(('localhost', PORT), ClientRedirectHandler) + # Let the OS select an available port to avoid colliding with other services. + web_server = ClientRedirectServer(('localhost', 0), ClientRedirectHandler) + oauth_uri = auth_redirect_link(method, web_server.server_port) print(f'To sign in, use your browser and navigate to {oauth_uri}') webbrowser.open(oauth_uri, new=2) From 5bcff3f87d2c0c819acf3686591e84365cff97b0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 26 Jul 2026 12:18:11 -0700 Subject: [PATCH 047/170] bump opendbc: rm jinja (#38463) --- docs/CARS.md | 4 +- opendbc_repo | 2 +- openpilot/selfdrive/car/CARS_template.md | 26 ++-------- openpilot/selfdrive/car/docs.py | 57 +++++++++++++++++++++- openpilot/selfdrive/car/tests/test_docs.py | 4 +- 5 files changed, 66 insertions(+), 27 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 31abdf433c..d0ce526e7f 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -232,8 +232,8 @@ A supported vehicle is one that just works when you install a comma device. All |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|0 mph|32 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|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|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|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
||| |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
||| |Subaru|Ascent 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| diff --git a/opendbc_repo b/opendbc_repo index 58e07d4aaa..d7c9aff771 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 58e07d4aaa82147a631d2f38e23013780442dd4f +Subproject commit d7c9aff771a847f066e49573eaf69a458e7f2e14 diff --git a/openpilot/selfdrive/car/CARS_template.md b/openpilot/selfdrive/car/CARS_template.md index 35804ff1fa..a67fa971a4 100644 --- a/openpilot/selfdrive/car/CARS_template.md +++ b/openpilot/selfdrive/car/CARS_template.md @@ -1,31 +1,16 @@ -{% set footnote_tag = '[{}](#footnotes)' %} -{% set star_icon = '[![star](assets/icon-star-{}.svg)](##)' %} -{% set video_icon = '' %} -{# Force hardware column wider by using a blank image with max width. #} -{% set width_tag = '%s
 ' %} -{% set hardware_col_name = 'Hardware Needed' %} -{% set wide_hardware_col_name = width_tag|format(hardware_col_name) -%} - # 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. -# {{all_car_docs | selectattr('support_type', 'eq', SupportType.UPSTREAM) | list | length}} Supported Cars - -|{{Column | map(attribute='value') | join('|') | replace(hardware_col_name, wide_hardware_col_name)}}| -|---|---|---|{% for _ in range((Column | length) - 3) %}{{':---:|'}}{% endfor +%} -{% for car_docs in all_car_docs | selectattr('support_type', 'eq', SupportType.UPSTREAM) %} -|{% for column in Column %}{{car_docs.get_column(column, star_icon, video_icon, footnote_tag)}}|{% endfor %} - -{% endfor %} +# $supported_count Supported Cars +$table_header +$table_separator +$table_rows ### Footnotes -{% for footnote in footnotes %} -{{loop.index}}{{footnote | replace('
', '')}}
-{% endfor %} - +$footnotes ## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). @@ -71,4 +56,3 @@ openpilot does not yet support these Toyota models due to a new message authenti * Lexus NX 2022+ * Toyota bZ4x 2023+ * Subaru Solterra 2023+ - diff --git a/openpilot/selfdrive/car/docs.py b/openpilot/selfdrive/car/docs.py index ea7a70688e..8d135d82ea 100755 --- a/openpilot/selfdrive/car/docs.py +++ b/openpilot/selfdrive/car/docs.py @@ -1,13 +1,68 @@ #!/usr/bin/env python3 import argparse import os +from string import Template from openpilot.common.basedir import BASEDIR -from opendbc.car.docs import get_all_car_docs, generate_cars_md +from opendbc.car.docs import get_all_car_docs, get_all_footnotes +from opendbc.car.docs_definitions import Column, SupportType CARS_MD_OUT = os.path.join(BASEDIR, "docs", "CARS.md") CARS_MD_TEMPLATE = os.path.join(BASEDIR, "openpilot/selfdrive", "car", "CARS_template.md") +FOOTNOTE_TAG = '[{}](#footnotes)' +STAR_ICON = '[![star](assets/icon-star-{}.svg)](##)' +VIDEO_ICON = '' +# Force hardware column wider by using a blank image with max width. +HARDWARE_COL_NAME = 'Hardware Needed' +WIDE_HARDWARE_COL_NAME = f'{HARDWARE_COL_NAME}
 ' + + +def _build_cars_table(upstream_cars) -> tuple[str, str, str]: + columns = list(Column) + header_cells = [ + WIDE_HARDWARE_COL_NAME if col.value == HARDWARE_COL_NAME else col.value + for col in columns + ] + table_header = "|" + "|".join(header_cells) + "|" + + # First three columns left-aligned (---), remaining centered (:---:) + sep_parts = ["---"] * min(3, len(columns)) + [":---:"] * max(0, len(columns) - 3) + table_separator = "|" + "|".join(sep_parts) + "|" + + rows = [] + for car_docs in upstream_cars: + cells = [car_docs.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in columns] + rows.append("|" + "|".join(cells) + "|") + table_rows = "\n".join(rows) + ("\n" if rows else "") + + return table_header, table_separator, table_rows + + +def generate_cars_md(all_car_docs, template_fn: str, **kwargs) -> str: + del kwargs # kept for call-site compatibility + + upstream_cars = [c for c in all_car_docs if c.support_type == SupportType.UPSTREAM] + table_header, table_separator, table_rows = _build_cars_table(upstream_cars) + + footnotes = [fn.value.text.replace('
', '') for fn in get_all_footnotes()] + footnotes_md = "\n".join( + f"{i}{text}
" + for i, text in enumerate(footnotes, start=1) + ) + ("\n" if footnotes else "") + + with open(template_fn) as f: + template = Template(f.read()) + + return template.substitute( + supported_count=len(upstream_cars), + table_header=table_header, + table_separator=table_separator, + table_rows=table_rows, + footnotes=footnotes_md, + ) + + if __name__ == "__main__": parser = argparse.ArgumentParser(description="Auto generates supported cars documentation", formatter_class=argparse.ArgumentDefaultsHelpFormatter) diff --git a/openpilot/selfdrive/car/tests/test_docs.py b/openpilot/selfdrive/car/tests/test_docs.py index 9f5abba336..99438b4720 100644 --- a/openpilot/selfdrive/car/tests/test_docs.py +++ b/openpilot/selfdrive/car/tests/test_docs.py @@ -1,6 +1,6 @@ -from opendbc.car.docs import generate_cars_md, get_all_car_docs from openpilot.common.test import OpenpilotTestCase -from openpilot.selfdrive.car.docs import CARS_MD_TEMPLATE +from openpilot.selfdrive.car.docs import CARS_MD_TEMPLATE, generate_cars_md +from opendbc.car.docs import get_all_car_docs class TestCarDocs(OpenpilotTestCase): From e1db0f59f1f11a379942ac3e18c147d1e12d01bd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 26 Jul 2026 13:56:32 -0700 Subject: [PATCH 048/170] docs: replace zensical with dumb html template (#38462) --- .github/workflows/docs.yaml | 5 +- .gitignore | 2 +- docs/DEVELOPMENT.md | 24 -- docs/README.md | 14 ++ docs/assets/comma-logo.png | 2 +- docs/assets/favicon.svg | 3 + docs/concepts/glossary.md | 3 - docs/ext/glossary.py | 216 ------------------ docs/ext/glossary.toml | 8 - docs/serve.py | 444 ++++++++++++++++++++++++++++++++++++ docs/stylesheets/extra.css | 42 ---- docs/template.html | 209 +++++++++++++++++ pyproject.toml | 4 - scripts/docs.py | 63 ----- uv.lock | 135 +---------- zensical.toml | 81 ------- 16 files changed, 676 insertions(+), 579 deletions(-) delete mode 100644 docs/DEVELOPMENT.md create mode 100644 docs/README.md create mode 100644 docs/assets/favicon.svg delete mode 100644 docs/concepts/glossary.md delete mode 100644 docs/ext/glossary.py delete mode 100644 docs/ext/glossary.toml create mode 100644 docs/serve.py delete mode 100644 docs/stylesheets/extra.css create mode 100644 docs/template.html delete mode 100644 scripts/docs.py delete mode 100644 zensical.toml diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 4be4fac916..f4a8e7cb89 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -35,8 +35,7 @@ jobs: - name: Build docs run: | git lfs pull - pip install zensical - python scripts/docs.py build + python docs/serve.py --build # Push to docs.comma.ai - uses: actions/checkout@v7 @@ -57,7 +56,7 @@ jobs: git rm -rf . # copy over docs - cp -r ../docs_site/ docs/ + cp -r ../docs/_site/ docs/ # GitHub pages config touch docs/.nojekyll diff --git a/.gitignore b/.gitignore index 5c99567581..48ce8f0414 100644 --- a/.gitignore +++ b/.gitignore @@ -54,7 +54,7 @@ compare_runtime*.html openpilot/selfdrive/modeld/models/tg_input_devices.json # build artifacts -docs_site/ +docs/_site/ openpilot/selfdrive/pandad/pandad openpilot/cereal/services.h openpilot/cereal/gen diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md deleted file mode 100644 index e803a3fb8a..0000000000 --- a/docs/DEVELOPMENT.md +++ /dev/null @@ -1,24 +0,0 @@ -# Docs development - -The `docs/` tree is the source for [docs.comma.ai](https://docs.comma.ai). -The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml). - -Those commands must be run in the root directory of openpilot, **not /docs** - -**1. Install the docs dependencies** -``` bash -uv pip install .[docs] -``` - -**2. Build the new site** -``` bash -docs build -``` - -**3. Run the new site locally** -``` bash -docs serve -``` - -References: -* https://zensical.org/docs/ diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..d6a0126b39 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,14 @@ +# Docs development + +The `docs/` tree is the source for [docs.comma.ai](https://docs.comma.ai). +The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml). + +**1. Build the site** +``` bash +python docs/serve.py --build +``` + +**2. Run the site locally** (rebuilds on change) +``` bash +python docs/serve.py +``` diff --git a/docs/assets/comma-logo.png b/docs/assets/comma-logo.png index 2838d92bfb..19b67d0739 120000 --- a/docs/assets/comma-logo.png +++ b/docs/assets/comma-logo.png @@ -1 +1 @@ -../../selfdrive/assets/icons_mici/settings/comma_icon.png \ No newline at end of file +../../openpilot/selfdrive/assets/icons_mici/settings/comma_icon.png \ No newline at end of file diff --git a/docs/assets/favicon.svg b/docs/assets/favicon.svg new file mode 100644 index 0000000000..304454837a --- /dev/null +++ b/docs/assets/favicon.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e019ed5af500e8820d05934d47e0380728e1b30e01a179f786dc4edb1eccbd7 +size 349 diff --git a/docs/concepts/glossary.md b/docs/concepts/glossary.md deleted file mode 100644 index 4f4dd54756..0000000000 --- a/docs/concepts/glossary.md +++ /dev/null @@ -1,3 +0,0 @@ -# openpilot glossary - -{{GLOSSARY_DEFINITIONS}} diff --git a/docs/ext/glossary.py b/docs/ext/glossary.py deleted file mode 100644 index 9bbf3c78d7..0000000000 --- a/docs/ext/glossary.py +++ /dev/null @@ -1,216 +0,0 @@ -import posixpath -import re -import tomllib -import xml.etree.ElementTree as ET -from pathlib import Path - -from markdown.extensions import Extension -from markdown.preprocessors import Preprocessor -from markdown.treeprocessors import Treeprocessor - -from zensical.extensions.links import LinksTreeprocessor - -GlossaryTerm = tuple[str, re.Pattern[str], str] - -GLOSSARY_FILE = Path(__file__).with_name("glossary.toml") -GLOSSARY_PAGE = "concepts/glossary.md" -GLOSSARY_PLACEHOLDER = "{{GLOSSARY_DEFINITIONS}}" - -SKIP_TAGS = { - "a", - "code", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "kbd", - "pre", - "script", - "style", -} - -def clean_tooltip(description: str) -> str: - text = re.sub(r"\[([^\]]+)]\([^)]+\)", r"\1", description) - text = re.sub(r"`([^`]+)`", r"\1", text) - text = re.sub(r"[*_~]", "", text) - return re.sub(r"\s+", " ", text).strip() - - -def load_glossary() -> tuple[list[GlossaryTerm], str]: - with GLOSSARY_FILE.open("rb") as f: - glossary_data = tomllib.load(f).get("glossary", {}) - - glossary: list[GlossaryTerm] = [] - rendered = [] - for key, value in glossary_data.items(): - label = str(key).strip().replace("_", " ") - description = str(value).strip() - if not description: - continue - - slug = label.replace(" ", "-").replace("_", "-").lower() - glossary.append((slug, re.compile(rf"(?**{label}**: {description}') - - return glossary, "\n".join(rendered) - - -class GlossaryPreprocessor(Preprocessor): - def __init__(self, md, glossary: str): - super().__init__(md) - self.glossary = glossary - - def run(self, lines: list[str]) -> list[str]: - markdown = "\n".join(lines) - if GLOSSARY_PLACEHOLDER not in markdown: - return lines - return markdown.replace(GLOSSARY_PLACEHOLDER, self.glossary).splitlines() - - -class GlossaryTreeprocessor(Treeprocessor): - def __init__(self, md, glossary: list[GlossaryTerm]): - super().__init__(md) - self.glossary = glossary - self.seen: set[str] = set() - - def run(self, root: ET.Element) -> None: - at = self.md.treeprocessors.get_index_for_name("zrelpath") - processor = self.md.treeprocessors[at] - if not isinstance(processor, LinksTreeprocessor): - raise TypeError("Links processor not registered") - if processor.path == GLOSSARY_PAGE: - return - - self.seen.clear() - glossary_href = f"{posixpath.relpath(GLOSSARY_PAGE, posixpath.dirname(processor.path) or '.')}#" - self._walk(root, glossary_href) - - def _walk(self, element: ET.Element, glossary_href: str) -> None: - if element.tag in SKIP_TAGS or element.attrib.get("data-glossary-skip") is not None: - return - - self._replace(element, glossary_href) - - idx = 0 - while idx < len(element): - child = element[idx] - self._walk(child, glossary_href) - idx = self._replace(element, glossary_href, idx) + 1 - - def _replace(self, parent: ET.Element, glossary_href: str, index: int | None = None) -> int: - child = None if index is None else parent[index] - text = parent.text if child is None else child.tail - pieces = self._pieces(text or "", glossary_href) - if not pieces: - return -1 if index is None else index - - if child is None: - parent.text = pieces[0] if isinstance(pieces[0], str) else "" - # Insert replacements for parent.text before the first existing child. - insert_at = -1 - else: - assert index is not None - child.tail = pieces[0] if isinstance(pieces[0], str) else "" - insert_at = index - - start = 1 if isinstance(pieces[0], str) else 0 - previous = child - - for piece in pieces[start:]: - if isinstance(piece, str): - previous.tail = (previous.tail or "") + piece - continue - - insert_at += 1 - parent.insert(insert_at, piece) - previous = piece - - return insert_at - - def _pieces(self, text: str, glossary_href: str) -> list[str | ET.Element]: - if not text.strip(): - return [] - - pieces: list[str | ET.Element] = [] - cursor = 0 - - while True: - best = None - for slug, pattern, tooltip in self.glossary: - if slug in self.seen: - continue - - found = pattern.search(text, cursor) - if found is None: - continue - - candidate = (slug, tooltip, found.start(), found.end()) - if best is None: - best = candidate - continue - - _, _, best_start, best_end = best - _, _, current_start, current_end = candidate - if current_start < best_start: - best = candidate - continue - - if current_start == best_start and current_end - current_start > best_end - best_start: - best = candidate - - if best is None: - break - - slug, tooltip, start, end = best - if start > cursor: - pieces.append(text[cursor:start]) - - link = ET.Element( - "a", - { - "class": "glossary-term", - "data-glossary-term": "", - "href": f"{glossary_href}{slug}", - }, - ) - ET.SubElement(link, "span", {"class": "glossary-term__label"}).text = text[start:end] - ET.SubElement( - link, - "span", - { - "class": "glossary-term__tooltip", - "data-search-exclude": "", - }, - ).text = tooltip - pieces.append(link) - self.seen.add(slug) - cursor = end - - if not pieces: - return [] - if cursor < len(text): - pieces.append(text[cursor:]) - return pieces - - -class GlossaryExtension(Extension): - def extendMarkdown(self, md) -> None: - md.registerExtension(self) - glossary, rendered = load_glossary() - - md.preprocessors.register( - GlossaryPreprocessor(md, rendered), - "docs-ext-glossary-preprocessor", - 27, - ) - md.treeprocessors.register( - GlossaryTreeprocessor(md, glossary), - "docs-ext-glossary-treeprocessor", - 0, - ) - - -def makeExtension(**kwargs) -> GlossaryExtension: - return GlossaryExtension(**kwargs) diff --git a/docs/ext/glossary.toml b/docs/ext/glossary.toml deleted file mode 100644 index 62408d9ddd..0000000000 --- a/docs/ext/glossary.toml +++ /dev/null @@ -1,8 +0,0 @@ -[glossary] -onroad = "openpilot's system state while ignition is on." -offroad = "openpilot's system state while ignition is off." -route = "A route is a recording of an onroad session." -segment = "Routes are split into one minute chunks called segments." -"comma connect" = "The web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai)." -panda = "The secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda)." -"comma four" = "The latest hardware by comma.ai for running openpilot. More info at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four)." diff --git a/docs/serve.py b/docs/serve.py new file mode 100644 index 0000000000..dcc78f7b0e --- /dev/null +++ b/docs/serve.py @@ -0,0 +1,444 @@ +import argparse +import functools +import html +import http.server +import json +import posixpath +import re +import shutil +import threading +import time +import urllib.parse +from pathlib import Path + +DOCS_DIR = Path(__file__).resolve().parent +SITE_DIR = DOCS_DIR / "_site" +TEMPLATE_FILE = DOCS_DIR / "template.html" +EXCLUDE_DIRS = {"_site"} + +REPO_URL = "https://github.com/commaai/openpilot/" + +# (title, target) pairs. target is a page path or an absolute URL. +# A None target marks a section header. +NAV: list[tuple[str, str | None]] = [ + ("What is openpilot?", "index.md"), + ("How-to", None), + ("Turn the speed blue", "how-to/turn-the-speed-blue.md"), + ("Connect to a comma 3X or four", "how-to/connect-to-comma.md"), + ("Add support for a car", "how-to/car-port.md"), + ("Concepts", None), + ("Logs", "concepts/logs.md"), + ("Safety", "concepts/safety.md"), + ("Glossary", "concepts/glossary.md"), + ("Contributing", None), + ("Feedback", "contributing/feedback.md"), + ("Roadmap", "contributing/roadmap.md"), + ("Contributing Guide →", "https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md"), + ("Links", None), + ("Blog →", "https://blog.comma.ai"), + ("Bounties →", "https://comma.ai/bounties"), + ("GitHub →", "https://github.com/commaai"), + ("Discord →", "https://discord.comma.ai"), + ("X →", "https://x.com/comma_ai"), +] + +GLOSSARY_DESCRIPTIONS = { + "onroad": "openpilot's system state while ignition is on.", + "offroad": "openpilot's system state while ignition is off.", + "route": "A route is a recording of an onroad session.", + "segment": "Routes are split into one minute chunks called segments.", + "comma connect": "The web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai).", + "panda": "The secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda).", + "comma four": "The latest hardware by comma.ai for running openpilot. More info at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four).", +} +GLOSSARY_PAGE = "concepts/glossary.md" +GLOSSARY_ROUTE = GLOSSARY_PAGE.removesuffix(".md") +GLOSSARY_SKIP = frozenset("a code h1 h2 h3 h4 h5 h6 kbd pre script style".split()) + +_ENTITY = re.compile(r"&(?:#x?[0-9a-fA-F]+|[a-zA-Z]+);") +_LIST = re.compile(r"^(\s*)([*+-]|\d+\.)\s+(.*)$") +_HEADING = re.compile(r"^(#{1,6})\s+(.*)$") +_HR = re.compile(r"^(-{3,}|\*{3,}|_{3,})$") +_ATTR_URL = re.compile(r"""(?P
\b(?:href|src)=(?P["']))(?P.*?)(?P=q)""")
+_VOID = frozenset("br img hr meta link input".split())
+
+def page_route(path: str) -> str:
+  path = path.removesuffix(".md")
+  return posixpath.dirname(path) or "." if posixpath.basename(path) == "index" else path
+
+def page_href(current: str, target: str) -> str:
+  route = posixpath.relpath(page_route(target), page_route(current))
+  return ("." if route == "." else route) + "/"
+
+def rewrite_relative_url(value: str, page: str) -> str | None:
+  url = urllib.parse.urlparse(value)
+  if value.startswith(("#", "/")) or url.scheme or url.netloc or not url.path:
+    return None
+  target = posixpath.normpath(posixpath.join(posixpath.dirname(page), url.path))
+  if target == ".." or target.startswith("../"):
+    return None
+  path = page_href(page, target) if target.endswith(".md") else posixpath.relpath(target, page_route(page))
+  return url._replace(path=path).geturl()
+
+def rewrite_html_urls(fragment: str, page: str) -> str:
+  def repl(m: re.Match[str]) -> str:
+    r = rewrite_relative_url(m.group("url"), page)
+    return m.group(0) if r is None else f'{m.group("pre")}{r}{m.group("q")}'
+  return _ATTR_URL.sub(repl, fragment)
+
+def esc(text: str, attr: bool = False) -> str:
+  held: list[str] = []
+  def hold(m: re.Match[str]) -> str:
+    held.append(m.group(0))
+    return f"\0{len(held) - 1}\0"
+  return re.sub(r"\0(\d+)\0", lambda m: held[int(m.group(1))], html.escape(_ENTITY.sub(hold, text), quote=attr))
+
+def clean_tooltip(description: str) -> str:
+  text = re.sub(r"\[([^\]]+)]\([^)]+\)", r"\1", description)
+  return re.sub(r"\s+", " ", re.sub(r"[*_~]", "", re.sub(r"`([^`]+)`", r"\1", text))).strip()
+
+def glossary_slug(label: str) -> str:
+  return label.replace(" ", "-").replace("_", "-").lower()
+
+GLOSSARY_TERMS = [
+  (glossary_slug(l), re.compile(rf"(?**{l}**: {d}' for l, d in GLOSSARY_DESCRIPTIONS.items()
+)
+
+def inject_glossary(body: str, page: str) -> str:
+  if page == GLOSSARY_PAGE:
+    return body
+  route = "." if page == "index.md" else page.removesuffix(".md")
+  base, seen, out, skip, depth = f"{posixpath.relpath(GLOSSARY_ROUTE, route)}/#", set(), [], None, 0
+  for part in re.split(r"(<[^>]+>)", body):
+    if not part:
+      continue
+    if part.startswith("<"):
+      out.append(part)
+      if part.startswith("") or tag in _VOID
+      if closing and skip == tag and depth:
+        depth -= 1
+        skip = None if not depth else skip
+      elif not closing and not void:
+        skip, depth = (tag, 1) if skip is None else (skip, depth + (skip == tag))
+      continue
+    if depth:
+      out.append(part); continue
+    cur, text = 0, part
+    while True:
+      best = None
+      for order, (slug, pat, tip) in enumerate(GLOSSARY_TERMS):
+        if slug in seen or (found := pat.search(text, cur)) is None:
+          continue
+        cand = (found.start(), found.start() - found.end(), order, slug, tip, found.end(), found.group(0))
+        if best is None or cand[:3] < best[:3]:
+          best = cand
+      if best is None:
+        out.append(text[cur:]); break
+      start, _, _, slug, tip, end, matched = best
+      out.append(text[cur:start])
+      out.append(
+        f''
+        f'{matched}'
+        f'{esc(tip)}'
+      )
+      seen.add(slug); cur = end
+  return "".join(out)
+
+def slugify(text: str) -> str:
+  text = html.unescape(re.sub(r"<[^>]+>", "", text)).lower()
+  return re.sub(r"[-\s]+", "-", re.sub(r"[^\w\s-]", "", text, flags=re.UNICODE)).strip("-")
+
+def _parse_link(text: str, start: int) -> tuple[str, str, int] | None:
+  if start >= len(text) or text[start] != "[":
+    return None
+  depth, i = 0, start
+  while i < len(text):
+    depth += (text[i] == "[") - (text[i] == "]")
+    if text[i] == "]" and depth == 0:
+      label = text[start + 1:i]
+      if i + 1 >= len(text) or text[i + 1] != "(":
+        return None
+      j, dp = i + 2, 1
+      while j < len(text) and dp:
+        dp += (text[j] == "(") - (text[j] == ")"); j += 1
+      return None if dp else (label, text[i + 2:j - 1], j)
+    i += 1
+  return None
+
+def render_inline(text: str, page: str) -> str:
+  out, i, n = [], 0, len(text)
+  while i < n:
+    if text[i] == "\n" and i >= 2 and text[i-2:i] == "  " and out and out[-1].endswith("  "):
+      out[-1] = out[-1][:-2]; out.append("
\n"); i += 1; continue + if text[i] == "`" and (end := text.find("`", i + 1)) != -1: + out.append(f"{esc(text[i+1:end])}"); i = end + 1; continue + if text[i] == "!" and i + 1 < n and text[i+1] == "[" and (p := _parse_link(text, i + 1)): + label, url, end = p + src = rewrite_relative_url(url, page) or url + out.append(f'{esc(label, True)}'); i = end; continue + if text[i] == "[" and (p := _parse_link(text, i)): + label, url, end = p + href = rewrite_relative_url(url, page) or url + out.append(f'{render_inline(label, page)}'); i = end; continue + if text[i] == "<": + if text.startswith("", i + 4); end = n if end < 0 else end + 3 + out.append(rewrite_html_urls(text[i:end], page)); i = end; continue + if m := re.match(r"<[^>]+>", text[i:]): + out.append(rewrite_html_urls(m.group(0), page)); i += len(m.group(0)); continue + if (text.startswith("**", i) or text.startswith("__", i)) and (end := text.find(text[i:i+2], i+2)) != -1: + out.append(f"{render_inline(text[i+2:end], page)}"); i = end + 2; continue + if text[i] in "*_" and i + 1 < n and text[i+1] not in " \t\n" and (end := text.find(text[i], i+1)) > i + 1: + out.append(f"{render_inline(text[i+1:end], page)}"); i = end + 1; continue + j = i + 1 + while j < n and text[j] not in "`[ list[str]: + return [c.strip() for c in line.strip().removeprefix("|").removesuffix("|").split("|")] + +def _is_sep(line: str) -> bool: + return "|" in line and all(re.fullmatch(r":?-{3,}:?", c.strip()) for c in _trow(line)) + +def _align(sep: str) -> str: + s = sep.strip() + if s.startswith(":") and s.endswith(":"): return ' style="text-align: center;"' + if s.endswith(":"): return ' style="text-align: right;"' + if s.startswith(":"): return ' style="text-align: left;"' + return "" + +def _list_info(line: str) -> tuple[int, str, str] | None: + m = _LIST.match(line) + return None if not m else (len(m.group(1)) // 4, "ol" if m.group(2)[-1] == "." else "ul", m.group(3)) + +def render_markdown(text: str, page: str) -> str: + lines, out, i, n = text.splitlines(), [], 0, 0 + n = len(lines) + while i < n: + line, s = lines[i], lines[i].strip() + if not s: + i += 1; continue + + if s.startswith("```"): + lang, body = s[3:].strip(), [] + i += 1 + while i < n and not lines[i].strip().startswith("```"): + body.append(lines[i]); i += 1 + if i < n: i += 1 + code, cls = html.escape("\n".join(body) + ("\n" if body else "")), (f' class="language-{html.escape(lang)}"' if lang else "") + out.append(f"
{code}
"); continue + + if m := _HEADING.match(s): + content, level = m.group(2).rstrip("#").strip(), len(m.group(1)) + sid = slugify(content) + out.append(f'{render_inline(content, page)}#') + i += 1; continue + + if _HR.fullmatch(s): + out.append("
"); i += 1; continue + + if "|" in line and i + 1 < n and _is_sep(lines[i + 1]): + headers, aligns = _trow(line), [_align(c) for c in _trow(lines[i + 1])] + i += 2; rows = [] + while i < n and "|" in lines[i] and lines[i].strip(): + rows.append(_trow(lines[i])); i += 1 + parts = ["", "", ""] + [ + f"{render_inline(h, page)}" for j, h in enumerate(headers) + ] + ["", "", ""] + for row in rows: + parts.append("") + for j in range(len(headers)): + parts.append(f"{render_inline(row[j] if j < len(row) else '', page)}") + parts.append("") + out.append("\n".join(parts + ["", "
"])); continue + + if _list_info(line): + items: list[tuple[int, str, list[str]]] = [] + while i < n: + if not lines[i].strip(): + if i + 1 < n and _list_info(lines[i + 1]): + i += 1; continue + break + info = _list_info(lines[i]) + if not info: break + level, kind, body = info + chunk = [body]; i += 1 + while i < n and lines[i].strip() and _list_info(lines[i]) is None: + t = lines[i].strip() + if t.startswith("```") or _HEADING.match(t) or _HR.fullmatch(t): break + chunk.append(lines[i]); i += 1 + items.append((level, kind, chunk)) + + def render_list(start: int, min_level: int) -> tuple[str, int]: + if start >= len(items) or items[start][0] < min_level: + return "", start + kind, chunks, idx = items[start][1], [f"<{items[start][1]}>"], start + while idx < len(items) and items[idx][0] >= min_level: + level, ikind, body_lines = items[idx] + if level > min_level: + nested, idx = render_list(idx, level) + chunks[-1] = (chunks[-1][:-5] + nested + "") if chunks[-1].endswith("") else chunks[-1] + nested + continue + if ikind != kind: + chunks += [f"", f"<{ikind}>"]; kind = ikind + idx += 1 + body = render_inline("\n".join(body_lines), page) + nested = "" + if idx < len(items) and items[idx][0] > min_level: + nested, idx = render_list(idx, min_level + 1) + chunks.append(f"
  • {body}{nested}\n
  • " if nested else f"
  • {body}
  • ") + chunks.append(f"") + return "\n".join(chunks), idx + + out.append(render_list(0, items[0][0])[0]); continue + + if s.startswith(">"): + q = [] + while i < n and lines[i].strip().startswith(">"): + q.append(re.sub(r"^>\s?", "", lines[i].strip())); i += 1 + out.append(f"
    \n

    {render_inline(chr(10).join(q), page)}

    \n
    "); continue + + if s.startswith("", i + 4); end = n if end < 0 else end + 3 - out.append(rewrite_html_urls(text[i:end], page)); i = end; continue + end = text.find("-->", i + 4) + end = n if end < 0 else end + 3 + out.append(rewrite_html_urls(text[i:end], page)) + i = end + continue if m := re.match(r"<[^>]+>", text[i:]): - out.append(rewrite_html_urls(m.group(0), page)); i += len(m.group(0)); continue - if (text.startswith("**", i) or text.startswith("__", i)) and (end := text.find(text[i:i+2], i+2)) != -1: - out.append(f"{render_inline(text[i+2:end], page)}"); i = end + 2; continue - if text[i] in "*_" and i + 1 < n and text[i+1] not in " \t\n" and (end := text.find(text[i], i+1)) > i + 1: - out.append(f"{render_inline(text[i+1:end], page)}"); i = end + 1; continue + out.append(rewrite_html_urls(m.group(0), page)) + i += len(m.group(0)) + continue + if (text.startswith("**", i) or text.startswith("__", i)) and (end := text.find(text[i : i + 2], i + 2)) != -1: + out.append(f"{render_inline(text[i + 2 : end], page)}") + i = end + 2 + continue + if text[i] in "*_" and i + 1 < n and text[i + 1] not in " \t\n" and (end := text.find(text[i], i + 1)) > i + 1: + out.append(f"{render_inline(text[i + 1 : end], page)}") + i = end + 1 + continue j = i + 1 while j < n and text[j] not in "`[ list[str]: return [c.strip() for c in line.strip().removeprefix("|").removesuffix("|").split("|")] + def _is_sep(line: str) -> bool: return "|" in line and all(re.fullmatch(r":?-{3,}:?", c.strip()) for c in _trow(line)) + def _align(sep: str) -> str: s = sep.strip() - if s.startswith(":") and s.endswith(":"): return ' style="text-align: center;"' - if s.endswith(":"): return ' style="text-align: right;"' - if s.startswith(":"): return ' style="text-align: left;"' + if s.startswith(":") and s.endswith(":"): + return ' style="text-align: center;"' + if s.endswith(":"): + return ' style="text-align: right;"' + if s.startswith(":"): + return ' style="text-align: left;"' return "" + def _list_info(line: str) -> tuple[int, str, str] | None: m = _LIST.match(line) return None if not m else (len(m.group(1)) // 4, "ol" if m.group(2)[-1] == "." else "ul", m.group(3)) -def render_markdown(text: str, page: str) -> str: + +def _render_blocks(text: str, page: str) -> str: lines, out, i, n = text.splitlines(), [], 0, 0 n = len(lines) while i < n: line, s = lines[i], lines[i].strip() if not s: - i += 1; continue + i += 1 + continue if s.startswith("```"): lang, body = s[3:].strip(), [] i += 1 while i < n and not lines[i].strip().startswith("```"): - body.append(lines[i]); i += 1 - if i < n: i += 1 - code, cls = html.escape("\n".join(body) + ("\n" if body else "")), (f' class="language-{html.escape(lang)}"' if lang else "") - out.append(f"
    {code}
    "); continue + body.append(lines[i]) + i += 1 + if i < n: + i += 1 + code = html.escape("\n".join(body) + ("\n" if body else "")) + cls = f' class="language-{html.escape(lang)}"' if lang else "" + out.append(f"
    {code}
    ") + continue if m := _HEADING.match(s): content, level = m.group(2).rstrip("#").strip(), len(m.group(1)) sid = slugify(content) out.append(f'{render_inline(content, page)}#') - i += 1; continue + i += 1 + continue if _HR.fullmatch(s): - out.append("
    "); i += 1; continue + out.append("
    ") + i += 1 + continue if "|" in line and i + 1 < n and _is_sep(lines[i + 1]): headers, aligns = _trow(line), [_align(c) for c in _trow(lines[i + 1])] - i += 2; rows = [] + i += 2 + rows = [] while i < n and "|" in lines[i] and lines[i].strip(): - rows.append(_trow(lines[i])); i += 1 - parts = ["", "", ""] + [ - f"{render_inline(h, page)}" for j, h in enumerate(headers) - ] + ["", "", ""] + rows.append(_trow(lines[i])) + i += 1 + parts = ( + ["
    ", "", ""] + + [f"{render_inline(h, page)}" for j, h in enumerate(headers)] + + ["", "", ""] + ) for row in rows: parts.append("") for j in range(len(headers)): parts.append(f"{render_inline(row[j] if j < len(row) else '', page)}") parts.append("") - out.append("\n".join(parts + ["", "
    "])); continue + out.append("\n".join(parts + ["", ""])) + continue if _list_info(line): items: list[tuple[int, str, list[str]]] = [] while i < n: if not lines[i].strip(): if i + 1 < n and _list_info(lines[i + 1]): - i += 1; continue + i += 1 + continue break info = _list_info(lines[i]) - if not info: break + if not info: + break level, kind, body = info - chunk = [body]; i += 1 + chunk = [body] + i += 1 while i < n and lines[i].strip() and _list_info(lines[i]) is None: t = lines[i].strip() - if t.startswith("```") or _HEADING.match(t) or _HR.fullmatch(t): break - chunk.append(lines[i]); i += 1 + if t.startswith(("```", ">")) or _HEADING.match(t) or _HR.fullmatch(t): + break + chunk.append(lines[i]) + i += 1 items.append((level, kind, chunk)) - def render_list(start: int, min_level: int) -> tuple[str, int]: + def render_list(items: list[tuple[int, str, list[str]]], start: int, min_level: int) -> tuple[str, int]: if start >= len(items) or items[start][0] < min_level: return "", start kind, chunks, idx = items[start][1], [f"<{items[start][1]}>"], start while idx < len(items) and items[idx][0] >= min_level: level, ikind, body_lines = items[idx] if level > min_level: - nested, idx = render_list(idx, level) + nested, idx = render_list(items, idx, level) chunks[-1] = (chunks[-1][:-5] + nested + "") if chunks[-1].endswith("") else chunks[-1] + nested continue if ikind != kind: - chunks += [f"", f"<{ikind}>"]; kind = ikind + chunks += [f"", f"<{ikind}>"] + kind = ikind idx += 1 body = render_inline("\n".join(body_lines), page) nested = "" if idx < len(items) and items[idx][0] > min_level: - nested, idx = render_list(idx, min_level + 1) + nested, idx = render_list(items, idx, min_level + 1) chunks.append(f"
  • {body}{nested}\n
  • " if nested else f"
  • {body}
  • ") chunks.append(f"") return "\n".join(chunks), idx - out.append(render_list(0, items[0][0])[0]); continue + out.append(render_list(items, 0, items[0][0])[0]) + continue if s.startswith(">"): q = [] while i < n and lines[i].strip().startswith(">"): - q.append(re.sub(r"^>\s?", "", lines[i].strip())); i += 1 - out.append(f"
    \n

    {render_inline(chr(10).join(q), page)}

    \n
    "); continue + q.append(re.sub(r"^>\s?", "", lines[i].strip())) + i += 1 + m = _ADMONITION.match(q[0].strip()) if q else None + if m: + kind = m.group(1).lower() + title = m.group(1).capitalize() + body = _render_blocks("\n".join(q[1:]), page) + out.append(f'
    \n

    {title}

    \n{body}\n
    ') + else: + out.append(f"
    \n

    {render_inline(chr(10).join(q), page)}

    \n
    ") + continue - if s.startswith(" - - - - - - - - - - - From 96ca1f8ed54354a6b7bdff4f3680e6cb5c48c70e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 7 Aug 2026 13:10:32 -0700 Subject: [PATCH 126/170] ui: load fonts on the fly (#38567) --- openpilot/selfdrive/assets/fonts/process.py | 136 -------------------- openpilot/selfdrive/ui/SConscript | 17 +-- openpilot/system/ui/lib/application.py | 33 +++-- 3 files changed, 23 insertions(+), 163 deletions(-) delete mode 100755 openpilot/selfdrive/assets/fonts/process.py diff --git a/openpilot/selfdrive/assets/fonts/process.py b/openpilot/selfdrive/assets/fonts/process.py deleted file mode 100755 index 62ae6ec684..0000000000 --- a/openpilot/selfdrive/assets/fonts/process.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 -from pathlib import Path -import json - -import pyray as rl - -FONT_DIR = Path(__file__).resolve().parent -SELFDRIVE_DIR = FONT_DIR.parents[1] -TRANSLATIONS_DIR = SELFDRIVE_DIR / "ui" / "translations" -LANGUAGES_FILE = TRANSLATIONS_DIR / "languages.json" - -GLYPH_PADDING = 6 -EXTRA_CHARS = "–‑✓×°§•X⚙✕◀▶✔⌫⇧␣○●↳çêüñ–‑✓×°§•€£¥" -UNIFONT_LANGUAGES = {"th", "zh-CHT", "zh-CHS", "ko", "ja"} - - -def _languages(): - if not LANGUAGES_FILE.exists(): - return {} - with LANGUAGES_FILE.open(encoding="utf-8") as f: - return json.load(f) - - -def _char_sets(): - base = set(map(chr, range(32, 127))) | set(EXTRA_CHARS) - unifont = set(base) - - for language, code in _languages().items(): - unifont.update(language) - po_path = TRANSLATIONS_DIR / f"app_{code}.po" - try: - chars = set(po_path.read_text(encoding="utf-8")) - except FileNotFoundError: - continue - (unifont if code in UNIFONT_LANGUAGES else base).update(chars) - - return tuple(sorted(ord(c) for c in base)), tuple(sorted(ord(c) for c in unifont)) - - -def _glyph_metrics(glyphs, rects, glyph_count: int): - entries = [] - min_offset_y, max_extent = None, 0 - for idx in range(glyph_count): - glyph = glyphs[idx] - rect = rects[idx] - width = int(round(rect.width)) - height = int(round(rect.height)) - offset_y = int(round(glyph.offsetY)) - min_offset_y = offset_y if min_offset_y is None else min(min_offset_y, offset_y) - max_extent = max(max_extent, offset_y + height) - entries.append({ - "id": glyph.value, - "x": int(round(rect.x)), - "y": int(round(rect.y)), - "width": width, - "height": height, - "xoffset": int(round(glyph.offsetX)), - "yoffset": offset_y, - "xadvance": int(round(glyph.advanceX)), - }) - - if min_offset_y is None: - raise RuntimeError("No glyphs were generated") - - line_height = int(round(max_extent - min_offset_y)) - base = int(round(max_extent)) - return entries, line_height, base - - -def _write_bmfont(path: Path, font_size: int, face: str, atlas_name: str, line_height: int, base: int, atlas_size, entries): - # TODO: why doesn't raylib calculate these metrics correctly? - if line_height != font_size: - print("using font size for line height", atlas_name) - line_height = font_size - lines = [ - f"info face=\"{face}\" size=-{font_size} bold=0 italic=0 charset=\"\" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0", - f"common lineHeight={line_height} base={base} scaleW={atlas_size[0]} scaleH={atlas_size[1]} pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4", - f"page id=0 file=\"{atlas_name}\"", - f"chars count={len(entries)}", - ] - for entry in entries: - lines.append( - ("char id={id:<4} x={x:<5} y={y:<5} width={width:<5} height={height:<5} " + - "xoffset={xoffset:<5} yoffset={yoffset:<5} xadvance={xadvance:<5} page=0 chnl=15").format(**entry) - ) - path.write_text("\n".join(lines) + "\n") - - -def _process_font(font_path: Path, codepoints: tuple[int, ...]): - print(f"Processing {font_path.name}...") - - font_size = { - "unifont.otf": 16, # unifont is only 16x8 or 16x16 pixels per glyph - }.get(font_path.name, 200) - - data = font_path.read_bytes() - file_buf = rl.ffi.new("unsigned char[]", data) - cp_buffer = rl.ffi.new("int[]", codepoints) - cp_ptr = rl.ffi.cast("int *", cp_buffer) - glyph_count = rl.ffi.new("int *", len(codepoints)) - glyphs = rl.load_font_data( - rl.ffi.cast("unsigned char *", file_buf), len(data), font_size, cp_ptr, len(codepoints), - rl.FontType.FONT_DEFAULT, glyph_count - ) - if glyphs == rl.ffi.NULL: - raise RuntimeError("raylib failed to load font data") - - rects_ptr = rl.ffi.new("Rectangle **") - image = rl.gen_image_font_atlas(glyphs, rects_ptr, glyph_count[0], font_size, GLYPH_PADDING, 0) - if image.width == 0 or image.height == 0: - raise RuntimeError("raylib returned an empty atlas") - - rects = rects_ptr[0] - atlas_name = f"{font_path.stem}.png" - atlas_path = FONT_DIR / atlas_name - entries, line_height, base = _glyph_metrics(glyphs, rects, glyph_count[0]) - - if not rl.export_image(image, atlas_path.as_posix()): - raise RuntimeError("Failed to export atlas image") - - _write_bmfont(FONT_DIR / f"{font_path.stem}.fnt", font_size, font_path.stem, atlas_name, line_height, base, (image.width, image.height), entries) - - -def main(): - base_cp, unifont_cp = _char_sets() - fonts = sorted(FONT_DIR.glob("*.ttf")) + sorted(FONT_DIR.glob("*.otf")) - for font in fonts: - if "emoji" in font.name.lower(): - continue - glyphs = unifont_cp if font.stem.lower().startswith("unifont") else base_cp - _process_font(font, glyphs) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/openpilot/selfdrive/ui/SConscript b/openpilot/selfdrive/ui/SConscript index d187b4ca3c..2b02759687 100644 --- a/openpilot/selfdrive/ui/SConscript +++ b/openpilot/selfdrive/ui/SConscript @@ -1,23 +1,8 @@ -from pathlib import Path import importlib.util +from pathlib import Path Import('env', 'arch', 'common') -# build the fonts -generator = File("#openpilot/selfdrive/assets/fonts/process.py") -source_files = Glob("#openpilot/selfdrive/assets/fonts/*.ttf") + Glob("#openpilot/selfdrive/assets/fonts/*.otf") -output_files = [ - (f"#{Path(f.path).with_suffix('.fnt')}", f"#{Path(f.path).with_suffix('.png')}") - for f in source_files - if "NotoColor" not in f.name -] -env.Command( - target=output_files, - source=[generator, source_files], - action=f"python3 {generator}", -) - - if GetOption('extras') and arch == "larch64": # build installers raylib_dir = Path(importlib.util.find_spec("raylib").submodule_search_locations[0]) / "install" diff --git a/openpilot/system/ui/lib/application.py b/openpilot/system/ui/lib/application.py index 3fa47ebd20..e294b02eaf 100644 --- a/openpilot/system/ui/lib/application.py +++ b/openpilot/system/ui/lib/application.py @@ -19,7 +19,7 @@ from typing import NamedTuple from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog from openpilot.common.hardware import HARDWARE, PC -from openpilot.system.ui.lib.multilang import multilang +from openpilot.system.ui.lib.multilang import TRANSLATIONS_DIR, UNIFONT_LANGUAGES, multilang from openpilot.common.realtime import Ratekeeper _DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20}.get(HARDWARE.get_device_type(), 60))) @@ -92,19 +92,20 @@ FONT_SCALE = 1.242 if BIG_UI else 1.16 ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets") FONT_DIR = ASSETS_DIR.joinpath("fonts") +EXTRA_FONT_CHARS = "–‑✓×°§•X⚙✕◀▶✔⌫⇧␣○●↳çêüñ–‑✓×°§•€£¥" class FontWeight(StrEnum): - NORMAL = "Inter-Regular.fnt" if BIG_UI else "Inter-Medium.fnt" - MEDIUM = "Inter-Medium.fnt" - BOLD = "Inter-Bold.fnt" - SEMI_BOLD = "Inter-SemiBold.fnt" - UNIFONT = "unifont.fnt" + NORMAL = "Inter-Regular.ttf" if BIG_UI else "Inter-Medium.ttf" + MEDIUM = "Inter-Medium.ttf" + BOLD = "Inter-Bold.ttf" + SEMI_BOLD = "Inter-SemiBold.ttf" + UNIFONT = "unifont.otf" # Small UI fonts - DISPLAY_REGULAR = "Inter-Regular.fnt" - ROMAN = "Inter-Regular.fnt" - DISPLAY = "Inter-Bold.fnt" + DISPLAY_REGULAR = "Inter-Regular.ttf" + ROMAN = "Inter-Regular.ttf" + DISPLAY = "Inter-Bold.ttf" def font_fallback(font: rl.Font) -> rl.Font: @@ -684,10 +685,20 @@ class GuiApplication: return self._height def _load_fonts(self): + base_chars = set(map(chr, range(32, 127))) | set(EXTRA_FONT_CHARS) + unifont_chars = set(base_chars) + for language, code in multilang.languages.items(): + unifont_chars.update(language) + chars = set(TRANSLATIONS_DIR.joinpath(f"app_{code}.po").read_text(encoding="utf-8")) + (unifont_chars if code in UNIFONT_LANGUAGES else base_chars).update(chars) + for font_weight_file in FontWeight: with as_file(FONT_DIR) as fspath: - fnt_path = fspath / font_weight_file - font = rl.load_font(fnt_path.as_posix()) + unifont = font_weight_file == FontWeight.UNIFONT + codepoints = sorted(map(ord, unifont_chars if unifont else base_chars)) + codepoint_buffer = rl.ffi.new("int[]", codepoints) + font = rl.load_font_ex((fspath / font_weight_file).as_posix(), 16 if unifont else 200, + rl.ffi.cast("int *", codepoint_buffer), len(codepoints)) if font_weight_file != FontWeight.UNIFONT: rl.gen_texture_mipmaps(font.texture) rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_TRILINEAR) From e6939dbedeb12e12cfe6b2863d09d043c8722b45 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 7 Aug 2026 13:19:02 -0700 Subject: [PATCH 127/170] inline modem serial support (#38564) --- openpilot/common/hardware/tici/modem.py | 56 +++++++++++++++++++++---- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/openpilot/common/hardware/tici/modem.py b/openpilot/common/hardware/tici/modem.py index 54251d4815..171ed8c0e4 100755 --- a/openpilot/common/hardware/tici/modem.py +++ b/openpilot/common/hardware/tici/modem.py @@ -3,17 +3,19 @@ import fcntl import json import logging import os +import select import signal +import struct import subprocess import tempfile +import termios import time +from contextlib import contextmanager from ipaddress import IPv4Address, AddressValueError from enum import Enum -from openpilot.common.serial import Serial - logging.basicConfig( level=logging.INFO, format="%(asctime)s.%(msecs)03d %(levelname)-7s modem: %(message)s", @@ -64,6 +66,39 @@ INITIAL_STATE: dict[str, object] = { } +@contextmanager +def _serial_port(port: str, baudrate: int): + fd = os.open(port, os.O_RDWR | os.O_NOCTTY) + try: + attrs = termios.tcgetattr(fd) + attrs[0] = 0 + attrs[1] = 0 + attrs[2] = termios.CLOCAL | termios.CREAD | termios.CS8 + attrs[3] = 0 + attrs[4] = attrs[5] = getattr(termios, f"B{baudrate}") + attrs[6][termios.VMIN] = 0 + attrs[6][termios.VTIME] = 0 + termios.tcsetattr(fd, termios.TCSANOW, attrs) + yield fd + finally: + os.close(fd) + + +def _read_line(fd: int, timeout: float) -> bytes: + data = bytearray() + deadline = time.monotonic() + timeout + while True: + readable, _, _ = select.select([fd], [], [], max(0.0, deadline - time.monotonic())) + if not readable: + return bytes(data) + byte = os.read(fd, 1) + if not byte: + return bytes(data) + data.extend(byte) + if byte == b"\n": + return bytes(data) + + class State(Enum): INITIALIZING = "INITIALIZING" SEARCHING = "SEARCHING" @@ -97,10 +132,11 @@ class PPPSession: def reset_data_port(): """Drop DTR on PPP_PORT so the modem terminates any stuck PPP session.""" try: - with Serial(PPP_PORT, baudrate=460800, timeout=1) as s: - s.dtr = False + with _serial_port(PPP_PORT, 460800) as fd: + dtr = struct.pack("I", termios.TIOCM_DTR) + fcntl.ioctl(fd, termios.TIOCMBIC, dtr) time.sleep(0.2) - s.dtr = True + fcntl.ioctl(fd, termios.TIOCMBIS, dtr) except Exception as e: logging.warning(f"data port reset failed: {e}") @@ -221,12 +257,14 @@ class Modem: os.close(fd) return [] try: - with Serial(AT_PORT, baudrate=9600, timeout=5) as ser: - ser.reset_input_buffer() - ser.write((cmd + "\r").encode()) + with _serial_port(AT_PORT, 9600) as serial_fd: + termios.tcflush(serial_fd, termios.TCIFLUSH) + command = (cmd + "\r").encode() + while command: + command = command[os.write(serial_fd, command):] lines = [] while True: - raw = ser.readline() + raw = _read_line(serial_fd, 5) if not raw: raise TimeoutError("AT timeout") line = raw.decode(errors="ignore").strip() From b77da00697d00728134f776a068298d674f246c7 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 7 Aug 2026 13:20:53 -0700 Subject: [PATCH 128/170] rm unused NotoColorEmoji.ttf --- openpilot/selfdrive/assets/fonts/NotoColorEmoji.ttf | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 openpilot/selfdrive/assets/fonts/NotoColorEmoji.ttf diff --git a/openpilot/selfdrive/assets/fonts/NotoColorEmoji.ttf b/openpilot/selfdrive/assets/fonts/NotoColorEmoji.ttf deleted file mode 100644 index 778e821ce3..0000000000 --- a/openpilot/selfdrive/assets/fonts/NotoColorEmoji.ttf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93cdc4ee9aa40e2afceecc63da0ca05ec7aab4bec991ece51a6b52389f48a477 -size 10788068 From 66b3590b637db36c059c176fd08f0c15870a6006 Mon Sep 17 00:00:00 2001 From: eFini Date: Sat, 8 Aug 2026 04:30:58 +0800 Subject: [PATCH 129/170] Use Noto fonts for Asian languages (#36514) ui: use Noto fonts for Asian languages Co-authored-by: Adeeb Shihadeh --- .../assets/fonts/NotoSansCJKjp-Regular.otf | 3 ++ .../assets/fonts/NotoSansCJKkr-Regular.otf | 3 ++ .../assets/fonts/NotoSansCJKsc-Regular.otf | 3 ++ .../assets/fonts/NotoSansCJKtc-Regular.otf | 3 ++ .../assets/fonts/NotoSansThai-Regular.ttf | 3 ++ openpilot/system/ui/lib/application.py | 40 ++++++++++++++++--- openpilot/system/ui/lib/multilang.py | 7 ++-- 7 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 openpilot/selfdrive/assets/fonts/NotoSansCJKjp-Regular.otf create mode 100644 openpilot/selfdrive/assets/fonts/NotoSansCJKkr-Regular.otf create mode 100644 openpilot/selfdrive/assets/fonts/NotoSansCJKsc-Regular.otf create mode 100644 openpilot/selfdrive/assets/fonts/NotoSansCJKtc-Regular.otf create mode 100644 openpilot/selfdrive/assets/fonts/NotoSansThai-Regular.ttf diff --git a/openpilot/selfdrive/assets/fonts/NotoSansCJKjp-Regular.otf b/openpilot/selfdrive/assets/fonts/NotoSansCJKjp-Regular.otf new file mode 100644 index 0000000000..045f73ba40 --- /dev/null +++ b/openpilot/selfdrive/assets/fonts/NotoSansCJKjp-Regular.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80135e7ab71496c3b744047ef9100f5d24edaa275d248a3e0488bc769e453baa +size 240500 diff --git a/openpilot/selfdrive/assets/fonts/NotoSansCJKkr-Regular.otf b/openpilot/selfdrive/assets/fonts/NotoSansCJKkr-Regular.otf new file mode 100644 index 0000000000..f06e31bae8 --- /dev/null +++ b/openpilot/selfdrive/assets/fonts/NotoSansCJKkr-Regular.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e86fb33e127558b845aec3016d46c36118811a1e30fd9dacc374840ad3001221 +size 109356 diff --git a/openpilot/selfdrive/assets/fonts/NotoSansCJKsc-Regular.otf b/openpilot/selfdrive/assets/fonts/NotoSansCJKsc-Regular.otf new file mode 100644 index 0000000000..ccd161ef62 --- /dev/null +++ b/openpilot/selfdrive/assets/fonts/NotoSansCJKsc-Regular.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71933d4233b691fb499524cbf9b3d74ccdf6465c81791687e661667aefa8e660 +size 196068 diff --git a/openpilot/selfdrive/assets/fonts/NotoSansCJKtc-Regular.otf b/openpilot/selfdrive/assets/fonts/NotoSansCJKtc-Regular.otf new file mode 100644 index 0000000000..fec43d0c6f --- /dev/null +++ b/openpilot/selfdrive/assets/fonts/NotoSansCJKtc-Regular.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:292f8ad096fd0a47e4016bd3607cb4edcd3b1f97634f67a115c0b0973ba5002f +size 245884 diff --git a/openpilot/selfdrive/assets/fonts/NotoSansThai-Regular.ttf b/openpilot/selfdrive/assets/fonts/NotoSansThai-Regular.ttf new file mode 100644 index 0000000000..c0a05b3965 --- /dev/null +++ b/openpilot/selfdrive/assets/fonts/NotoSansThai-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8656eb9e4e182333e58c59cd07ae0dd82c913d31c29b0d2726887ffbbef137a2 +size 26536 diff --git a/openpilot/system/ui/lib/application.py b/openpilot/system/ui/lib/application.py index e294b02eaf..5e980937d7 100644 --- a/openpilot/system/ui/lib/application.py +++ b/openpilot/system/ui/lib/application.py @@ -19,7 +19,7 @@ from typing import NamedTuple from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog from openpilot.common.hardware import HARDWARE, PC -from openpilot.system.ui.lib.multilang import TRANSLATIONS_DIR, UNIFONT_LANGUAGES, multilang +from openpilot.system.ui.lib.multilang import FONT_FALLBACK_LANGUAGES, TRANSLATIONS_DIR, multilang from openpilot.common.realtime import Ratekeeper _DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20}.get(HARDWARE.get_device_type(), 60))) @@ -93,6 +93,13 @@ FONT_SCALE = 1.242 if BIG_UI else 1.16 ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets") FONT_DIR = ASSETS_DIR.joinpath("fonts") EXTRA_FONT_CHARS = "–‑✓×°§•X⚙✕◀▶✔⌫⇧␣○●↳çêüñ–‑✓×°§•€£¥" +NOTO_FONTS = { + "ja": "NotoSansCJKjp-Regular.otf", + "ko": "NotoSansCJKkr-Regular.otf", + "th": "NotoSansThai-Regular.ttf", + "zh-CHS": "NotoSansCJKsc-Regular.otf", + "zh-CHT": "NotoSansCJKtc-Regular.otf", +} class FontWeight(StrEnum): @@ -109,9 +116,9 @@ class FontWeight(StrEnum): def font_fallback(font: rl.Font) -> rl.Font: - """Fall back to unifont for languages that require it.""" - if multilang.requires_unifont(): - return gui_app.font(FontWeight.UNIFONT) + """Use a Noto fallback for languages not covered by Inter.""" + if multilang.requires_font_fallback(): + return gui_app.fallback_font() return font @@ -199,6 +206,7 @@ class GuiApplication: self._set_log_callback() self._fonts: dict[FontWeight, rl.Font] = {} + self._fallback_fonts: dict[str, rl.Font] = {} self._width = width if width is not None else GuiApplication._default_width() self._height = height if height is not None else GuiApplication._default_height() @@ -554,6 +562,9 @@ class GuiApplication: for font in self._fonts.values(): rl.unload_font(font) self._fonts = {} + for font in self._fallback_fonts.values(): + rl.unload_font(font) + self._fallback_fonts = {} if self._render_texture is not None: rl.unload_render_texture(self._render_texture) @@ -676,6 +687,21 @@ class GuiApplication: def font(self, font_weight: FontWeight = FontWeight.NORMAL) -> rl.Font: return self._fonts[font_weight] + def fallback_font(self) -> rl.Font: + language = multilang.language + if language not in self._fallback_fonts: + chars = set(map(chr, range(32, 127))) | set(EXTRA_FONT_CHARS) + chars.update(TRANSLATIONS_DIR.joinpath(f"app_{language}.po").read_text(encoding="utf-8")) + codepoints = sorted(map(ord, chars)) + codepoint_buffer = rl.ffi.new("int[]", codepoints) + with as_file(FONT_DIR) as fspath: + font = rl.load_font_ex((fspath / NOTO_FONTS[language]).as_posix(), 48, + rl.ffi.cast("int *", codepoint_buffer), len(codepoints)) + rl.gen_texture_mipmaps(font.texture) + rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_TRILINEAR) + self._fallback_fonts[language] = font + return self._fallback_fonts[language] + @property def width(self): return self._width @@ -689,8 +715,8 @@ class GuiApplication: unifont_chars = set(base_chars) for language, code in multilang.languages.items(): unifont_chars.update(language) - chars = set(TRANSLATIONS_DIR.joinpath(f"app_{code}.po").read_text(encoding="utf-8")) - (unifont_chars if code in UNIFONT_LANGUAGES else base_chars).update(chars) + if code not in FONT_FALLBACK_LANGUAGES: + base_chars.update(TRANSLATIONS_DIR.joinpath(f"app_{code}.po").read_text(encoding="utf-8")) for font_weight_file in FontWeight: with as_file(FONT_DIR) as fspath: @@ -703,6 +729,8 @@ class GuiApplication: rl.gen_texture_mipmaps(font.texture) rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_TRILINEAR) self._fonts[font_weight_file] = font + if multilang.requires_font_fallback(): + self.fallback_font() rl.gui_set_font(self._fonts[FontWeight.NORMAL]) def _set_styles(self): diff --git a/openpilot/system/ui/lib/multilang.py b/openpilot/system/ui/lib/multilang.py index 3d786a7248..6bdf18f1cd 100644 --- a/openpilot/system/ui/lib/multilang.py +++ b/openpilot/system/ui/lib/multilang.py @@ -19,7 +19,7 @@ UI_DIR = files("openpilot.selfdrive.ui") TRANSLATIONS_DIR = UI_DIR.joinpath("translations") LANGUAGES_FILE = TRANSLATIONS_DIR.joinpath("languages.json") -UNIFONT_LANGUAGES = [ +FONT_FALLBACK_LANGUAGES = [ "th", "zh-CHT", "zh-CHS", @@ -164,9 +164,8 @@ class Multilang: def language(self) -> str: return self._language - def requires_unifont(self) -> bool: - """Certain languages require unifont to render their glyphs.""" - return self._language in UNIFONT_LANGUAGES + def requires_font_fallback(self) -> bool: + return self._language in FONT_FALLBACK_LANGUAGES def setup(self): try: From 0819f5c0fd3c608e3b2775d4fc5237f6dca47bee Mon Sep 17 00:00:00 2001 From: GavinnnK Date: Fri, 7 Aug 2026 16:34:11 -0400 Subject: [PATCH 130/170] common: clean up failed atomic writes (#38552) * common: clean up failed atomic writes * common: remove atomic write regression test --------- Co-authored-by: GavinnnK <239280171+GavinnnK@users.noreply.github.com> Co-authored-by: Adeeb Shihadeh --- openpilot/common/utils.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpilot/common/utils.py b/openpilot/common/utils.py index 22d024fca7..a73d2b8226 100644 --- a/openpilot/common/utils.py +++ b/openpilot/common/utils.py @@ -108,10 +108,14 @@ def atomic_write(path: str, mode: str = 'w', buffering: int = -1, encoding: str if not overwrite and os.path.exists(path): raise FileExistsError(f"File '{path}' already exists. To overwrite it, set 'overwrite' to True.") - with tempfile.NamedTemporaryFile(mode=mode, buffering=buffering, encoding=encoding, newline=newline, dir=dir_name, delete=False) as tmp_file: - yield tmp_file - tmp_file_name = tmp_file.name - os.replace(tmp_file_name, path) + tmp_file = tempfile.NamedTemporaryFile(mode=mode, buffering=buffering, encoding=encoding, newline=newline, dir=dir_name, delete=False) + try: + with tmp_file: + yield tmp_file + os.replace(tmp_file.name, path) + finally: + with contextlib.suppress(FileNotFoundError): + os.unlink(tmp_file.name) def get_upload_stream(filepath: str, should_compress: bool) -> tuple[io.BufferedIOBase, int]: From 5fadc71a3aa9f484abac2ded7476fa6bd2290e02 Mon Sep 17 00:00:00 2001 From: PeterPhuTran Date: Fri, 7 Aug 2026 13:37:57 -0700 Subject: [PATCH 131/170] swaglog: delete oldest logs on rollover, not newest (#38322) --- openpilot/common/swaglog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpilot/common/swaglog.py b/openpilot/common/swaglog.py index 8b629b3fba..7a8c119c6b 100644 --- a/openpilot/common/swaglog.py +++ b/openpilot/common/swaglog.py @@ -45,7 +45,8 @@ class SwaglogRotatingFileHandler(BaseRotatingHandler): fp = os.path.join(base_dir, fn) if fp.startswith(self.base_filename) and os.path.isfile(fp): log_files.append(fp) - return sorted(log_files) + # newest first, matching _open()'s insert(0, ...) so doRollover()'s pop() deletes the oldest + return sorted(log_files, reverse=True) def shouldRollover(self, record): size_exceeded = self.max_bytes > 0 and self.stream.tell() >= self.max_bytes From edd938105edbdd2d59ef4657f6393400b0275f84 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 7 Aug 2026 14:11:12 -0700 Subject: [PATCH 132/170] remove old regen infra (#38570) --- .github/workflows/tests.yaml | 6 - .../selfdrive/test/process_replay/regen.py | 118 ------------------ .../test/process_replay/regen_all.py | 54 -------- tools/test_runner.py | 1 - 4 files changed, 179 deletions(-) delete mode 100755 openpilot/selfdrive/test/process_replay/regen.py delete mode 100755 openpilot/selfdrive/test/process_replay/regen_all.py diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 85475189f8..b8b1ace97a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -173,12 +173,6 @@ jobs: timeout_minutes: 2 max_attempts: 3 command: cd ${{ github.workspace }}/ci-artifacts && git push origin process-replay --force - - name: Run regen - if: false - timeout-minutes: 4 - env: - ONNXCPU: 1 - run: python openpilot/selfdrive/test/process_replay/test_regen.py simulator_driving: name: simulator driving diff --git a/openpilot/selfdrive/test/process_replay/regen.py b/openpilot/selfdrive/test/process_replay/regen.py deleted file mode 100755 index c501a4b250..0000000000 --- a/openpilot/selfdrive/test/process_replay/regen.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env python3 -import os -import argparse -import time -import capnp - -from typing import Any -from collections.abc import Iterable - -from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, FAKEDATA, ProcessConfig, replay_process, get_process_config, \ - check_openpilot_enabled, check_most_messages_valid, get_custom_params_from_lr -from openpilot.selfdrive.test.update_ci_routes import upload_route -from openpilot.tools.lib.framereader import FrameReader -from openpilot.tools.lib.logreader import LogReader, LogIterable, save_log -from openpilot.tools.lib.openpilotci import get_url - - -def regen_segment( - lr: LogIterable, frs: dict[str, Any] | None = None, - processes: Iterable[ProcessConfig] = CONFIGS, disable_tqdm: bool = False -) -> list[capnp._DynamicStructReader]: - all_msgs = sorted(lr, key=lambda m: m.logMonoTime) - custom_params = get_custom_params_from_lr(all_msgs) - - print("Replayed processes:", [p.proc_name for p in processes]) - print("\n\n", "*"*30, "\n\n", sep="") - - output_logs = replay_process(processes, all_msgs, frs, return_all_logs=True, custom_params=custom_params, disable_progress=disable_tqdm) - - return output_logs - - -def setup_data_readers( - route: str, sidx: int, needs_driver_cam: bool = True, needs_road_cam: bool = True, dummy_driver_cam: bool = False -) -> tuple[LogReader, dict[str, Any]]: - lr = LogReader(f"{route}/{sidx}/r") - frs = {} - if needs_road_cam: - frs['roadCameraState'] = FrameReader(get_url(route, str(sidx), "fcamera.hevc")) - if next((True for m in lr if m.which() == "wideRoadCameraState"), False): - frs['wideRoadCameraState'] = FrameReader(get_url(route, str(sidx), "ecamera.hevc")) - if needs_driver_cam: - if dummy_driver_cam: - frs['driverCameraState'] = FrameReader(get_url(route, str(sidx), "fcamera.hevc")) # Use fcam as dummy - else: - device_type = next(str(msg.initData.deviceType) for msg in lr if msg.which() == "initData") - assert device_type != "neo", "Driver camera not supported on neo segments. Use dummy dcamera." - frs['driverCameraState'] = FrameReader(get_url(route, str(sidx), "dcamera.hevc")) - - return lr, frs - - -def regen_and_save( - route: str, sidx: int, processes: str | Iterable[str] = "all", outdir: str = FAKEDATA, - upload: bool = False, disable_tqdm: bool = False, dummy_driver_cam: bool = False -) -> str: - if not isinstance(processes, str) and not hasattr(processes, "__iter__"): - raise ValueError("whitelist_proc must be a string or iterable") - - if processes != "all": - if isinstance(processes, str): - raise ValueError(f"Invalid value for processes: {processes}") - - replayed_processes = [] - for d in processes: - cfg = get_process_config(d) - replayed_processes.append(cfg) - else: - replayed_processes = CONFIGS - - all_vision_pubs = {pub for cfg in replayed_processes for pub in cfg.vision_pubs} - lr, frs = setup_data_readers(route, sidx, - needs_driver_cam="driverCameraState" in all_vision_pubs, - needs_road_cam="roadCameraState" in all_vision_pubs or "wideRoadCameraState" in all_vision_pubs, - dummy_driver_cam=dummy_driver_cam) - output_logs = regen_segment(lr, frs, replayed_processes, disable_tqdm=disable_tqdm) - - log_dir = os.path.join(outdir, time.strftime("%Y-%m-%d--%H-%M-%S--0", time.gmtime())) - rel_log_dir = os.path.relpath(log_dir) - rpath = os.path.join(log_dir, "rlog.zst") - - os.makedirs(log_dir) - save_log(rpath, output_logs, compress=True) - - print("\n\n", "*"*30, "\n\n", sep="") - print("New route:", rel_log_dir, "\n") - - if not check_openpilot_enabled(output_logs): - raise Exception("Route did not engage for long enough") - if not check_most_messages_valid(output_logs): - raise Exception("Route has too many invalid messages") - - if upload: - upload_route(rel_log_dir) - - return rel_log_dir - - -if __name__ == "__main__": - def comma_separated_list(string): - return string.split(",") - - all_procs = [p.proc_name for p in CONFIGS] - parser = argparse.ArgumentParser(description="Generate new segments from old ones") - parser.add_argument("--upload", action="store_true", help="Upload the new segment to the CI bucket") - parser.add_argument("--outdir", help="log output dir", default=FAKEDATA) - parser.add_argument("--dummy-dcamera", action='store_true', help="Use dummy blank driver camera") - parser.add_argument("--whitelist-procs", type=comma_separated_list, default=all_procs, - help="Comma-separated whitelist of processes to regen (e.g. controlsd,radard)") - parser.add_argument("--blacklist-procs", type=comma_separated_list, default=[], - help="Comma-separated blacklist of processes to regen (e.g. controlsd,radard)") - parser.add_argument("route", type=str, help="The source route") - parser.add_argument("seg", type=int, help="Segment in source route") - args = parser.parse_args() - - blacklist_set = set(args.blacklist_procs) - processes = [p for p in args.whitelist_procs if p not in blacklist_set] - regen_and_save(args.route, args.seg, processes=processes, upload=args.upload, outdir=args.outdir, dummy_driver_cam=args.dummy_dcamera) diff --git a/openpilot/selfdrive/test/process_replay/regen_all.py b/openpilot/selfdrive/test/process_replay/regen_all.py deleted file mode 100755 index 78a90b420c..0000000000 --- a/openpilot/selfdrive/test/process_replay/regen_all.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import concurrent.futures -import os -import random -import traceback -from tqdm import tqdm - -from openpilot.common.prefix import OpenpilotPrefix -from openpilot.selfdrive.test.process_replay.regen import regen_and_save -from openpilot.selfdrive.test.process_replay.test_processes import FAKEDATA, source_segments as segments -from openpilot.tools.lib.route import SegmentName - - -def regen_job(segment, upload, disable_tqdm): - with OpenpilotPrefix(): - sn = SegmentName(segment[1]) - fake_dongle_id = 'regen' + ''.join(random.choice('0123456789ABCDEF') for _ in range(11)) - try: - relr = regen_and_save(sn.route_name.canonical_name, sn.segment_num, upload=upload, - outdir=os.path.join(FAKEDATA, fake_dongle_id), disable_tqdm=disable_tqdm, dummy_driver_cam=True) - relr = '|'.join(relr.split('/')[-2:]) - return f' ("{segment[0]}", "{relr}"), ' - except Exception as e: - err = f" {segment} failed: {str(e)}" - err += traceback.format_exc() - err += "\n\n" - return err - - -if __name__ == "__main__": - all_cars = {car for car, _ in segments} - - parser = argparse.ArgumentParser(description="Generate new segments from old ones") - parser.add_argument("-j", "--jobs", type=int, default=1) - parser.add_argument("--no-upload", action="store_true") - parser.add_argument("--whitelist-cars", type=str, nargs="*", default=all_cars, - help="Whitelist given cars from the test (e.g. HONDA)") - parser.add_argument("--blacklist-cars", type=str, nargs="*", default=[], - help="Blacklist given cars from the test (e.g. HONDA)") - args = parser.parse_args() - - tested_cars = set(args.whitelist_cars) - set(args.blacklist_cars) - tested_cars = {c.upper() for c in tested_cars} - tested_segments = [(car, segment) for car, segment in segments if car in tested_cars] - - with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool: - p = pool.map(regen_job, tested_segments, [not args.no_upload] * len(tested_segments), [args.jobs > 1] * len(tested_segments)) - msg = "Copy these new segments into test_processes.py:" - for seg in tqdm(p, desc="Generating segments", total=len(tested_segments)): - msg += "\n" + str(seg) - print() - print() - print(msg) diff --git a/tools/test_runner.py b/tools/test_runner.py index eb2f109f97..1979ce01e4 100755 --- a/tools/test_runner.py +++ b/tools/test_runner.py @@ -16,7 +16,6 @@ import warnings ROOT = Path(__file__).resolve().parents[1] IGNORED = ( ROOT / "openpilot/selfdrive/test/process_replay/test_processes.py", - ROOT / "openpilot/selfdrive/test/process_replay/test_regen.py", ROOT / "openpilot/tools/sim", ) FAILURES = {"failed", "error", "xpassed"} From 612d97cfdbae347394dc7f3e80139eea7b3a52ad Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 7 Aug 2026 14:56:18 -0700 Subject: [PATCH 133/170] paramsd: don't learn in reverse gear (#38573) --- openpilot/selfdrive/locationd/paramsd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpilot/selfdrive/locationd/paramsd.py b/openpilot/selfdrive/locationd/paramsd.py index c1088a8180..9b5ff7143e 100755 --- a/openpilot/selfdrive/locationd/paramsd.py +++ b/openpilot/selfdrive/locationd/paramsd.py @@ -121,7 +121,7 @@ class VehicleParamsLearner: in_linear_region = abs(steering_angle) < 45 self.observed_speed = msg.vEgo - self.active = self.observed_speed > MIN_ACTIVE_SPEED and in_linear_region + self.active = self.observed_speed > MIN_ACTIVE_SPEED and in_linear_region and msg.gearShifter != car.CarState.GearShifter.reverse if self.active: self.kf.predict_and_observe(t, ObservationKind.STEER_ANGLE, np.array([[np.radians(steering_angle)]])) From 4eb515b32d9ab416a2e9cb894f49af6800eb58d9 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Fri, 7 Aug 2026 15:39:59 -0700 Subject: [PATCH 134/170] DM: Zoom Zoom Model (#38554) --- openpilot/selfdrive/modeld/models/dmonitoring_model.onnx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpilot/selfdrive/modeld/models/dmonitoring_model.onnx b/openpilot/selfdrive/modeld/models/dmonitoring_model.onnx index bd3f2c85a6..87a116cdba 100644 --- a/openpilot/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/openpilot/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e7b31dfbc0a5234f1baf196513b77fc6af12204b8a8ffe8ee0417e48352f316 -size 7494962 +oid sha256:607ed8f64a2d756657b621aaccf92a5cadb296b0a1d13d11605766901510cef1 +size 7486861 From 8a8860880086b8fa9f983364c07393f2498e7b54 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 7 Aug 2026 15:41:28 -0700 Subject: [PATCH 135/170] rm qrcode (#38575) --- openpilot/common/qrcode.py | 218 ++++++++++++++++++ .../selfdrive/ui/mici/layouts/onboarding.py | 18 +- .../ui/mici/widgets/pairing_dialog.py | 20 +- .../selfdrive/ui/widgets/pairing_dialog.py | 20 +- pyproject.toml | 1 - uv.lock | 14 -- 6 files changed, 224 insertions(+), 67 deletions(-) create mode 100644 openpilot/common/qrcode.py diff --git a/openpilot/common/qrcode.py b/openpilot/common/qrcode.py new file mode 100644 index 0000000000..b6b345c227 --- /dev/null +++ b/openpilot/common/qrcode.py @@ -0,0 +1,218 @@ +"""Small QR encoder for the UI's byte-mode, error-correction-level-L codes.""" + +import numpy as np +import pyray as rl + + +# Indexes are QR versions. These are the only two Reed-Solomon parameters needed +# for error-correction level L. +_ECC_LEN = (0, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28) +_NUM_BLOCKS = (0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8) + +# 15 format-info bits for level L (01) with mask 0: ((0x08 << 10) | bch_remainder) ^ 0x5412 +_FORMAT_BITS = 0b111011111000100 + + +def _raw_modules(version: int) -> int: + result = (16 * version + 128) * version + 64 + if version >= 2: + align = version // 7 + 2 + result -= (25 * align - 10) * align - 55 + return result - (36 if version >= 7 else 0) + + +def _capacity(version: int) -> int: + return _raw_modules(version) // 8 - _ECC_LEN[version] * _NUM_BLOCKS[version] + + +def _append_bits(bits: list[int], value: int, length: int) -> None: + bits.extend((value >> i) & 1 for i in range(length - 1, -1, -1)) + + +def _data_codewords(data: bytes, version: int) -> bytes: + """Byte-mode-encode the payload, terminated and padded to the version's capacity.""" + capacity = _capacity(version) + bits: list[int] = [] + _append_bits(bits, 4, 4) # byte mode + _append_bits(bits, len(data), 8 if version <= 9 else 16) + for value in data: + _append_bits(bits, value, 8) + bits.extend([0] * min(4, capacity * 8 - len(bits))) # terminator + bits.extend([0] * (-len(bits) % 8)) # byte alignment + result = bytearray(sum(bits[i + j] << (7 - j) for j in range(8)) for i in range(0, len(bits), 8)) + pad = (0xEC, 0x11) + while len(result) < capacity: + result.append(pad[(len(result) - (len(bits) // 8)) & 1]) + return bytes(result) + + +def _codewords(data: bytes, version: int) -> bytes: + """Split data codewords into Reed-Solomon blocks and interleave data + ECC.""" + data = _data_codewords(data, version) + num_blocks = _NUM_BLOCKS[version] + ecc_len = _ECC_LEN[version] + raw_codewords = _raw_modules(version) // 8 + short_len = raw_codewords // num_blocks + num_short = num_blocks - raw_codewords % num_blocks + divisor = _divisor(ecc_len) + blocks: list[tuple[bytes, bytes]] = [] + offset = 0 + for i in range(num_blocks): + length = short_len - ecc_len + (0 if i < num_short else 1) + block = data[offset:offset + length] + blocks.append((block, _remainder(block, divisor))) + offset += length + result = bytearray() + for i in range(short_len - ecc_len + 1): + for block, _ in blocks: + result.extend(block[i:i + 1]) + for i in range(ecc_len): + for _, ecc in blocks: + result.append(ecc[i]) + return bytes(result) + + +def _multiply(x: int, y: int) -> int: + result = 0 + for _ in range(8): + result = (result << 1) ^ (0x11D if result & 0x80 else 0) + if y & 0x80: + result ^= x + y <<= 1 + return result + + +def _divisor(degree: int) -> bytes: + result = bytearray([0] * (degree - 1) + [1]) + root = 1 + for _ in range(degree): + for j in range(degree): + result[j] = _multiply(result[j], root) + if j + 1 < degree: + result[j] ^= result[j + 1] + root = _multiply(root, 2) + return bytes(result) + + +def _remainder(data: bytes, divisor: bytes) -> bytes: + result = bytearray(len(divisor)) + for value in data: + factor = value ^ result.pop(0) + result.append(0) + for i, coefficient in enumerate(divisor): + result[i] ^= _multiply(coefficient, factor) + return bytes(result) + + +def _alignment_positions(version: int) -> list[int]: + if version == 1: + return [] + count = version // 7 + 2 + step = ((version * 4 + count * 2 + 1) // (count * 2 - 2)) * 2 + return [6] + [version * 4 + 10 - step * i for i in range(count - 1)][::-1] + + +class _Qr: + def __init__(self, version: int, data: bytes): + self.version = version + self.size = version * 4 + 17 + self.modules = [[False] * self.size for _ in range(self.size)] + self.function = [[False] * self.size for _ in range(self.size)] + self._draw_functions() + self._draw_data(_codewords(data, version)) + for y in range(self.size): + for x in range(self.size): + if not self.function[y][x]: + self.modules[y][x] ^= (x + y) % 2 == 0 + self._format() + + def _set_function(self, x: int, y: int, dark: bool) -> None: + if 0 <= x < self.size and 0 <= y < self.size: + self.modules[y][x] = dark + self.function[y][x] = True + + def _finder(self, x: int, y: int) -> None: + for dy in range(-4, 5): + for dx in range(-4, 5): + distance = max(abs(dx), abs(dy)) + self._set_function(x + dx, y + dy, distance != 2 and distance != 4) + + def _alignment(self, x: int, y: int) -> None: + for dy in range(-2, 3): + for dx in range(-2, 3): + self._set_function(x + dx, y + dy, max(abs(dx), abs(dy)) != 1) + + def _draw_functions(self) -> None: + for i in range(self.size): + self._set_function(6, i, i % 2 == 0) + self._set_function(i, 6, i % 2 == 0) + self._finder(3, 3) + self._finder(self.size - 4, 3) + self._finder(3, self.size - 4) + positions = _alignment_positions(self.version) + for y in positions: + for x in positions: + if not ((x == 6 and y in (6, self.size - 7)) or (x == self.size - 7 and y == 6)): + self._alignment(x, y) + # reserve the format-info modules before the data is placed; the real + # values are written by the second _format call after masking + self._format() + if self.version >= 7: + value = self.version + for _ in range(12): + value = (value << 1) ^ ((value >> 11) * 0x1F25) + value = self.version << 12 | value + for i in range(18): + bit = ((value >> i) & 1) != 0 + a = self.size - 11 + i % 3 + b = i // 3 + self._set_function(a, b, bit) + self._set_function(b, a, bit) + + def _format(self) -> None: + for i in range(15): + bit = ((_FORMAT_BITS >> i) & 1) != 0 + y_pos = i if i < 6 else i + 1 if i < 8 else self.size - 15 + i + self._set_function(8, y_pos, bit) + x_pos = self.size - 1 - i if i < 8 else 15 - i if i < 9 else 14 - i + self._set_function(x_pos, 8, bit) + self._set_function(8, self.size - 8, True) + + def _draw_data(self, data: bytes) -> None: + bits = ((byte >> s) & 1 for byte in data for s in reversed(range(8))) + upward = True + right = self.size - 1 + while right >= 1: + if right == 6: # skip the vertical timing column + right = 5 + for vert in range(self.size): + y = self.size - 1 - vert if upward else vert + for x in (right, right - 1): + if not self.function[y][x]: + self.modules[y][x] = bool(next(bits, 0)) + upward = not upward + right -= 2 + + +def make_texture(data: str, inverted: bool = False) -> rl.Texture: + """Render a URL as the RGBA QR texture used by the UI. The texture upload + copies the pixels, so the intermediate image/array don't need to outlive it.""" + raw = data.encode() + for version in range(1, 21): + count_bits = 8 if version <= 9 else 16 + if 4 + count_bits + len(raw) * 8 <= _capacity(version) * 8: + break + else: + raise ValueError("QR URL is too long") + modules = np.pad(_Qr(version, raw).modules, 0 if inverted else 4) + modules = np.repeat(np.repeat(modules, 10, axis=0), 10, axis=1) + gray = ((modules == inverted) * 255).astype(np.uint8) + img_array = np.dstack((gray, gray, gray, np.full_like(gray, 255))) + + rl_image = rl.Image() + rl_image.data = rl.ffi.cast("void *", img_array.ctypes.data) + rl_image.width = img_array.shape[1] + rl_image.height = img_array.shape[0] + rl_image.mipmaps = 1 + rl_image.format = rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + return rl.load_texture_from_image(rl_image) diff --git a/openpilot/selfdrive/ui/mici/layouts/onboarding.py b/openpilot/selfdrive/ui/mici/layouts/onboarding.py index 8d80df3a89..f1ebd3bacd 100644 --- a/openpilot/selfdrive/ui/mici/layouts/onboarding.py +++ b/openpilot/selfdrive/ui/mici/layouts/onboarding.py @@ -1,9 +1,9 @@ import math import numpy as np -import qrcode import pyray as rl from collections.abc import Callable from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.qrcode import make_texture from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import SmallCircleIconButton @@ -284,21 +284,7 @@ class QRCodeWidget(Widget): self._generate_qr(url) def _generate_qr(self, url: str): - qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=0) - qr.add_data(url) - qr.make(fit=True) - - pil_img = qr.make_image(fill_color="white", back_color="black").convert('RGBA') - img_array = np.array(pil_img, dtype=np.uint8) - - rl_image = rl.Image() - rl_image.data = rl.ffi.cast("void *", img_array.ctypes.data) - rl_image.width = pil_img.width - rl_image.height = pil_img.height - rl_image.mipmaps = 1 - rl_image.format = rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 - - self._qr_texture = rl.load_texture_from_image(rl_image) + self._qr_texture = make_texture(url, inverted=True) def _render(self, _): if self._qr_texture: diff --git a/openpilot/selfdrive/ui/mici/widgets/pairing_dialog.py b/openpilot/selfdrive/ui/mici/widgets/pairing_dialog.py index a18b26ec02..7e05bc2fa0 100644 --- a/openpilot/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/openpilot/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -1,9 +1,8 @@ import pyray as rl -import qrcode -import numpy as np import time from openpilot.common.api import Api +from openpilot.common.qrcode import make_texture from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.selfdrive.ui.ui_state import ui_state @@ -37,24 +36,9 @@ class PairingDialog(NavWidget): def _generate_qr_code(self) -> None: try: - qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=0) - qr.add_data(self._get_pairing_url()) - qr.make(fit=True) - - pil_img = qr.make_image(fill_color="white", back_color="black").convert('RGBA') - img_array = np.array(pil_img, dtype=np.uint8) - if self._qr_texture and self._qr_texture.id != 0: rl.unload_texture(self._qr_texture) - - rl_image = rl.Image() - rl_image.data = rl.ffi.cast("void *", img_array.ctypes.data) - rl_image.width = pil_img.width - rl_image.height = pil_img.height - rl_image.mipmaps = 1 - rl_image.format = rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 - - self._qr_texture = rl.load_texture_from_image(rl_image) + self._qr_texture = make_texture(self._get_pairing_url(), inverted=True) except Exception as e: cloudlog.warning(f"QR code generation failed: {e}") self._qr_texture = None diff --git a/openpilot/selfdrive/ui/widgets/pairing_dialog.py b/openpilot/selfdrive/ui/widgets/pairing_dialog.py index 1ff550e4b6..54ccfe19be 100644 --- a/openpilot/selfdrive/ui/widgets/pairing_dialog.py +++ b/openpilot/selfdrive/ui/widgets/pairing_dialog.py @@ -1,9 +1,8 @@ import pyray as rl -import qrcode -import numpy as np import time from openpilot.common.api import Api +from openpilot.common.qrcode import make_texture from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.system.ui.widgets import Widget @@ -39,24 +38,9 @@ class PairingDialog(Widget): def _generate_qr_code(self) -> None: try: - qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4) - qr.add_data(self._get_pairing_url()) - qr.make(fit=True) - - pil_img = qr.make_image(fill_color="black", back_color="white").convert('RGBA') - img_array = np.array(pil_img, dtype=np.uint8) - if self.qr_texture and self.qr_texture.id != 0: rl.unload_texture(self.qr_texture) - - rl_image = rl.Image() - rl_image.data = rl.ffi.cast("void *", img_array.ctypes.data) - rl_image.width = pil_img.width - rl_image.height = pil_img.height - rl_image.mipmaps = 1 - rl_image.format = rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 - - self.qr_texture = rl.load_texture_from_image(rl_image) + self.qr_texture = make_texture(self._get_pairing_url()) except Exception: cloudlog.exception("QR code generation failed") self.qr_texture = None diff --git a/pyproject.toml b/pyproject.toml index 00c21165ec..78fb53ed1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,6 @@ dependencies = [ # ui "comma-deps-raylib", - "qrcode", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index df38331dfc..efd0aad651 100644 --- a/uv.lock +++ b/uv.lock @@ -588,7 +588,6 @@ dependencies = [ { name = "pycapnp" }, { name = "pyjwt", extra = ["crypto"] }, { name = "pyzmq" }, - { name = "qrcode" }, { name = "requests" }, { name = "scons" }, { name = "sentry-sdk" }, @@ -654,7 +653,6 @@ requires-dist = [ { name = "pycapnp", specifier = "==2.1.0" }, { name = "pyjwt", extras = ["crypto"] }, { name = "pyzmq" }, - { name = "qrcode" }, { name = "rednose", marker = "extra == 'submodules'", editable = "rednose_repo" }, { name = "requests" }, { name = "ruff", marker = "extra == 'testing'" }, @@ -835,18 +833,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, ] -[[package]] -name = "qrcode" -version = "8.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317, upload-time = "2025-05-01T15:44:24.726Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986, upload-time = "2025-05-01T15:44:22.781Z" }, -] - [[package]] name = "rednose" version = "0.0.1" From a75cd2d2776a2a50d173669e19ac1ee4b5a026d5 Mon Sep 17 00:00:00 2001 From: Fang Li Date: Fri, 7 Aug 2026 16:56:49 -0700 Subject: [PATCH 136/170] Process eSIM notifications after profile operations per SGP.22 spec (#38241) * Process eSIM notifications after profile operations * Clarify sleep purpose after operation in execute_and_process_notifications Added comment to clarify the purpose of the sleep. --- openpilot/common/esim/esim.py | 20 +++++++++++++++++--- openpilot/common/esim/lpa.py | 2 +- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/openpilot/common/esim/esim.py b/openpilot/common/esim/esim.py index cb155a1d88..1a05fbe85d 100755 --- a/openpilot/common/esim/esim.py +++ b/openpilot/common/esim/esim.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 import argparse +import sys +import time from openpilot.common.hardware import HARDWARE from openpilot.common.esim.base import LPABase, Profile @@ -27,6 +29,17 @@ def print_profiles(lpa: LPABase) -> None: print(f'{i}. {p.iccid} (nickname: {p.nickname or ""}) (provider: {p.provider}) - {"enabled" if p.enabled else "disabled"}') +def execute_and_process_notifications(lpa: LPABase, operation) -> None: + try: + operation() + finally: + time.sleep(1) # Need to wait for 1s after the operation is finished so the eUICC/modem can settle down. + try: + lpa.process_notifications() + except Exception as e: + print(f'failed to process eSIM notifications: {e}', file=sys.stderr) + + if __name__ == '__main__': parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai') sub = parser.add_subparsers(dest='cmd') @@ -53,17 +66,18 @@ if __name__ == '__main__': if not lpa.is_euicc(): raise SystemExit("no eUICC detected") if args.cmd == 'switch': - lpa.switch_profile(resolve_iccid(lpa, args.profile)) + iccid = resolve_iccid(lpa, args.profile) + execute_and_process_notifications(lpa, lambda: lpa.switch_profile(iccid)) elif args.cmd == 'delete': iccid = resolve_iccid(lpa, args.profile) confirm = input(f'are you sure you want to delete profile {iccid}? (y/N) ') if confirm == 'y': - lpa.delete_profile(iccid) + execute_and_process_notifications(lpa, lambda: lpa.delete_profile(iccid)) else: print('cancelled') exit(0) elif args.cmd == 'download': - lpa.download_profile(args.qr, args.name) + execute_and_process_notifications(lpa, lambda: lpa.download_profile(args.qr, args.name)) elif args.cmd == 'nickname': lpa.nickname_profile(resolve_iccid(lpa, args.profile), args.name) else: diff --git a/openpilot/common/esim/lpa.py b/openpilot/common/esim/lpa.py index 80138b54ae..545f5306d4 100644 --- a/openpilot/common/esim/lpa.py +++ b/openpilot/common/esim/lpa.py @@ -451,7 +451,7 @@ def process_notifications(client: AtClient) -> None: response = es10x_command(client, request) content = require_tag(require_tag(response, TAG_RETRIEVE_NOTIFICATION, "RetrieveNotificationsListResponse"), TAG_OK, "RetrieveNotificationsListResponse") - pending_notif = next((v for t, v in iter_tlv(content) if t in (TAG_PROFILE_INSTALL_RESULT, 0x30)), None) + pending_notif = next((content[start:end] for t, _, start, end in iter_tlv(content, with_positions=True) if t in (TAG_PROFILE_INSTALL_RESULT, 0x30)), None) if pending_notif is None: raise RuntimeError("Missing PendingNotification") From cbda3f7994322ed40b428ea49c5bd868ff018700 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 7 Aug 2026 18:49:27 -0700 Subject: [PATCH 137/170] Rename hardware/tici/ -> hardware/comma/ (#38580) --- .gitattributes | 2 +- Jenkinsfile | 2 +- launch_chffrplus.sh | 6 +++--- openpilot/common/hardware/__init__.py | 2 +- openpilot/common/hardware/{tici => comma}/__init__.py | 0 openpilot/common/hardware/{tici => comma}/agnos.json | 0 openpilot/common/hardware/{tici => comma}/agnos.py | 2 +- .../common/hardware/{tici => comma}/all-partitions.json | 0 openpilot/common/hardware/{tici => comma}/amplifier.py | 0 openpilot/common/hardware/{tici => comma}/hardware.h | 0 openpilot/common/hardware/{tici => comma}/hardware.py | 4 ++-- openpilot/common/hardware/{tici => comma}/id_rsa | 0 openpilot/common/hardware/{tici => comma}/modem.py | 0 openpilot/common/hardware/{tici => comma}/pins.py | 0 openpilot/common/hardware/{tici => comma}/power_monitor.py | 0 openpilot/common/hardware/{tici => comma}/tests/__init__.py | 0 .../hardware/{tici => comma}/tests/test_agnos_updater.py | 0 .../common/hardware/{tici => comma}/tests/test_amplifier.py | 2 +- openpilot/common/hardware/{tici => comma}/updater | 0 openpilot/common/hardware/hw.h | 2 +- openpilot/selfdrive/pandad/tests/test_pandad.py | 2 +- openpilot/selfdrive/test/test_onroad.py | 2 +- openpilot/selfdrive/test/test_power_draw.py | 2 +- openpilot/system/hardware/{tici => comma}/agnos.json | 0 openpilot/system/manager/process_config.py | 2 +- openpilot/system/qcomgpsd/nmeaport.py | 2 +- openpilot/system/qcomgpsd/qcomgpsd.py | 2 +- openpilot/system/ubloxd/pigeond.py | 2 +- openpilot/system/ubloxd/tests/test_pigeond.py | 2 +- openpilot/system/updated/updated.py | 4 ++-- tools/op.sh | 4 ++-- tools/scripts/ssh.py | 2 +- 32 files changed, 24 insertions(+), 24 deletions(-) rename openpilot/common/hardware/{tici => comma}/__init__.py (100%) rename openpilot/common/hardware/{tici => comma}/agnos.json (100%) rename openpilot/common/hardware/{tici => comma}/agnos.py (99%) rename openpilot/common/hardware/{tici => comma}/all-partitions.json (100%) rename openpilot/common/hardware/{tici => comma}/amplifier.py (100%) rename openpilot/common/hardware/{tici => comma}/hardware.h (100%) rename openpilot/common/hardware/{tici => comma}/hardware.py (99%) rename openpilot/common/hardware/{tici => comma}/id_rsa (100%) rename openpilot/common/hardware/{tici => comma}/modem.py (100%) rename openpilot/common/hardware/{tici => comma}/pins.py (100%) rename openpilot/common/hardware/{tici => comma}/power_monitor.py (100%) rename openpilot/common/hardware/{tici => comma}/tests/__init__.py (100%) rename openpilot/common/hardware/{tici => comma}/tests/test_agnos_updater.py (100%) rename openpilot/common/hardware/{tici => comma}/tests/test_amplifier.py (96%) rename openpilot/common/hardware/{tici => comma}/updater (100%) rename openpilot/system/hardware/{tici => comma}/agnos.json (100%) diff --git a/.gitattributes b/.gitattributes index 1cf541aa3a..5cb404146d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,4 +11,4 @@ *.wav filter=lfs diff=lfs merge=lfs -text openpilot/selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text -openpilot/common/hardware/tici/updater filter=lfs diff=lfs merge=lfs -text +openpilot/common/hardware/comma/updater filter=lfs diff=lfs merge=lfs -text diff --git a/Jenkinsfile b/Jenkinsfile index 7c8d1c060e..341df8a8dd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -248,7 +248,7 @@ node { step("build openpilot", "cd openpilot/system/manager && ./build.py"), step("test pandad loopback", "./openpilot/selfdrive/pandad/tests/test_pandad_loopback.py"), step("test pandad spi", "./openpilot/selfdrive/pandad/tests/test_pandad_spi.py"), - step("test amp", "./openpilot/common/hardware/tici/tests/test_amplifier.py"), + step("test amp", "./openpilot/common/hardware/comma/tests/test_amplifier.py"), ]) }, diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 4f767f3b19..62ebffd27b 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -19,12 +19,12 @@ function agnos_init { # Check if AGNOS update is required if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then - AGNOS_PY="$DIR/openpilot/common/hardware/tici/agnos.py" - MANIFEST="$DIR/openpilot/system/hardware/tici/agnos.json" + AGNOS_PY="$DIR/openpilot/common/hardware/comma/agnos.py" + MANIFEST="$DIR/openpilot/system/hardware/comma/agnos.json" if $AGNOS_PY --verify $MANIFEST; then sudo reboot fi - $DIR/openpilot/common/hardware/tici/updater $AGNOS_PY $MANIFEST + $DIR/openpilot/common/hardware/comma/updater $AGNOS_PY $MANIFEST fi } diff --git a/openpilot/common/hardware/__init__.py b/openpilot/common/hardware/__init__.py index 3387d8a0ca..99e619b33b 100644 --- a/openpilot/common/hardware/__init__.py +++ b/openpilot/common/hardware/__init__.py @@ -2,7 +2,7 @@ import os from typing import cast from openpilot.common.hardware.base import HardwareBase -from openpilot.common.hardware.tici.hardware import Tici +from openpilot.common.hardware.comma.hardware import Tici from openpilot.common.hardware.pc.hardware import Pc TICI = os.path.isfile('/TICI') diff --git a/openpilot/common/hardware/tici/__init__.py b/openpilot/common/hardware/comma/__init__.py similarity index 100% rename from openpilot/common/hardware/tici/__init__.py rename to openpilot/common/hardware/comma/__init__.py diff --git a/openpilot/common/hardware/tici/agnos.json b/openpilot/common/hardware/comma/agnos.json similarity index 100% rename from openpilot/common/hardware/tici/agnos.json rename to openpilot/common/hardware/comma/agnos.json diff --git a/openpilot/common/hardware/tici/agnos.py b/openpilot/common/hardware/comma/agnos.py similarity index 99% rename from openpilot/common/hardware/tici/agnos.py rename to openpilot/common/hardware/comma/agnos.py index b3b5e05176..de0fb24551 100755 --- a/openpilot/common/hardware/tici/agnos.py +++ b/openpilot/common/hardware/comma/agnos.py @@ -12,7 +12,7 @@ import requests SPARSE_CHUNK_FMT = struct.Struct('H2xI4x') -AGNOS_MANIFEST_FILE = "openpilot/system/hardware/tici/agnos.json" +AGNOS_MANIFEST_FILE = "openpilot/system/hardware/comma/agnos.json" class StreamingDecompressor: diff --git a/openpilot/common/hardware/tici/all-partitions.json b/openpilot/common/hardware/comma/all-partitions.json similarity index 100% rename from openpilot/common/hardware/tici/all-partitions.json rename to openpilot/common/hardware/comma/all-partitions.json diff --git a/openpilot/common/hardware/tici/amplifier.py b/openpilot/common/hardware/comma/amplifier.py similarity index 100% rename from openpilot/common/hardware/tici/amplifier.py rename to openpilot/common/hardware/comma/amplifier.py diff --git a/openpilot/common/hardware/tici/hardware.h b/openpilot/common/hardware/comma/hardware.h similarity index 100% rename from openpilot/common/hardware/tici/hardware.h rename to openpilot/common/hardware/comma/hardware.h diff --git a/openpilot/common/hardware/tici/hardware.py b/openpilot/common/hardware/comma/hardware.py similarity index 99% rename from openpilot/common/hardware/tici/hardware.py rename to openpilot/common/hardware/comma/hardware.py index aa125ed97d..3bd1538a36 100644 --- a/openpilot/common/hardware/tici/hardware.py +++ b/openpilot/common/hardware/comma/hardware.py @@ -13,8 +13,8 @@ from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action from openpilot.common.esim.base import LPABase from openpilot.common.hardware.base import HardwareBase, ThermalConfig, ThermalZone from openpilot.common.esim.lpa import TiciLPA -from openpilot.common.hardware.tici.pins import GPIO -from openpilot.common.hardware.tici.amplifier import Amplifier +from openpilot.common.hardware.comma.pins import GPIO +from openpilot.common.hardware.comma.amplifier import Amplifier MODEM_STATE_PATH = "/dev/shm/modem" diff --git a/openpilot/common/hardware/tici/id_rsa b/openpilot/common/hardware/comma/id_rsa similarity index 100% rename from openpilot/common/hardware/tici/id_rsa rename to openpilot/common/hardware/comma/id_rsa diff --git a/openpilot/common/hardware/tici/modem.py b/openpilot/common/hardware/comma/modem.py similarity index 100% rename from openpilot/common/hardware/tici/modem.py rename to openpilot/common/hardware/comma/modem.py diff --git a/openpilot/common/hardware/tici/pins.py b/openpilot/common/hardware/comma/pins.py similarity index 100% rename from openpilot/common/hardware/tici/pins.py rename to openpilot/common/hardware/comma/pins.py diff --git a/openpilot/common/hardware/tici/power_monitor.py b/openpilot/common/hardware/comma/power_monitor.py similarity index 100% rename from openpilot/common/hardware/tici/power_monitor.py rename to openpilot/common/hardware/comma/power_monitor.py diff --git a/openpilot/common/hardware/tici/tests/__init__.py b/openpilot/common/hardware/comma/tests/__init__.py similarity index 100% rename from openpilot/common/hardware/tici/tests/__init__.py rename to openpilot/common/hardware/comma/tests/__init__.py diff --git a/openpilot/common/hardware/tici/tests/test_agnos_updater.py b/openpilot/common/hardware/comma/tests/test_agnos_updater.py similarity index 100% rename from openpilot/common/hardware/tici/tests/test_agnos_updater.py rename to openpilot/common/hardware/comma/tests/test_agnos_updater.py diff --git a/openpilot/common/hardware/tici/tests/test_amplifier.py b/openpilot/common/hardware/comma/tests/test_amplifier.py similarity index 96% rename from openpilot/common/hardware/tici/tests/test_amplifier.py rename to openpilot/common/hardware/comma/tests/test_amplifier.py index 2845805860..d26e2e0518 100755 --- a/openpilot/common/hardware/tici/tests/test_amplifier.py +++ b/openpilot/common/hardware/comma/tests/test_amplifier.py @@ -7,7 +7,7 @@ import unittest from panda import Panda from openpilot.common.test import OpenpilotTestCase from openpilot.common.hardware import HARDWARE -from openpilot.common.hardware.tici.amplifier import Amplifier +from openpilot.common.hardware.comma.amplifier import Amplifier class TestAmplifier(OpenpilotTestCase): diff --git a/openpilot/common/hardware/tici/updater b/openpilot/common/hardware/comma/updater similarity index 100% rename from openpilot/common/hardware/tici/updater rename to openpilot/common/hardware/comma/updater diff --git a/openpilot/common/hardware/hw.h b/openpilot/common/hardware/hw.h index 1e097d99b1..d5711de24d 100644 --- a/openpilot/common/hardware/hw.h +++ b/openpilot/common/hardware/hw.h @@ -6,7 +6,7 @@ #include "common/util.h" #if __TICI__ -#include "common/hardware/tici/hardware.h" +#include "common/hardware/comma/hardware.h" #define Hardware HardwareTici #else #include "common/hardware/pc/hardware.h" diff --git a/openpilot/selfdrive/pandad/tests/test_pandad.py b/openpilot/selfdrive/pandad/tests/test_pandad.py index 0b5ea89a9d..bead2ffa84 100755 --- a/openpilot/selfdrive/pandad/tests/test_pandad.py +++ b/openpilot/selfdrive/pandad/tests/test_pandad.py @@ -11,7 +11,7 @@ from openpilot.common.gpio import gpio_set, gpio_init from panda import Panda, PandaDFU from openpilot.system.manager.process_config import managed_processes from openpilot.common.hardware import HARDWARE -from openpilot.common.hardware.tici.pins import GPIO +from openpilot.common.hardware.comma.pins import GPIO HERE = os.path.dirname(os.path.realpath(__file__)) diff --git a/openpilot/selfdrive/test/test_onroad.py b/openpilot/selfdrive/test/test_onroad.py index 57bf48ee81..9a514139c2 100755 --- a/openpilot/selfdrive/test/test_onroad.py +++ b/openpilot/selfdrive/test/test_onroad.py @@ -69,7 +69,7 @@ PROCS = { "openpilot.system.loggerd.deleter": 1.0, "./pandad": 19.0, "openpilot.system.qcomgpsd.qcomgpsd": 1.0, - "openpilot.common.hardware.tici.modem": 10.0, + "openpilot.common.hardware.comma.modem": 10.0, } TIMINGS = { diff --git a/openpilot/selfdrive/test/test_power_draw.py b/openpilot/selfdrive/test/test_power_draw.py index 4c2a15a41f..01856f81e8 100755 --- a/openpilot/selfdrive/test/test_power_draw.py +++ b/openpilot/selfdrive/test/test_power_draw.py @@ -13,7 +13,7 @@ from openpilot.cereal.services import SERVICE_LIST from opendbc.car.car_helpers import get_demo_car_params from openpilot.common.mock import mock_messages from openpilot.common.params import Params -from openpilot.common.hardware.tici.power_monitor import get_power +from openpilot.common.hardware.comma.power_monitor import get_power from openpilot.system.manager.process_config import managed_processes from openpilot.system.manager.manager import manager_cleanup diff --git a/openpilot/system/hardware/tici/agnos.json b/openpilot/system/hardware/comma/agnos.json similarity index 100% rename from openpilot/system/hardware/tici/agnos.json rename to openpilot/system/hardware/comma/agnos.json diff --git a/openpilot/system/manager/process_config.py b/openpilot/system/manager/process_config.py index 1e9e236c97..a4326b4107 100644 --- a/openpilot/system/manager/process_config.py +++ b/openpilot/system/manager/process_config.py @@ -112,7 +112,7 @@ procs = [ PythonProcess("lateral_maneuversd", "openpilot.tools.lateral_maneuvers.lateral_maneuversd", lat_maneuver), PythonProcess("radard", "openpilot.selfdrive.controls.radard", only_onroad), PythonProcess("hardwared", "openpilot.system.hardware.hardwared", always_run), - PythonProcess("modem", "openpilot.common.hardware.tici.modem", always_run, enabled=TICI), + PythonProcess("modem", "openpilot.common.hardware.comma.modem", always_run, enabled=TICI), PythonProcess("tombstoned", "openpilot.system.tombstoned", always_run, enabled=not PC), PythonProcess("updated", "openpilot.system.updated.updated", only_offroad, enabled=not PC), PythonProcess("uploader", "openpilot.system.loggerd.uploader", always_run), diff --git a/openpilot/system/qcomgpsd/nmeaport.py b/openpilot/system/qcomgpsd/nmeaport.py index db1fe5552d..e270e23a3a 100644 --- a/openpilot/system/qcomgpsd/nmeaport.py +++ b/openpilot/system/qcomgpsd/nmeaport.py @@ -121,7 +121,7 @@ def process_nmea_port_messages(device:str="/dev/ttyUSB1") -> NoReturn: def main() -> NoReturn: from openpilot.common.gpio import gpio_init, gpio_set - from openpilot.common.hardware.tici.pins import GPIO + from openpilot.common.hardware.comma.pins import GPIO from openpilot.system.qcomgpsd.qcomgpsd import at_cmd try: diff --git a/openpilot/system/qcomgpsd/qcomgpsd.py b/openpilot/system/qcomgpsd/qcomgpsd.py index 4e52154419..6261b18d56 100755 --- a/openpilot/system/qcomgpsd/qcomgpsd.py +++ b/openpilot/system/qcomgpsd/qcomgpsd.py @@ -15,7 +15,7 @@ import openpilot.cereal.messaging as messaging from openpilot.common.gpio import gpio_init, gpio_set from openpilot.common.utils import retry from openpilot.common.time_helpers import system_time_valid -from openpilot.common.hardware.tici.pins import GPIO +from openpilot.common.hardware.comma.pins import GPIO from openpilot.common.serial import Serial from openpilot.common.swaglog import cloudlog from openpilot.system.qcomgpsd.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv diff --git a/openpilot/system/ubloxd/pigeond.py b/openpilot/system/ubloxd/pigeond.py index 9891285465..f704761e35 100755 --- a/openpilot/system/ubloxd/pigeond.py +++ b/openpilot/system/ubloxd/pigeond.py @@ -14,7 +14,7 @@ from openpilot.common.serial import Serial from openpilot.common.swaglog import cloudlog from openpilot.common.hardware import TICI from openpilot.common.gpio import gpio_init, gpio_set -from openpilot.common.hardware.tici.pins import GPIO +from openpilot.common.hardware.comma.pins import GPIO UBLOX_TTY = "/dev/ttyHS0" diff --git a/openpilot/system/ubloxd/tests/test_pigeond.py b/openpilot/system/ubloxd/tests/test_pigeond.py index b7a0cd216d..87e3f18523 100644 --- a/openpilot/system/ubloxd/tests/test_pigeond.py +++ b/openpilot/system/ubloxd/tests/test_pigeond.py @@ -6,7 +6,7 @@ from openpilot.cereal.services import SERVICE_LIST from openpilot.common.gpio import gpio_read from openpilot.selfdrive.test.helpers import with_processes from openpilot.system.manager.process_config import managed_processes -from openpilot.common.hardware.tici.pins import GPIO +from openpilot.common.hardware.comma.pins import GPIO # TODO: test TTFF when we have good A-GNSS diff --git a/openpilot/system/updated/updated.py b/openpilot/system/updated/updated.py index 9610785861..24acb2fd17 100755 --- a/openpilot/system/updated/updated.py +++ b/openpilot/system/updated/updated.py @@ -203,7 +203,7 @@ def finalize_update() -> None: def handle_agnos_update() -> None: - from openpilot.common.hardware.tici.agnos import flash_agnos_update, get_target_slot_number + from openpilot.common.hardware.comma.agnos import flash_agnos_update, get_target_slot_number cur_version = HARDWARE.get_os_version() updated_version = run(["bash", "-c", r"unset AGNOS_VERSION && source launch_env.sh && \ @@ -219,7 +219,7 @@ def handle_agnos_update() -> None: cloudlog.info(f"Beginning background installation for AGNOS {updated_version}") set_offroad_alert("Offroad_NeosUpdate", True) - manifest_path = os.path.join(OVERLAY_MERGED, "openpilot/system/hardware/tici/agnos.json") + manifest_path = os.path.join(OVERLAY_MERGED, "openpilot/system/hardware/comma/agnos.json") target_slot_number = get_target_slot_number() flash_agnos_update(manifest_path, target_slot_number, cloudlog) set_offroad_alert("Offroad_NeosUpdate", False) diff --git a/tools/op.sh b/tools/op.sh index a21b1c6ab1..3d7d17a76b 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -364,8 +364,8 @@ function op_check_agnos_update() { echo -e "${BOLD}AGNOS update available:${NC} $current_version → $target_version" if read -r -p "Install it now? [y/N] " choice && [[ "$choice" =~ ^[Yy]$ ]]; then - op_run_command "$OPENPILOT_ROOT/openpilot/common/hardware/tici/agnos.py" --swap \ - "$OPENPILOT_ROOT/openpilot/common/hardware/tici/agnos.json" + op_run_command "$OPENPILOT_ROOT/openpilot/common/hardware/comma/agnos.py" --swap \ + "$OPENPILOT_ROOT/openpilot/common/hardware/comma/agnos.json" if read -r -p "Reboot now to apply the update? [y/N] " choice && [[ "$choice" =~ ^[Yy]$ ]]; then op_run_command sudo reboot diff --git a/tools/scripts/ssh.py b/tools/scripts/ssh.py index 86e86c7eed..2d8ff46604 100755 --- a/tools/scripts/ssh.py +++ b/tools/scripts/ssh.py @@ -14,7 +14,7 @@ if __name__ == "__main__": parser.add_argument("device", help="device name or dongle id") parser.add_argument("--host", help="ssh jump server host", default="ssh.comma.ai") parser.add_argument("--port", help="ssh jump server port", default=22, type=int) - parser.add_argument("--key", help="ssh key", default=os.path.join(BASEDIR, "openpilot/common/hardware/tici/id_rsa")) + parser.add_argument("--key", help="ssh key", default=os.path.join(BASEDIR, "openpilot/common/hardware/comma/id_rsa")) parser.add_argument("--debug", help="enable debug output", action="store_true") args = parser.parse_args() From b755d32276a4934690e9e6f6f5824888245d582f Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Fri, 7 Aug 2026 19:38:13 -0700 Subject: [PATCH 138/170] fix telemetry hanging big model (#38565) * modeld: fail fast when the gpu hangs mid run * modeld: harden chestnut telemetry * selfdrived: rm the big model lagging trigger --- openpilot/selfdrive/modeld/modeld.py | 49 ++++++++++++-------- openpilot/selfdrive/selfdrived/selfdrived.py | 3 -- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/openpilot/selfdrive/modeld/modeld.py b/openpilot/selfdrive/modeld/modeld.py index 92cb9e0866..be08c9c4c7 100755 --- a/openpilot/selfdrive/modeld/modeld.py +++ b/openpilot/selfdrive/modeld/modeld.py @@ -77,31 +77,38 @@ class ChestnutState: @cached_property def power_limit(self) -> int: smu = Device["AMD"].iface.dev_impl.smu - return smu._send_msg(smu.smu_mod.PPSMC_MSG_GetPptLimit, 0, read_back_arg=True) + return smu._send_msg(smu.smu_mod.PPSMC_MSG_GetPptLimit, 0, read_back_arg=True, timeout=100) def send(self) -> None: msg = messaging.new_message('chestnutState') state = msg.chestnutState - try: - smu = Device["AMD"].iface.dev_impl.smu - metrics = smu.read_table(smu.smu_mod.SmuMetricsExternal_t, smu.smu_mod.TABLE_SMU_METRICS).SmuMetrics - state.tempC = metrics.AvgTemperature[smu.smu_mod.TEMP_HOTSPOT] - state.memoryTempC = metrics.AvgTemperature[smu.smu_mod.TEMP_MEM] - state.powerDrawW = metrics.AverageSocketPower - state.powerLimitW = self.power_limit - state.gpuUsagePercent = metrics.AverageGfxActivity - state.gpuClockMhz = metrics.AverageGfxclkFrequencyPostDs - state.fanSpeedRpm = metrics.AvgFanRpm - asm = Device["AMD"].iface.pci_dev.usb - state.pcieLtssm = asm.read(0xB450, 1)[0] - state.supplyVoltage, state.supplyCurrent = struct.unpack(' Date: Fri, 7 Aug 2026 20:12:34 -0700 Subject: [PATCH 139/170] rename tici hardware platform to comma (#38581) * rename tici hardware platform to comma * no /TICI * lil more * lil more * you had a good life larch64 * lil more * one more --- .gitignore | 2 +- Jenkinsfile | 4 +-- SConstruct | 32 +++++++++---------- openpilot/cereal/log.capnp | 8 ++--- openpilot/common/esim/lpa.py | 2 +- openpilot/common/hardware/__init__.py | 14 ++++---- openpilot/common/hardware/comma/hardware.h | 2 +- openpilot/common/hardware/comma/hardware.py | 18 ++++++++--- .../hardware/comma/tests/test_amplifier.py | 2 +- openpilot/common/hardware/hw.h | 4 +-- openpilot/common/hardware/pc/hardware.py | 2 +- openpilot/common/test.py | 16 +++++----- openpilot/selfdrive/modeld/SConscript | 2 +- .../selfdrive/pandad/tests/test_pandad.py | 2 +- .../pandad/tests/test_pandad_loopback.py | 2 +- .../selfdrive/pandad/tests/test_pandad_spi.py | 2 +- openpilot/selfdrive/test/test_onroad.py | 2 +- openpilot/selfdrive/test/test_power_draw.py | 2 +- openpilot/selfdrive/ui/SConscript | 2 +- .../ui/mici/onroad/alert_renderer.py | 4 +-- .../selfdrive/ui/mici/onroad/cameraview.py | 18 +++++------ .../selfdrive/ui/onroad/alert_renderer.py | 4 +-- openpilot/selfdrive/ui/onroad/cameraview.py | 18 +++++------ openpilot/selfdrive/ui/ui.py | 4 +-- .../system/athena/tests/test_athenad_ping.py | 8 ++--- openpilot/system/camerad/test/test_camerad.py | 2 +- openpilot/system/hardware/hardwared.py | 4 +-- openpilot/system/loggerd/SConscript | 2 +- openpilot/system/loggerd/encoderd.cc | 8 ++--- .../system/loggerd/tests/test_encoder.py | 8 ++--- .../system/loggerd/tests/test_loggerd.py | 4 +-- openpilot/system/manager/process_config.py | 10 +++--- .../system/sensord/tests/test_sensord.py | 2 +- openpilot/system/ubloxd/pigeond.py | 4 +-- openpilot/system/ubloxd/tests/test_pigeond.py | 2 +- openpilot/system/ui/lib/scroll_panel2.py | 4 +-- openpilot/system/ui/mici_setup.py | 4 +-- openpilot/system/ui/mici_updater.py | 4 +-- 38 files changed, 122 insertions(+), 112 deletions(-) diff --git a/.gitignore b/.gitignore index e1897e0ed9..54f9f176b7 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ a.out .cache/ bin/ -# created at launch for TICI PYTHONPATH (PC uses editable installs via pyproject.toml) +# created at launch for comma hardware PYTHONPATH (PC uses editable installs via pyproject.toml) /msgq /opendbc /rednose diff --git a/Jenkinsfile b/Jenkinsfile index 341df8a8dd..db18d13ddd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -30,14 +30,14 @@ export GIT_COMMIT=${env.GIT_COMMIT} export CI_ARTIFACTS_TOKEN=${env.CI_ARTIFACTS_TOKEN} export GITHUB_COMMENTS_TOKEN=${env.GITHUB_COMMENTS_TOKEN} export AZURE_TOKEN='${env.AZURE_TOKEN}' -# only use 1 thread for tici tests since most require HIL +# only use 1 thread since most require real hardware that can't be shared export PYTEST_ADDOPTS="-n0 -s" export GIT_SSH_COMMAND="ssh -i /data/gitkey" source ~/.bash_profile -if [ -f /TICI ]; then +if [ -f /AGNOS ]; then source /etc/profile rm -rf /tmp/tmp* diff --git a/SConstruct b/SConstruct index 18efb9f8a1..bb5c35feb7 100644 --- a/SConstruct +++ b/SConstruct @@ -10,7 +10,7 @@ import numpy as np import SCons.Errors from SCons.Defaults import _stripixes -TICI = os.path.isfile('/TICI') +COMMA_HARDWARE = os.path.isfile('/AGNOS') SCons.Warnings.warningAsException(True) @@ -24,7 +24,7 @@ release = not os.path.exists(File('#.gitattributes').abspath) # file absent on r AddOption('--minimal', action='store_false', dest='extras', - default=(not TICI and not release), + default=(not COMMA_HARDWARE and not release), help='the minimum build to run openpilot. no tests, tools, etc.') submodule_python_paths = [ @@ -46,13 +46,13 @@ if external_pythonpath := os.environ.get("PYTHONPATH"): arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() if platform.system() == "Darwin": arch = "Darwin" -elif arch == "aarch64" and TICI: - arch = "larch64" +elif arch == "aarch64" and COMMA_HARDWARE: + arch = "comma_arm64" assert arch in [ - "larch64", # linux tici arm64 - "aarch64", # linux pc arm64 - "x86_64", # linux pc x64 - "Darwin", # macOS arm64 (x86 not supported) + "comma_arm64", # linux comma hardware (AGNOS) arm64 + "aarch64", # linux pc arm64 + "x86_64", # linux pc x64 + "Darwin", # macOS arm64 (x86 not supported) ] pkg_names = ['acados', 'capnproto', 'ffmpeg', 'json11', 'ncurses', 'zeromq', 'zstd'] @@ -61,7 +61,7 @@ acados = pkgs[pkg_names.index('acados')] ffmpeg = pkgs[pkg_names.index('ffmpeg')] # Shared package ships .so/.dylib; older device venvs still have static .a only. # Keep static link deps (x264/z/va/drm) when the installed package is static so -# TICI CI works without upgrading the device venv yet. +# COMMA_HARDWARE CI works without upgrading the device venv yet. # TODO: drop the static fallback once device venvs have comma-deps-ffmpeg>=7.1.0.post94 _ffmpeg_lib_names = os.listdir(ffmpeg.LIB_DIR) if os.path.isdir(ffmpeg.LIB_DIR) else [] ffmpeg_shared = any( @@ -133,7 +133,7 @@ env = Environment( "-O2", "-Wunused", "-Werror", - "-Wshadow" if arch in ("Darwin", "larch64") else "-Wshadow=local", + "-Wshadow" if arch in ("Darwin", "comma_arm64") else "-Wshadow=local", "-Wno-unknown-warning-option", "-Wno-inconsistent-missing-override", "-Wno-c99-designator", @@ -172,17 +172,17 @@ if arch == "Darwin": env["RPATHPREFIX"] = "-Wl,-rpath," env["RPATHSUFFIX"] = "" env["_RPATH"] = "${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}" -if arch != "larch64": +if arch != "comma_arm64": env['_LIBFLAGS'] = _libflags # Arch-specific flags and paths -if arch == "larch64": +if arch == "comma_arm64": env["CC"] = "clang" env["CXX"] = "clang++" env.Append(LIBPATH=[ "/usr/lib/aarch64-linux-gnu", ]) - arch_flags = ["-D__TICI__", "-mcpu=cortex-a57"] + arch_flags = ["-D__COMMA_HARDWARE__", "-mcpu=cortex-a57"] env.Append(CCFLAGS=arch_flags) env.Append(CXXFLAGS=arch_flags) elif arch == "Darwin": @@ -234,7 +234,7 @@ Export('envCython', 'np_version') Export('env', 'arch', 'acados', 'ffmpeg_libs') # Setup cache dir -cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache' +cache_dir = '/data/scons_cache' if arch == "comma_arm64" else '/tmp/scons_cache' cache_size_limit = 4e9 if "CI" in os.environ else 2e9 CacheDir(cache_dir) Clean(["."], cache_dir) @@ -280,7 +280,7 @@ SConscript([ 'openpilot/system/loggerd/SConscript', ]) -if arch == "larch64": +if arch == "comma_arm64": SConscript(['openpilot/system/camerad/SConscript']) # Build selfdrive @@ -293,7 +293,7 @@ SConscript([ ]) # Build desktop-only tools -if GetOption('extras') and arch != "larch64": +if GetOption('extras') and arch != "comma_arm64": SConscript([ 'openpilot/tools/replay/SConscript', 'openpilot/tools/cabana/SConscript', diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp index fdb025d838..da700b58a7 100644 --- a/openpilot/cereal/log.capnp +++ b/openpilot/cereal/log.capnp @@ -180,13 +180,13 @@ struct InitData { enum DeviceType { unknown @0; - neo @1; + neo @1; # NEO, EON, & comma two chffrAndroid @2; chffrIos @3; - tici @4; + tici @4; # comma three pc @5; - tizi @6; - mici @7; + tizi @6; # comma 3X + mici @7; # comma four } struct PandaInfo { diff --git a/openpilot/common/esim/lpa.py b/openpilot/common/esim/lpa.py index 545f5306d4..fb128c32e1 100644 --- a/openpilot/common/esim/lpa.py +++ b/openpilot/common/esim/lpa.py @@ -682,7 +682,7 @@ def download_profile(client: AtClient, activation_code: str) -> str: session.close() -class TiciLPA(LPABase): +class LPA(LPABase): def __init__(self): if hasattr(self, '_client'): return diff --git a/openpilot/common/hardware/__init__.py b/openpilot/common/hardware/__init__.py index 99e619b33b..58e18736fd 100644 --- a/openpilot/common/hardware/__init__.py +++ b/openpilot/common/hardware/__init__.py @@ -2,15 +2,15 @@ import os from typing import cast from openpilot.common.hardware.base import HardwareBase -from openpilot.common.hardware.comma.hardware import Tici -from openpilot.common.hardware.pc.hardware import Pc +from openpilot.common.hardware.comma.hardware import HardwareComma +from openpilot.common.hardware.pc.hardware import HardwarePc -TICI = os.path.isfile('/TICI') AGNOS = os.path.isfile('/AGNOS') -PC = not TICI +COMMA_HARDWARE = AGNOS +PC = not COMMA_HARDWARE -if TICI: - HARDWARE = cast(HardwareBase, Tici()) +if COMMA_HARDWARE: + HARDWARE = cast(HardwareBase, HardwareComma()) else: - HARDWARE = cast(HardwareBase, Pc()) + HARDWARE = cast(HardwareBase, HardwarePc()) diff --git a/openpilot/common/hardware/comma/hardware.h b/openpilot/common/hardware/comma/hardware.h index a1a8090c35..6292183d9d 100644 --- a/openpilot/common/hardware/comma/hardware.h +++ b/openpilot/common/hardware/comma/hardware.h @@ -9,7 +9,7 @@ #include "common/util.h" #include "common/hardware/base.h" -class HardwareTici : public HardwareNone { +class HardwareComma : public HardwareNone { public: static std::string get_name() { static const std::string name = []() { diff --git a/openpilot/common/hardware/comma/hardware.py b/openpilot/common/hardware/comma/hardware.py index 3bd1538a36..dfbf1e3f17 100644 --- a/openpilot/common/hardware/comma/hardware.py +++ b/openpilot/common/hardware/comma/hardware.py @@ -12,7 +12,7 @@ from openpilot.common.utils import sudo_read, sudo_write from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action from openpilot.common.esim.base import LPABase from openpilot.common.hardware.base import HardwareBase, ThermalConfig, ThermalZone -from openpilot.common.esim.lpa import TiciLPA +from openpilot.common.esim.lpa import LPA from openpilot.common.hardware.comma.pins import GPIO from openpilot.common.hardware.comma.amplifier import Amplifier @@ -58,7 +58,17 @@ def get_default_route_iface(): routes = [(int(route[6]), route[0]) for line in f.readlines()[1:] if (route := line.split())[1] == "00000000" and int(route[3], 16) & 0x1] return min(routes)[1] if routes else None -class Tici(HardwareBase): +class HardwareComma(HardwareBase): + """ + This platform covers the Snapdragon 845-based comma devices: + - tici = comma three + - tizi = comma 3X + - mici = comma four + + We strictly use only the device codenames in this codebase for + consistency, though all user-facing UI should use the product names. + """ + @cached_property def amplifier(self): if self.get_device_type() == "mici": @@ -145,7 +155,7 @@ class Tici(HardwareBase): } def get_sim_lpa(self) -> LPABase: - return TiciLPA() + return LPA() def get_imei(self): return self.get_modem_state().get('imei', '') @@ -413,7 +423,7 @@ class Tici(HardwareBase): return True if __name__ == "__main__": - t = Tici() + t = HardwareComma() t.initialize_hardware() t.set_power_save(False) print(t.get_sim_info()) diff --git a/openpilot/common/hardware/comma/tests/test_amplifier.py b/openpilot/common/hardware/comma/tests/test_amplifier.py index d26e2e0518..1752bf6e5e 100755 --- a/openpilot/common/hardware/comma/tests/test_amplifier.py +++ b/openpilot/common/hardware/comma/tests/test_amplifier.py @@ -11,7 +11,7 @@ from openpilot.common.hardware.comma.amplifier import Amplifier class TestAmplifier(OpenpilotTestCase): - TICI_TEST = True + COMMA_HARDWARE_TEST = True def setup_method(self): # clear dmesg diff --git a/openpilot/common/hardware/hw.h b/openpilot/common/hardware/hw.h index d5711de24d..83dc452da8 100644 --- a/openpilot/common/hardware/hw.h +++ b/openpilot/common/hardware/hw.h @@ -5,9 +5,9 @@ #include "common/hardware/base.h" #include "common/util.h" -#if __TICI__ +#if __COMMA_HARDWARE__ #include "common/hardware/comma/hardware.h" -#define Hardware HardwareTici +#define Hardware HardwareComma #else #include "common/hardware/pc/hardware.h" #define Hardware HardwarePC diff --git a/openpilot/common/hardware/pc/hardware.py b/openpilot/common/hardware/pc/hardware.py index b4aa56c904..417d2db37d 100644 --- a/openpilot/common/hardware/pc/hardware.py +++ b/openpilot/common/hardware/pc/hardware.py @@ -1,7 +1,7 @@ from openpilot.cereal import log from openpilot.common.hardware.base import HardwareBase -class Pc(HardwareBase): +class HardwarePc(HardwareBase): def get_device_type(self): return "pc" diff --git a/openpilot/common/test.py b/openpilot/common/test.py index 6ccc76c5e3..23b934bfd5 100644 --- a/openpilot/common/test.py +++ b/openpilot/common/test.py @@ -6,7 +6,7 @@ import subprocess import unittest from unittest import mock -from openpilot.common.hardware import HARDWARE, TICI +from openpilot.common.hardware import HARDWARE, COMMA_HARDWARE from openpilot.common.prefix import OpenpilotPrefix from openpilot.system.manager import manager @@ -24,7 +24,7 @@ def clean_env(): class OpenpilotTestCase(unittest.TestCase): """TestCase with openpilot's per-test isolation.""" - TICI_TEST = False + COMMA_HARDWARE_TEST = False SHARED_DOWNLOAD_CACHE = False def __init_subclass__(cls, **kwargs): @@ -61,7 +61,7 @@ class OpenpilotTestCase(unittest.TestCase): def run(self, result=None): # This boundary cannot live in setUp/tearDown: existing unittest classes # are allowed to override those hooks without calling super(). - if (self.TICI_TEST and not TICI) or getattr(type(self), "__unittest_skip__", False): + if (self.COMMA_HARDWARE_TEST and not COMMA_HARDWARE) or getattr(type(self), "__unittest_skip__", False): return super().run(result) test_env = clean_env() test_env.__enter__() @@ -80,8 +80,8 @@ class OpenpilotTestCase(unittest.TestCase): @classmethod def setUpClass(cls): super().setUpClass() - if cls.TICI_TEST and not TICI: - raise unittest.SkipTest("Skipping tici test on PC") + if cls.COMMA_HARDWARE_TEST and not COMMA_HARDWARE: + raise unittest.SkipTest("Skipping comma hardware test on PC") cls._class_env = clean_env() cls._class_env.__enter__() setup_class = getattr(cls, "setup_class", None) @@ -100,10 +100,10 @@ class OpenpilotTestCase(unittest.TestCase): def setUp(self): super().setUp() - if self.TICI_TEST and not TICI: - self.skipTest("Skipping tici test on PC") + if self.COMMA_HARDWARE_TEST and not COMMA_HARDWARE: + self.skipTest("Skipping comma hardware test on PC") - if self.TICI_TEST: + if self.COMMA_HARDWARE_TEST: HARDWARE.initialize_hardware() HARDWARE.set_power_save(False) subprocess.run(["pkill", "-9", "-f", "athena"], check=False) diff --git a/openpilot/selfdrive/modeld/SConscript b/openpilot/selfdrive/modeld/SConscript index 02d79ef7b4..2085d65835 100644 --- a/openpilot/selfdrive/modeld/SConscript +++ b/openpilot/selfdrive/modeld/SConscript @@ -27,7 +27,7 @@ tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + " def estimate_pickle_max_size(onnx_size): return 1.2 * onnx_size + 10 * 1024 * 1024 # 20% + 10MB is plenty -if arch == 'larch64': +if arch == 'comma_arm64': tg_backend = 'QCOM' tg_flags = f'DEV={tg_backend} IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1' else: diff --git a/openpilot/selfdrive/pandad/tests/test_pandad.py b/openpilot/selfdrive/pandad/tests/test_pandad.py index bead2ffa84..891508f2d7 100755 --- a/openpilot/selfdrive/pandad/tests/test_pandad.py +++ b/openpilot/selfdrive/pandad/tests/test_pandad.py @@ -17,7 +17,7 @@ HERE = os.path.dirname(os.path.realpath(__file__)) class TestPandad(OpenpilotTestCase): - TICI_TEST = True + COMMA_HARDWARE_TEST = True def setUp(self): super().setUp() diff --git a/openpilot/selfdrive/pandad/tests/test_pandad_loopback.py b/openpilot/selfdrive/pandad/tests/test_pandad_loopback.py index 5895a32060..ad35018f5f 100755 --- a/openpilot/selfdrive/pandad/tests/test_pandad_loopback.py +++ b/openpilot/selfdrive/pandad/tests/test_pandad_loopback.py @@ -73,7 +73,7 @@ def send_random_can_messages(sendcan, count): class TestBoarddLoopback(OpenpilotTestCase): - TICI_TEST = True + COMMA_HARDWARE_TEST = True @classmethod def setup_class(cls): os.environ['STARTED'] = '1' diff --git a/openpilot/selfdrive/pandad/tests/test_pandad_spi.py b/openpilot/selfdrive/pandad/tests/test_pandad_spi.py index 9cb0ac30c8..ba2e31ce82 100755 --- a/openpilot/selfdrive/pandad/tests/test_pandad_spi.py +++ b/openpilot/selfdrive/pandad/tests/test_pandad_spi.py @@ -16,7 +16,7 @@ from openpilot.selfdrive.pandad.tests.test_pandad_loopback import setup_pandad, JUNGLE_SPAM = "JUNGLE_SPAM" in os.environ class TestBoarddSpi(OpenpilotTestCase): - TICI_TEST = True + COMMA_HARDWARE_TEST = True @classmethod def setup_class(cls): os.environ['STARTED'] = '1' diff --git a/openpilot/selfdrive/test/test_onroad.py b/openpilot/selfdrive/test/test_onroad.py index 9a514139c2..2ee876a6ef 100755 --- a/openpilot/selfdrive/test/test_onroad.py +++ b/openpilot/selfdrive/test/test_onroad.py @@ -105,7 +105,7 @@ def cputime_total(ct): class TestOnroad(OpenpilotTestCase): - TICI_TEST = True + COMMA_HARDWARE_TEST = True @classmethod def setup_class(cls): diff --git a/openpilot/selfdrive/test/test_power_draw.py b/openpilot/selfdrive/test/test_power_draw.py index 01856f81e8..4fba2e8c99 100755 --- a/openpilot/selfdrive/test/test_power_draw.py +++ b/openpilot/selfdrive/test/test_power_draw.py @@ -42,7 +42,7 @@ PROCS = [ class TestPowerDraw(OpenpilotTestCase): - TICI_TEST = True + COMMA_HARDWARE_TEST = True def setup_method(self): Params().put("CarParams", get_demo_car_params().to_bytes(), block=True) diff --git a/openpilot/selfdrive/ui/SConscript b/openpilot/selfdrive/ui/SConscript index 2b02759687..b5c81a9009 100644 --- a/openpilot/selfdrive/ui/SConscript +++ b/openpilot/selfdrive/ui/SConscript @@ -3,7 +3,7 @@ from pathlib import Path Import('env', 'arch', 'common') -if GetOption('extras') and arch == "larch64": +if GetOption('extras') and arch == "comma_arm64": # build installers raylib_dir = Path(importlib.util.find_spec("raylib").submodule_search_locations[0]) / "install" raylib_env = env.Clone() diff --git a/openpilot/selfdrive/ui/mici/onroad/alert_renderer.py b/openpilot/selfdrive/ui/mici/onroad/alert_renderer.py index 12b62924c4..fe96bbd032 100644 --- a/openpilot/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/openpilot/selfdrive/ui/mici/onroad/alert_renderer.py @@ -9,7 +9,7 @@ from openpilot.cereal import messaging, log from opendbc.car.structs import car from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter -from openpilot.common.hardware import TICI +from openpilot.common.hardware import COMMA_HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import UnifiedLabel @@ -132,7 +132,7 @@ class AlertRenderer(Widget): return ALERT_STARTUP_PENDING # 2. Lost communication with selfdriveState after receiving it - if TICI and not waiting_for_startup: + if COMMA_HARDWARE and not waiting_for_startup: ss_missing = time.monotonic() - sm.recv_time['selfdriveState'] if ss_missing > SELFDRIVE_STATE_TIMEOUT: if ss.enabled and (ss_missing - SELFDRIVE_STATE_TIMEOUT) < SELFDRIVE_UNRESPONSIVE_TIMEOUT: diff --git a/openpilot/selfdrive/ui/mici/onroad/cameraview.py b/openpilot/selfdrive/ui/mici/onroad/cameraview.py index 4a31a9e04e..4ccfb04687 100644 --- a/openpilot/selfdrive/ui/mici/onroad/cameraview.py +++ b/openpilot/selfdrive/ui/mici/onroad/cameraview.py @@ -4,7 +4,7 @@ import pyray as rl from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf from openpilot.common.swaglog import cloudlog -from openpilot.common.hardware import TICI +from openpilot.common.hardware import COMMA_HARDWARE from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.egl import init_egl, create_egl_image, destroy_egl_image, bind_egl_image_to_texture, EGLImage from openpilot.system.ui.widgets import Widget @@ -38,7 +38,7 @@ void main() { """ # Choose fragment shader based on platform capabilities -if TICI: +if COMMA_HARDWARE: FRAME_FRAGMENT_SHADER = """ #version 300 es #extension GL_OES_EGL_image_external_essl3 : enable @@ -121,7 +121,7 @@ class CameraView(Widget): self._texture_needs_update = True self.last_connection_attempt: float = 0.0 self.shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAME_FRAGMENT_SHADER) - self._texture1_loc: int = rl.get_shader_location(self.shader, "texture1") if not TICI else -1 + self._texture1_loc: int = rl.get_shader_location(self.shader, "texture1") if not COMMA_HARDWARE else -1 self._engaged_loc = rl.get_shader_location(self.shader, "engaged") self._engaged_val = rl.ffi.new("int[1]", [1]) self._enhance_driver_loc = rl.get_shader_location(self.shader, "enhance_driver") @@ -137,8 +137,8 @@ class CameraView(Widget): self._placeholder_color: rl.Color | None = None - # Initialize EGL for zero-copy rendering on TICI - if TICI: + # Initialize EGL for zero-copy rendering on COMMA_HARDWARE + if COMMA_HARDWARE: if not init_egl(): raise RuntimeError("Failed to initialize EGL") @@ -185,7 +185,7 @@ class CameraView(Widget): self._clear_textures() # Clean up EGL texture - if TICI and self.egl_texture: + if COMMA_HARDWARE and self.egl_texture: rl.unload_texture(self.egl_texture) self.egl_texture = None @@ -260,7 +260,7 @@ class CameraView(Widget): dst_rect = rl.Rectangle(x_offset, y_offset, scale_x, scale_y) # Render with appropriate method - if TICI: + if COMMA_HARDWARE: self._render_egl(src_rect, dst_rect) else: self._render_textures(src_rect, dst_rect) @@ -384,7 +384,7 @@ class CameraView(Widget): def _initialize_textures(self): self._clear_textures() - if not TICI: + if not COMMA_HARDWARE: self.texture_y = rl.load_texture_from_image(rl.Image(None, int(self.client.stride), int(self.client.height), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE)) self.texture_uv = rl.load_texture_from_image(rl.Image(None, int(self.client.stride // 2), @@ -400,7 +400,7 @@ class CameraView(Widget): self.texture_uv = None # Clean up EGL resources - if TICI: + if COMMA_HARDWARE: for data in self.egl_images.values(): destroy_egl_image(data) self.egl_images = {} diff --git a/openpilot/selfdrive/ui/onroad/alert_renderer.py b/openpilot/selfdrive/ui/onroad/alert_renderer.py index 0ce404bec3..29ace66287 100644 --- a/openpilot/selfdrive/ui/onroad/alert_renderer.py +++ b/openpilot/selfdrive/ui/onroad/alert_renderer.py @@ -3,7 +3,7 @@ import pyray as rl from dataclasses import dataclass from openpilot.cereal import messaging, log from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.common.hardware import TICI +from openpilot.common.hardware import COMMA_HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.text_measure import measure_text_cached @@ -96,7 +96,7 @@ class AlertRenderer(Widget): return ALERT_STARTUP_PENDING # 2. Lost communication with selfdriveState after receiving it - if TICI and not waiting_for_startup: + if COMMA_HARDWARE and not waiting_for_startup: ss_missing = time.monotonic() - sm.recv_time['selfdriveState'] if ss_missing > SELFDRIVE_STATE_TIMEOUT: if ss.enabled and (ss_missing - SELFDRIVE_STATE_TIMEOUT) < SELFDRIVE_UNRESPONSIVE_TIMEOUT: diff --git a/openpilot/selfdrive/ui/onroad/cameraview.py b/openpilot/selfdrive/ui/onroad/cameraview.py index 846bf20bbc..1fed2dd683 100644 --- a/openpilot/selfdrive/ui/onroad/cameraview.py +++ b/openpilot/selfdrive/ui/onroad/cameraview.py @@ -4,7 +4,7 @@ import pyray as rl from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf from openpilot.common.swaglog import cloudlog -from openpilot.common.hardware import TICI +from openpilot.common.hardware import COMMA_HARDWARE from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.egl import init_egl, create_egl_image, destroy_egl_image, bind_egl_image_to_texture, EGLImage from openpilot.system.ui.widgets import Widget @@ -38,7 +38,7 @@ void main() { """ # Choose fragment shader based on platform capabilities -if TICI: +if COMMA_HARDWARE: FRAME_FRAGMENT_SHADER = """ #version 300 es #extension GL_OES_EGL_image_external_essl3 : enable @@ -82,7 +82,7 @@ class CameraView(Widget): self._texture_needs_update = True self.last_connection_attempt: float = 0.0 self.shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAME_FRAGMENT_SHADER) - self._texture1_loc: int = rl.get_shader_location(self.shader, "texture1") if not TICI else -1 + self._texture1_loc: int = rl.get_shader_location(self.shader, "texture1") if not COMMA_HARDWARE else -1 self.frame: VisionBuf | None = None self.texture_y: rl.Texture | None = None @@ -94,8 +94,8 @@ class CameraView(Widget): self._placeholder_color: rl.Color | None = None - # Initialize EGL for zero-copy rendering on TICI - if TICI: + # Initialize EGL for zero-copy rendering on COMMA_HARDWARE + if COMMA_HARDWARE: if not init_egl(): raise RuntimeError("Failed to initialize EGL") @@ -146,7 +146,7 @@ class CameraView(Widget): self._clear_textures() # Clean up EGL texture - if TICI and self.egl_texture: + if COMMA_HARDWARE and self.egl_texture: rl.unload_texture(self.egl_texture) self.egl_texture = None @@ -220,7 +220,7 @@ class CameraView(Widget): dst_rect = rl.Rectangle(x_offset, y_offset, scale_x, scale_y) # Render with appropriate method - if TICI: + if COMMA_HARDWARE: self._render_egl(src_rect, dst_rect) else: self._render_textures(src_rect, dst_rect) @@ -337,7 +337,7 @@ class CameraView(Widget): def _initialize_textures(self): self._clear_textures() - if not TICI: + if not COMMA_HARDWARE: self.texture_y = rl.load_texture_from_image(rl.Image(None, int(self.client.stride), int(self.client.height), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE)) self.texture_uv = rl.load_texture_from_image(rl.Image(None, int(self.client.stride // 2), @@ -353,7 +353,7 @@ class CameraView(Widget): self.texture_uv = None # Clean up EGL resources - if TICI: + if COMMA_HARDWARE: for data in self.egl_images.values(): destroy_egl_image(data) self.egl_images = {} diff --git a/openpilot/selfdrive/ui/ui.py b/openpilot/selfdrive/ui/ui.py index ff2bc7d3ce..30bba681c3 100755 --- a/openpilot/selfdrive/ui/ui.py +++ b/openpilot/selfdrive/ui/ui.py @@ -3,7 +3,7 @@ import os import time from openpilot.cereal import messaging -from openpilot.common.hardware import TICI +from openpilot.common.hardware import COMMA_HARDWARE from openpilot.common.realtime import Priority, config_realtime_process, set_core_affinity from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.main import MainLayout @@ -31,7 +31,7 @@ def main(): if should_render: # reaffine after power save offlines our core - if TICI and os.sched_getaffinity(0) != cores: + if COMMA_HARDWARE and os.sched_getaffinity(0) != cores: try: set_core_affinity(list(cores)) except OSError: diff --git a/openpilot/system/athena/tests/test_athenad_ping.py b/openpilot/system/athena/tests/test_athenad_ping.py index d5592c40f1..33876a6235 100644 --- a/openpilot/system/athena/tests/test_athenad_ping.py +++ b/openpilot/system/athena/tests/test_athenad_ping.py @@ -8,13 +8,13 @@ from openpilot.common.test import OpenpilotTestCase from openpilot.common.params import Params from openpilot.common.timeout import Timeout from openpilot.system.athena import athenad -from openpilot.common.hardware import TICI +from openpilot.common.hardware import COMMA_HARDWARE TIMEOUT_TOLERANCE = 20 # seconds def wifi_radio(on: bool) -> None: - if not TICI: + if not COMMA_HARDWARE: return print(f"wifi {'on' if on else 'off'}") subprocess.run(["nmcli", "radio", "wifi", "on" if on else "off"], check=True) @@ -91,12 +91,12 @@ class TestAthenadPing(OpenpilotTestCase): time.sleep(0.1) print("ping received") - @unittest.skipIf(not TICI, "only run on desk") + @unittest.skipIf(not COMMA_HARDWARE, "only run on desk") def test_offroad(self, subtests, mocker) -> None: self.params.put_bool("IsOffroad", True, block=True) self.assertTimeout(60 + TIMEOUT_TOLERANCE, subtests, mocker) # based using TCP keepalive settings - @unittest.skipIf(not TICI, "only run on desk") + @unittest.skipIf(not COMMA_HARDWARE, "only run on desk") def test_onroad(self, subtests, mocker) -> None: self.params.put_bool("IsOffroad", False, block=True) self.assertTimeout(21 + TIMEOUT_TOLERANCE, subtests, mocker) diff --git a/openpilot/system/camerad/test/test_camerad.py b/openpilot/system/camerad/test/test_camerad.py index 0d66f0e16b..580a30683b 100755 --- a/openpilot/system/camerad/test/test_camerad.py +++ b/openpilot/system/camerad/test/test_camerad.py @@ -73,7 +73,7 @@ def _camera_session(): return ts, exposure class TestCamerad(OpenpilotTestCase): - TICI_TEST = True + COMMA_HARDWARE_TEST = True @classmethod def setUpClass(cls): diff --git a/openpilot/system/hardware/hardwared.py b/openpilot/system/hardware/hardwared.py index 10ee0aee3b..270a75ade6 100755 --- a/openpilot/system/hardware/hardwared.py +++ b/openpilot/system/hardware/hardwared.py @@ -17,7 +17,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.common.realtime import DT_HW from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert -from openpilot.common.hardware import HARDWARE, TICI, PC +from openpilot.common.hardware import HARDWARE, COMMA_HARDWARE, PC from openpilot.common.basedir import BASEDIR from openpilot.common.hardware.usb import CHESTNUT_FW_VERSION, CHESTNUT_ROM_USB_IDS, CHESTNUT_USB_IDS, get_usb_state, get_usb_topology, set_usb_state from openpilot.common.linux import LinuxSystemStats @@ -478,7 +478,7 @@ def main(): threading.Thread(target=hardware_thread, args=(end_event, hw_queue)), ] - if TICI: + if COMMA_HARDWARE: threads.append(threading.Thread(target=touch_thread, args=(end_event,))) for t in threads: diff --git a/openpilot/system/loggerd/SConscript b/openpilot/system/loggerd/SConscript index 3f54552695..6890c29655 100644 --- a/openpilot/system/loggerd/SConscript +++ b/openpilot/system/loggerd/SConscript @@ -4,7 +4,7 @@ libs = [common, messaging, visionipc] + ffmpeg_libs + ['pthread', 'm', 'zstd'] frameworks = [] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/jpeg_encoder.cc'] -if arch == "larch64": +if arch == "comma_arm64": src += ['clip_encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/v4l_decoder.cc'] else: src += ['encoder/ffmpeg_encoder.cc'] diff --git a/openpilot/system/loggerd/encoderd.cc b/openpilot/system/loggerd/encoderd.cc index f5b69b3c42..55f0777fbb 100644 --- a/openpilot/system/loggerd/encoderd.cc +++ b/openpilot/system/loggerd/encoderd.cc @@ -1,16 +1,16 @@ #include -#ifdef __TICI__ +#ifdef __COMMA_HARDWARE__ #include #include #endif -#ifdef __TICI__ +#ifdef __COMMA_HARDWARE__ #include "system/loggerd/clip_encoder.h" #endif #include "system/loggerd/loggerd.h" #include "system/loggerd/encoder/jpeg_encoder.h" -#ifdef __TICI__ +#ifdef __COMMA_HARDWARE__ #include "system/loggerd/encoder/v4l_encoder.h" #define Encoder V4LEncoder #else @@ -178,7 +178,7 @@ void encoderd_thread(const LogCameraInfo (&cameras)[N]) { } int main(int argc, char* argv[]) { -#ifdef __TICI__ +#ifdef __COMMA_HARDWARE__ if (argc > 1 && std::string(argv[1]) == "--clip") { if (argc < 6) { fprintf(stderr, "usage: encoderd --clip OUTPUT START DURATION [--bitrate BPS] [--speedup N] " diff --git a/openpilot/system/loggerd/tests/test_encoder.py b/openpilot/system/loggerd/tests/test_encoder.py index 62c040cc19..307a89ba95 100755 --- a/openpilot/system/loggerd/tests/test_encoder.py +++ b/openpilot/system/loggerd/tests/test_encoder.py @@ -13,7 +13,7 @@ from tqdm import trange from openpilot.common.test import OpenpilotTestCase from openpilot.common.params import Params from openpilot.common.timeout import Timeout -from openpilot.common.hardware import TICI +from openpilot.common.hardware import COMMA_HARDWARE from openpilot.system.manager.process_config import managed_processes from openpilot.tools.lib.logreader import LogReader from openpilot.common.hardware.hw import Paths @@ -33,7 +33,7 @@ FILE_SIZE_TOLERANCE = 0.7 class TestEncoder(OpenpilotTestCase): - TICI_TEST = True + COMMA_HARDWARE_TEST = True def setup_method(self): self._clear_logs() @@ -86,7 +86,7 @@ class TestEncoder(OpenpilotTestCase): # TODO: this ffprobe call is really slow # get width and check frame count cmd = f"ffprobe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets,width -of csv=p=0 {file_path}" - if TICI: + if COMMA_HARDWARE: cmd = "LD_LIBRARY_PATH=/usr/local/lib " + cmd expected_frames = fps * SEGMENT_LENGTH @@ -130,7 +130,7 @@ class TestEncoder(OpenpilotTestCase): assert 1 == len(set(first_frames)) - if TICI: + if COMMA_HARDWARE: expected_frames = fps * SEGMENT_LENGTH assert min(counts) == expected_frames shutil.rmtree(f"{route_prefix_path}--{i}") diff --git a/openpilot/system/loggerd/tests/test_loggerd.py b/openpilot/system/loggerd/tests/test_loggerd.py index 618e3d0016..039bb17064 100644 --- a/openpilot/system/loggerd/tests/test_loggerd.py +++ b/openpilot/system/loggerd/tests/test_loggerd.py @@ -18,7 +18,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.timeout import Timeout from openpilot.common.hardware.hw import Paths -from openpilot.common.hardware import TICI +from openpilot.common.hardware import COMMA_HARDWARE from openpilot.system.loggerd.xattr_cache import getxattr from openpilot.system.loggerd.deleter import PRESERVE_ATTR_NAME, PRESERVE_ATTR_VALUE from openpilot.system.manager.process_config import managed_processes @@ -234,7 +234,7 @@ class TestLoggerd(OpenpilotTestCase): assert abs(boot.wallTimeNanos - time.time_ns()) < 5*1e9 # within 5s assert boot.launchLog == launch_log - if TICI: + if COMMA_HARDWARE: for fn in ["console-ramoops", "pmsg-ramoops-0"]: path = Path(os.path.join("/sys/fs/pstore/", fn)) if path.is_file(): diff --git a/openpilot/system/manager/process_config.py b/openpilot/system/manager/process_config.py index a4326b4107..32d8508696 100644 --- a/openpilot/system/manager/process_config.py +++ b/openpilot/system/manager/process_config.py @@ -4,7 +4,7 @@ import platform from opendbc.car.structs import car from openpilot.common.params import Params -from openpilot.common.hardware import PC, TICI +from openpilot.common.hardware import PC, COMMA_HARDWARE from openpilot.system.manager.process import PythonProcess, NativeProcess, DaemonProcess WEBCAM = os.getenv("USE_WEBCAM") is not None @@ -101,18 +101,18 @@ procs = [ PythonProcess("card", "openpilot.selfdrive.car.card", only_onroad), PythonProcess("deleter", "openpilot.system.loggerd.deleter", always_run), PythonProcess("dmonitoringd", "openpilot.selfdrive.monitoring.dmonitoringd", driverview, enabled=(WEBCAM or not PC)), - PythonProcess("qcomgpsd", "openpilot.system.qcomgpsd.qcomgpsd", qcomgps, enabled=TICI), + PythonProcess("qcomgpsd", "openpilot.system.qcomgpsd.qcomgpsd", qcomgps, enabled=COMMA_HARDWARE), PythonProcess("pandad", "openpilot.selfdrive.pandad.pandad", always_run), PythonProcess("paramsd", "openpilot.selfdrive.locationd.paramsd", only_onroad), PythonProcess("lagd", "openpilot.selfdrive.locationd.lagd", only_onroad), - PythonProcess("ubloxd", "openpilot.system.ubloxd.ubloxd", ublox, enabled=TICI), - PythonProcess("pigeond", "openpilot.system.ubloxd.pigeond", ublox, enabled=TICI), + PythonProcess("ubloxd", "openpilot.system.ubloxd.ubloxd", ublox, enabled=COMMA_HARDWARE), + PythonProcess("pigeond", "openpilot.system.ubloxd.pigeond", ublox, enabled=COMMA_HARDWARE), PythonProcess("plannerd", "openpilot.selfdrive.controls.plannerd", not_long_maneuver), PythonProcess("maneuversd", "openpilot.tools.longitudinal_maneuvers.maneuversd", long_maneuver), PythonProcess("lateral_maneuversd", "openpilot.tools.lateral_maneuvers.lateral_maneuversd", lat_maneuver), PythonProcess("radard", "openpilot.selfdrive.controls.radard", only_onroad), PythonProcess("hardwared", "openpilot.system.hardware.hardwared", always_run), - PythonProcess("modem", "openpilot.common.hardware.comma.modem", always_run, enabled=TICI), + PythonProcess("modem", "openpilot.common.hardware.comma.modem", always_run, enabled=COMMA_HARDWARE), PythonProcess("tombstoned", "openpilot.system.tombstoned", always_run, enabled=not PC), PythonProcess("updated", "openpilot.system.updated.updated", only_offroad, enabled=not PC), PythonProcess("uploader", "openpilot.system.loggerd.uploader", always_run), diff --git a/openpilot/system/sensord/tests/test_sensord.py b/openpilot/system/sensord/tests/test_sensord.py index e1580af9bb..ddbef7895b 100755 --- a/openpilot/system/sensord/tests/test_sensord.py +++ b/openpilot/system/sensord/tests/test_sensord.py @@ -58,7 +58,7 @@ def iter_measurements(events): yield measurement, getattr(measurement, measurement.which()) class TestSensord(OpenpilotTestCase): - TICI_TEST = True + COMMA_HARDWARE_TEST = True @classmethod def setup_class(cls): # enable LSM self test diff --git a/openpilot/system/ubloxd/pigeond.py b/openpilot/system/ubloxd/pigeond.py index f704761e35..58fba4d4e1 100755 --- a/openpilot/system/ubloxd/pigeond.py +++ b/openpilot/system/ubloxd/pigeond.py @@ -12,7 +12,7 @@ from openpilot.common.time_helpers import system_time_valid from openpilot.common.params import Params from openpilot.common.serial import Serial from openpilot.common.swaglog import cloudlog -from openpilot.common.hardware import TICI +from openpilot.common.hardware import COMMA_HARDWARE from openpilot.common.gpio import gpio_init, gpio_set from openpilot.common.hardware.comma.pins import GPIO @@ -302,7 +302,7 @@ def run_receiving(duration: int = 0): def main(): - assert TICI, "unsupported hardware for pigeond" + assert COMMA_HARDWARE, "unsupported hardware for pigeond" run_receiving() if __name__ == "__main__": diff --git a/openpilot/system/ubloxd/tests/test_pigeond.py b/openpilot/system/ubloxd/tests/test_pigeond.py index 87e3f18523..0d2f5b00e7 100644 --- a/openpilot/system/ubloxd/tests/test_pigeond.py +++ b/openpilot/system/ubloxd/tests/test_pigeond.py @@ -11,7 +11,7 @@ from openpilot.common.hardware.comma.pins import GPIO # TODO: test TTFF when we have good A-GNSS class TestPigeond(OpenpilotTestCase): - TICI_TEST = True + COMMA_HARDWARE_TEST = True def teardown_method(self): managed_processes['pigeond'].stop() diff --git a/openpilot/system/ui/lib/scroll_panel2.py b/openpilot/system/ui/lib/scroll_panel2.py index b6193672c4..19faaa542d 100644 --- a/openpilot/system/ui/lib/scroll_panel2.py +++ b/openpilot/system/ui/lib/scroll_panel2.py @@ -5,7 +5,7 @@ from collections.abc import Callable from enum import Enum from typing import cast from openpilot.system.ui.lib.application import gui_app, MouseEvent -from openpilot.common.hardware import TICI +from openpilot.common.hardware import COMMA_HARDWARE from collections import deque MIN_VELOCITY = 10 # px/s, changes from auto scroll to steady state @@ -52,7 +52,7 @@ class GuiScrollPanel2: self._initial_click_event: MouseEvent | None = None self._previous_mouse_event: MouseEvent | None = None self._velocity = 0.0 # pixels per second - self._velocity_buffer: deque[float] = deque(maxlen=12 if TICI else 6) + self._velocity_buffer: deque[float] = deque(maxlen=12 if COMMA_HARDWARE else 6) self._enabled: bool | Callable[[], bool] = True def set_enabled(self, enabled: bool | Callable[[], bool]) -> None: diff --git a/openpilot/system/ui/mici_setup.py b/openpilot/system/ui/mici_setup.py index 465cadda0b..e4977ffdf4 100755 --- a/openpilot/system/ui/mici_setup.py +++ b/openpilot/system/ui/mici_setup.py @@ -13,7 +13,7 @@ import pyray as rl from openpilot.cereal import log from openpilot.common.filter_simple import BounceFilter -from openpilot.common.hardware import HARDWARE, TICI +from openpilot.common.hardware import HARDWARE, COMMA_HARDWARE from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.common.swaglog import cloudlog from openpilot.common.time_helpers import system_time_valid @@ -570,7 +570,7 @@ class Setup(Widget): def main(): config_realtime_process(0, 51) # attempt to affine. AGNOS will start setup with all cores, should only fail when manually launching with screen off - if TICI: + if COMMA_HARDWARE: try: set_core_affinity([5]) except OSError: diff --git a/openpilot/system/ui/mici_updater.py b/openpilot/system/ui/mici_updater.py index a072354cf0..d9009b8259 100755 --- a/openpilot/system/ui/mici_updater.py +++ b/openpilot/system/ui/mici_updater.py @@ -5,7 +5,7 @@ import threading import pyray as rl from openpilot.common.realtime import config_realtime_process, set_core_affinity -from openpilot.common.hardware import HARDWARE, TICI +from openpilot.common.hardware import HARDWARE, COMMA_HARDWARE from openpilot.common.swaglog import cloudlog from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets.nav_widget import NavWidget @@ -152,7 +152,7 @@ class Updater(Scroller): def main(): config_realtime_process(0, 51) # attempt to affine. AGNOS will start setup with all cores, should only fail when manually launching with screen off - if TICI: + if COMMA_HARDWARE: try: set_core_affinity([5]) except OSError: From e32b1a344ddf746c28985784ddf7b4d118d7154d Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Fri, 7 Aug 2026 20:16:13 -0700 Subject: [PATCH 140/170] chestnut: update firmware (#38576) * chestnut: update firmware to b0805491 * update to ed4e39b7 --- openpilot/common/hardware/usb.py | 2 +- .../hardware/chestnut/firmware_wrapped.bin | Bin 9207 -> 9207 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/openpilot/common/hardware/usb.py b/openpilot/common/hardware/usb.py index e06e77b31e..1ebcf571b5 100644 --- a/openpilot/common/hardware/usb.py +++ b/openpilot/common/hardware/usb.py @@ -1,7 +1,7 @@ import os from pathlib import Path -CHESTNUT_FW_VERSION = "1d368808" +CHESTNUT_FW_VERSION = "ed4e39b7" CHESTNUT_USB_IDS = ((0xADD1, 0x0001), (0x3801, 0x0001)) CHESTNUT_ROM_USB_IDS = ((0x174C, 0x2464), (0x174C, 0x2463)) USB_DEVICES_PATH = Path("/sys/bus/usb/devices") diff --git a/openpilot/system/hardware/chestnut/firmware_wrapped.bin b/openpilot/system/hardware/chestnut/firmware_wrapped.bin index a0911e06e23c4843d3ed07592ebaf5ea7a707669..1d738e4a759d05a8905e6ddf1b5d5ee6a324b04d 100644 GIT binary patch delta 34 qcmezF{@s1U9X3XW&3D Date: Fri, 7 Aug 2026 21:08:33 -0700 Subject: [PATCH 141/170] AGNOS 19.5 (#38582) * bump version * add staging * add production * fix AGNOS manifest symlink --- launch_env.sh | 2 +- openpilot/common/hardware/comma/agnos.json | 22 +++++++++++----------- openpilot/system/hardware/comma/agnos.json | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index ce7b2bb15e..a201fd6c47 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="19.4" + export AGNOS_VERSION="19.5" fi export STAGING_ROOT="/data/safe_staging" diff --git a/openpilot/common/hardware/comma/agnos.json b/openpilot/common/hardware/comma/agnos.json index eb50d4efd6..3a8d03e104 100644 --- a/openpilot/common/hardware/comma/agnos.json +++ b/openpilot/common/hardware/comma/agnos.json @@ -56,29 +56,29 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-5463317ca7a231f2ddd55a667a8c2b0d6692982359773ca875a99a0e6fd67fb4.img.xz", - "hash": "5463317ca7a231f2ddd55a667a8c2b0d6692982359773ca875a99a0e6fd67fb4", - "hash_raw": "5463317ca7a231f2ddd55a667a8c2b0d6692982359773ca875a99a0e6fd67fb4", + "url": "https://commadist.azureedge.net/agnosupdate/boot-f716b81d557c274be707abc200276ba9e8320ad0386586f36f9065dd563fee94.img.xz", + "hash": "f716b81d557c274be707abc200276ba9e8320ad0386586f36f9065dd563fee94", + "hash_raw": "f716b81d557c274be707abc200276ba9e8320ad0386586f36f9065dd563fee94", "size": 46897152, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "daefa6f897bdcc277e50dbf562fd63e57d50d9fc7a599f2d171208a490e725d9" + "ondevice_hash": "269bc216189c4532a3e796ef2f4d47d0a03e774c883203be93feb8a8ed3c3adb" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-2e1ca22762e66898aae384fcc21f488cb015c020b6b588862a64fc8128f0ad1d.img.xz", - "hash": "14ab660bb955604ab6c482b3f59e5239e2c40c54e798201afcaed43d5cb0dcfd", - "hash_raw": "2e1ca22762e66898aae384fcc21f488cb015c020b6b588862a64fc8128f0ad1d", + "url": "https://commadist.azureedge.net/agnosupdate/system-7b3da36853ee71f7f43ff9eb0f59ca8c4e80a2ba96a9a4911731641a5d008ae2.img.xz", + "hash": "414a9145d6ba13bc04760b31545507109e9a2509958dd44782dd1912b1cb96e9", + "hash_raw": "7b3da36853ee71f7f43ff9eb0f59ca8c4e80a2ba96a9a4911731641a5d008ae2", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "18666ca3b529304f5e7a8fc2e3c9dce715b55941e3965a7af43c613f8e18cfce", + "ondevice_hash": "d4fadd9dfb4e20875650e60e37b9f8ccf2dc6c0fdc4f9a15912bee5ab8440a13", "alt": { - "hash": "2e1ca22762e66898aae384fcc21f488cb015c020b6b588862a64fc8128f0ad1d", - "url": "https://commadist.azureedge.net/agnosupdate/system-2e1ca22762e66898aae384fcc21f488cb015c020b6b588862a64fc8128f0ad1d.img", + "hash": "7b3da36853ee71f7f43ff9eb0f59ca8c4e80a2ba96a9a4911731641a5d008ae2", + "url": "https://commadist.azureedge.net/agnosupdate/system-7b3da36853ee71f7f43ff9eb0f59ca8c4e80a2ba96a9a4911731641a5d008ae2.img", "size": 4718592000 } } -] \ No newline at end of file +] diff --git a/openpilot/system/hardware/comma/agnos.json b/openpilot/system/hardware/comma/agnos.json index ffee992f25..b1465ff49d 120000 --- a/openpilot/system/hardware/comma/agnos.json +++ b/openpilot/system/hardware/comma/agnos.json @@ -1 +1 @@ -../../../common/hardware/tici/agnos.json \ No newline at end of file +../../../common/hardware/comma/agnos.json \ No newline at end of file From 5701fcad05e8f88ed7eae5902cb791b66ab755b2 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Fri, 7 Aug 2026 21:11:34 -0700 Subject: [PATCH 142/170] ui: show gpu icon longer (#38586) --- openpilot/selfdrive/ui/mici/onroad/hud_renderer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpilot/selfdrive/ui/mici/onroad/hud_renderer.py b/openpilot/selfdrive/ui/mici/onroad/hud_renderer.py index d4fe668294..0d1532057e 100644 --- a/openpilot/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/openpilot/selfdrive/ui/mici/onroad/hud_renderer.py @@ -168,8 +168,8 @@ class HudRenderer(Widget): if (engaged and not self._engaged and not ui_state.usbgpu_loading and ui_state.usbgpu_active is not True and ui_state.sm.recv_frame['modelV2'] > ui_state.started_frame): self._small_model_engaged = True - if engaged and not self._engaged: - self._egpu_fade_time = rl.get_time() + if engaged != self._engaged: + self._egpu_fade_time = rl.get_time() if engaged else 0 if (set_speed != self.set_speed and engaged) or (engaged and not self._engaged): self._set_speed_changed_time = rl.get_time() self._engaged = engaged @@ -223,7 +223,7 @@ class HudRenderer(Widget): if icon is not self._egpu_icon: self._egpu_fade_time = rl.get_time() self._egpu_icon = icon - alpha = self._egpu_alpha_filter.update(loading or (0 < rl.get_time() - self._egpu_fade_time < SET_SPEED_PERSISTENCE and self._engaged)) + alpha = self._egpu_alpha_filter.update(loading or 0 < rl.get_time() - self._egpu_fade_time < SET_SPEED_PERSISTENCE) if alpha < 1e-2: return From cf790746ccf11bd40116f7a25c70f799f5068d3d Mon Sep 17 00:00:00 2001 From: stef <19478336+stefpi@users.noreply.github.com> Date: Fri, 7 Aug 2026 23:20:17 -0700 Subject: [PATCH 143/170] encoder/webrtc: resize streams to baseline 3.1 H264 profile (#38587) * add h264 profile option and different profiles for comma3X and comma4 * update to high profile * resize to be baseline 3.1 * fix * gop 15 and clean * revert gop 15 * fix white space * simplify teleoprtc * bump teleop --- .../system/loggerd/encoder/v4l_encoder.cc | 26 ++++++++++++++++--- openpilot/system/loggerd/loggerd.h | 22 ++++++++++++++++ teleoprtc_repo | 2 +- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/openpilot/system/loggerd/encoder/v4l_encoder.cc b/openpilot/system/loggerd/encoder/v4l_encoder.cc index 515468c643..0d7e744568 100644 --- a/openpilot/system/loggerd/encoder/v4l_encoder.cc +++ b/openpilot/system/loggerd/encoder/v4l_encoder.cc @@ -251,11 +251,29 @@ V4LEncoder::V4LEncoder(const EncoderInfo &encoder_info, int in_width, int in_hei util::safe_ioctl(fd, VIDIOC_S_CTRL, &ctrl, "VIDIOC_S_CTRL failed"); } } else { + if (encoder_info.is_live) { + struct v4l2_control ctrls[] = { + { .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, .value = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH}, + { .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, .value = V4L2_MPEG_VIDEO_H264_LEVEL_3_1}, + { .id = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, .value = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC}, + { .id = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL, .value = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_0}, + }; + for (auto ctrl : ctrls) { + util::safe_ioctl(fd, VIDIOC_S_CTRL, &ctrl, "VIDIOC_S_CTRL failed"); + } + } else { + struct v4l2_control ctrls[] = { + { .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, .value = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH}, + { .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, .value = V4L2_MPEG_VIDEO_H264_LEVEL_UNKNOWN}, + { .id = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, .value = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC}, + { .id = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL, .value = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_0}, + }; + for (auto ctrl : ctrls) { + util::safe_ioctl(fd, VIDIOC_S_CTRL, &ctrl, "VIDIOC_S_CTRL failed"); + } + } + struct v4l2_control ctrls[] = { - { .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, .value = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH}, - { .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, .value = V4L2_MPEG_VIDEO_H264_LEVEL_UNKNOWN}, - { .id = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, .value = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC}, - { .id = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL, .value = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_0}, { .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE, .value = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED}, { .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA, .value = 0}, { .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA, .value = 0}, diff --git a/openpilot/system/loggerd/loggerd.h b/openpilot/system/loggerd/loggerd.h index 110dbe4fd2..4d33a77095 100644 --- a/openpilot/system/loggerd/loggerd.h +++ b/openpilot/system/loggerd/loggerd.h @@ -25,6 +25,22 @@ const auto MAIN_ENCODE_TYPE = Hardware::PC() ? cereal::EncodeIndex::Type::BIG_BO const bool LOGGERD_TEST = getenv("LOGGERD_TEST"); const int SEGMENT_LENGTH = LOGGERD_TEST ? atoi(getenv("LOGGERD_SEGMENT_LENGTH")) : 60; +inline int livestream_width() { + switch (Hardware::get_device_type()) { + case cereal::InitData::DeviceType::TIZI: return 1152; + case cereal::InitData::DeviceType::MICI: return 1280; + default: return -1; + } +} + +inline int livestream_height() { + switch (Hardware::get_device_type()) { + case cereal::InitData::DeviceType::TIZI: + case cereal::InitData::DeviceType::MICI: return 720; + default: return -1; + } +} + constexpr char PRESERVE_ATTR_NAME[] = "user.preserve"; constexpr char PRESERVE_ATTR_VALUE = '1'; @@ -106,6 +122,8 @@ const EncoderInfo stream_road_encoder_info = { //.thumbnail_name = "thumbnail", .record = false, .is_live = true, + .frame_width = livestream_width(), + .frame_height = livestream_height(), .get_settings = [](int){return EncoderSettings::StreamEncoderSettings();}, INIT_ENCODE_FUNCTIONS(LivestreamRoadEncode), }; @@ -114,6 +132,8 @@ const EncoderInfo stream_wide_road_encoder_info = { .publish_name = "livestreamWideRoadEncodeData", .record = false, .is_live = true, + .frame_width = livestream_width(), + .frame_height = livestream_height(), .get_settings = [](int){return EncoderSettings::StreamEncoderSettings();}, INIT_ENCODE_FUNCTIONS(LivestreamWideRoadEncode), }; @@ -122,6 +142,8 @@ const EncoderInfo stream_driver_encoder_info = { .publish_name = "livestreamDriverEncodeData", .record = false, .is_live = true, + .frame_width = livestream_width(), + .frame_height = livestream_height(), .get_settings = [](int){return EncoderSettings::StreamEncoderSettings();}, INIT_ENCODE_FUNCTIONS(LivestreamDriverEncode), }; diff --git a/teleoprtc_repo b/teleoprtc_repo index 15ecd3b62b..31db236a9e 160000 --- a/teleoprtc_repo +++ b/teleoprtc_repo @@ -1 +1 @@ -Subproject commit 15ecd3b62ba6c29679e2f6ff44aa7845637f82f8 +Subproject commit 31db236a9ef820d7051ccd53488153cfbc84d3b9 From df0bd5e6e3702ddd8b64086a820407468972ad91 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 8 Aug 2026 08:51:58 -0700 Subject: [PATCH 144/170] update translations (#38590) --- openpilot/selfdrive/ui/translations/app.pot | 26 ++---- openpilot/selfdrive/ui/translations/app_de.po | 90 +++++++++--------- openpilot/selfdrive/ui/translations/app_en.po | 90 +++++++++--------- openpilot/selfdrive/ui/translations/app_es.po | 90 +++++++++--------- openpilot/selfdrive/ui/translations/app_fr.po | 90 +++++++++--------- openpilot/selfdrive/ui/translations/app_ja.po | 89 +++++++++--------- openpilot/selfdrive/ui/translations/app_ko.po | 89 +++++++++--------- .../selfdrive/ui/translations/app_pt-BR.po | 90 +++++++++--------- openpilot/selfdrive/ui/translations/app_th.po | 89 +++++++++--------- openpilot/selfdrive/ui/translations/app_tr.po | 90 +++++++++--------- openpilot/selfdrive/ui/translations/app_uk.po | 91 +++++++++---------- .../selfdrive/ui/translations/app_zh-CHS.po | 90 +++++++++--------- .../selfdrive/ui/translations/app_zh-CHT.po | 90 +++++++++--------- 13 files changed, 514 insertions(+), 590 deletions(-) diff --git a/openpilot/selfdrive/ui/translations/app.pot b/openpilot/selfdrive/ui/translations/app.pot index 3f9f86af8e..cb0491e107 100644 --- a/openpilot/selfdrive/ui/translations/app.pot +++ b/openpilot/selfdrive/ui/translations/app.pot @@ -2,7 +2,6 @@ msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" -#: openpilot/selfdrive/ui/layouts/sidebar.py #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/html_render.py msgid "OK" @@ -162,8 +161,9 @@ msgstr "" msgid "Open" msgstr "" +#: openpilot/selfdrive/ui/layouts/settings/firehose.py #: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" +msgid "Firehose Mode" msgstr "" #: openpilot/selfdrive/ui/widgets/setup.py @@ -364,6 +364,10 @@ msgstr "" msgid "Unknown" msgstr "" +#: openpilot/selfdrive/ui/layouts/sidebar.py +msgid "HIGH" +msgstr "" + #: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" msgstr "" @@ -372,10 +376,6 @@ msgstr "" msgid "PANDA" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py -msgid "HIGH" -msgstr "" - #: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" msgstr "" @@ -528,16 +528,6 @@ msgstr "" msgid " Steering torque response calibration is {}% complete." msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "Firehose Mode" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -msgstr[1] "" - #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" msgstr "" @@ -558,6 +548,10 @@ msgstr "" msgid "Longitudinal Maneuver Mode" msgstr "" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" msgstr "" diff --git a/openpilot/selfdrive/ui/translations/app_de.po b/openpilot/selfdrive/ui/translations/app_de.po index 287ecde1a0..43baa133ae 100644 --- a/openpilot/selfdrive/ui/translations/app_de.po +++ b/openpilot/selfdrive/ui/translations/app_de.po @@ -52,7 +52,7 @@ msgstr "

    Kalibrierung der Lenkverzögerung zu {}% abgeschlossen." msgid "ADD" msgstr "HINZUFÜGEN" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "APN Setting" msgstr "APN‑Einstellung" @@ -60,7 +60,7 @@ msgstr "APN‑Einstellung" msgid "Acknowledge Excessive Actuation" msgstr "Übermäßige Betätigung bestätigen" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Advanced" msgstr "Erweitert" @@ -93,7 +93,7 @@ msgid "Are you sure you want to uninstall?" msgstr "Sind Sie sicher, dass Sie deinstallieren möchten?" #: openpilot/selfdrive/ui/layouts/onboarding.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Back" msgstr "Zurück" @@ -118,22 +118,22 @@ msgid "CHILL MODE ON" msgstr "CHILL‑MODUS AKTIV" #: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECT" msgstr "VERBINDUNG" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECTING..." msgstr "VERBINDUNG" -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/keyboard.py -#: system/ui/widgets/network.py -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/keyboard.py +#: openpilot/system/ui/widgets/network.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "Abbrechen" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Cellular Metered" msgstr "Getaktete Mobilfunkverbindung" @@ -213,7 +213,7 @@ msgstr "Fahrerkamera" msgid "Driving Personality" msgstr "Fahrstil" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "EDIT" msgstr "BEARBEITEN" @@ -242,7 +242,7 @@ msgstr "ADB aktivieren" msgid "Enable Lane Departure Warnings" msgstr "Spurverlassenswarnungen aktivieren" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Roaming" msgstr "Roaming aktivieren" @@ -250,7 +250,7 @@ msgstr "Roaming aktivieren" msgid "Enable SSH" msgstr "SSH aktivieren" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Tethering" msgstr "Tethering aktivieren" @@ -266,19 +266,19 @@ msgstr "openpilot aktivieren" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Den Schalter für die openpilot-Längsregelung (Alpha) aktivieren, um den Experimentalmodus zu erlauben." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter APN" msgstr "APN eingeben" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter SSID" msgstr "SSID eingeben" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "Neues Tethering‑Passwort eingeben" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter password" msgstr "Passwort eingeben" @@ -286,7 +286,7 @@ msgstr "Passwort eingeben" msgid "Enter your GitHub username" msgstr "Geben Sie Ihren GitHub‑Benutzernamen ein" -#: system/ui/widgets/list_view.py +#: openpilot/system/ui/widgets/list_view.py msgid "Error" msgstr "Fehler" @@ -298,7 +298,7 @@ msgstr "Experimentalmodus" msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "Der Experimentalmodus ist derzeit auf diesem Fahrzeug nicht verfügbar, da der serienmäßige ACC für die Längsregelung verwendet wird." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "FORGETTING..." msgstr "WIRD VERGESSEN..." @@ -311,14 +311,15 @@ msgid "Firehose" msgstr "Datenstrom" #: openpilot/selfdrive/ui/layouts/settings/firehose.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Firehose Mode" msgstr "Firehose‑Modus" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget" msgstr "Vergessen" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "WLAN‑Netz „{}“ vergessen?" @@ -334,7 +335,7 @@ msgstr "Gehen Sie auf Ihrem Telefon zu https://connect.comma.ai" msgid "HIGH" msgstr "HOCH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Hidden Network" msgstr "Verstecktes Netzwerk" @@ -342,7 +343,7 @@ msgstr "Verstecktes Netzwerk" msgid "INSTALL" msgstr "INSTALLIEREN" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "IP Address" msgstr "IP‑Adresse" @@ -362,6 +363,10 @@ msgstr "LADEN" msgid "LTE" msgstr "LTE" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Längsmanövermodus" @@ -398,9 +403,8 @@ msgstr "Keine Versionshinweise verfügbar." msgid "OFFLINE" msgstr "GETRENNT" -#: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/html_render.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/html_render.py msgid "OK" msgstr "OK" @@ -453,11 +457,11 @@ msgstr "Bitte mit WLAN verbinden, um das erste Koppeln abzuschließen" msgid "Power Off" msgstr "Ausschalten" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Verhindern Sie das Hochladen großer Datenmengen bei einer getakteten WLAN-Verbindung" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Verhindern Sie das Hochladen großer Datenmengen bei einer getakteten Mobilfunkverbindung" @@ -549,11 +553,11 @@ msgstr "WÄHLEN" msgid "SSH Keys" msgstr "SSH‑Schlüssel" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "WLAN‑Netzwerke werden gesucht..." -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Select" msgstr "Auswählen" @@ -597,7 +601,7 @@ msgstr "TEMP." msgid "Target Branch" msgstr "Zielzweig" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Tethering Password" msgstr "Tethering‑Passwort" @@ -665,11 +669,11 @@ msgstr "Wenn aktiviert, deaktiviert das Drücken des Gaspedals openpilot." msgid "Wi-Fi" msgstr "WLAN" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Getaktetes WLAN‑Netzwerk" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wrong password" msgstr "Falsches Passwort" @@ -693,7 +697,7 @@ msgstr "Überprüfung..." msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "default" msgstr "Standard" @@ -713,7 +717,7 @@ msgstr "Überprüfung auf Updates fehlgeschlagen" msgid "finalizing update..." msgstr "Update wird finalisiert..." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "for \"{}\"" msgstr "für „{}“" @@ -721,7 +725,7 @@ msgstr "für „{}“" msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "für automatische Konfiguration leer lassen" @@ -729,7 +733,7 @@ msgstr "für automatische Konfiguration leer lassen" msgid "left" msgstr "links" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "metered" msgstr "getaktet" @@ -765,7 +769,7 @@ msgstr "openpilot erfordert, dass das Gerät innerhalb von 4° nach links oder r msgid "right" msgstr "rechts" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "unmetered" msgstr "unbegrenzt" @@ -809,17 +813,7 @@ msgid_plural "{} minutes ago" msgstr[0] "vor {} Minute" msgstr[1] "vor {} Minuten" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "{} Segment Ihrer Fahrten ist bisher im Trainingsdatensatz." -msgstr[1] "{} Segmente Ihrer Fahrten sind bisher im Trainingsdatensatz." - #: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ ABONNIERT" -#: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 Firehose‑Modus 🔥" - diff --git a/openpilot/selfdrive/ui/translations/app_en.po b/openpilot/selfdrive/ui/translations/app_en.po index 9f99c42b11..3e578de6a4 100644 --- a/openpilot/selfdrive/ui/translations/app_en.po +++ b/openpilot/selfdrive/ui/translations/app_en.po @@ -52,7 +52,7 @@ msgstr "

    Steering lag calibration is {}% complete." msgid "ADD" msgstr "ADD" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "APN Setting" msgstr "APN Setting" @@ -60,7 +60,7 @@ msgstr "APN Setting" msgid "Acknowledge Excessive Actuation" msgstr "Acknowledge Excessive Actuation" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Advanced" msgstr "Advanced" @@ -93,7 +93,7 @@ msgid "Are you sure you want to uninstall?" msgstr "Are you sure you want to uninstall?" #: openpilot/selfdrive/ui/layouts/onboarding.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Back" msgstr "Back" @@ -118,22 +118,22 @@ msgid "CHILL MODE ON" msgstr "CHILL MODE ON" #: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECTING..." msgstr "CONNECTING..." -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/keyboard.py -#: system/ui/widgets/network.py -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/keyboard.py +#: openpilot/system/ui/widgets/network.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "Cancel" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Cellular Metered" msgstr "Cellular Metered" @@ -213,7 +213,7 @@ msgstr "Driver Camera" msgid "Driving Personality" msgstr "Driving Personality" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "EDIT" msgstr "EDIT" @@ -242,7 +242,7 @@ msgstr "Enable ADB" msgid "Enable Lane Departure Warnings" msgstr "Enable Lane Departure Warnings" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Roaming" msgstr "Enable Roaming" @@ -250,7 +250,7 @@ msgstr "Enable Roaming" msgid "Enable SSH" msgstr "Enable SSH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Tethering" msgstr "Enable Tethering" @@ -266,19 +266,19 @@ msgstr "Enable openpilot" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter APN" msgstr "Enter APN" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter SSID" msgstr "Enter SSID" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "Enter new tethering password" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter password" msgstr "Enter password" @@ -286,7 +286,7 @@ msgstr "Enter password" msgid "Enter your GitHub username" msgstr "Enter your GitHub username" -#: system/ui/widgets/list_view.py +#: openpilot/system/ui/widgets/list_view.py msgid "Error" msgstr "Error" @@ -298,7 +298,7 @@ msgstr "Experimental Mode" msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "FORGETTING..." msgstr "FORGETTING..." @@ -311,14 +311,15 @@ msgid "Firehose" msgstr "Firehose" #: openpilot/selfdrive/ui/layouts/settings/firehose.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Firehose Mode" msgstr "Firehose Mode" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget" msgstr "Forget" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Forget Wi-Fi Network \"{}\"?" @@ -334,7 +335,7 @@ msgstr "Go to https://connect.comma.ai on your phone" msgid "HIGH" msgstr "HIGH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Hidden Network" msgstr "Hidden Network" @@ -342,7 +343,7 @@ msgstr "Hidden Network" msgid "INSTALL" msgstr "INSTALL" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "IP Address" msgstr "IP Address" @@ -362,6 +363,10 @@ msgstr "LOADING" msgid "LTE" msgstr "LTE" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Longitudinal Maneuver Mode" @@ -398,9 +403,8 @@ msgstr "No release notes available." msgid "OFFLINE" msgstr "OFFLINE" -#: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/html_render.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/html_render.py msgid "OK" msgstr "OK" @@ -453,11 +457,11 @@ msgstr "Please connect to Wi-Fi to complete initial pairing" msgid "Power Off" msgstr "Power Off" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Prevent large data uploads when on a metered Wi-Fi connection" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Prevent large data uploads when on a metered cellular connection" @@ -549,11 +553,11 @@ msgstr "SELECT" msgid "SSH Keys" msgstr "SSH Keys" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Scanning Wi-Fi networks..." -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Select" msgstr "Select" @@ -597,7 +601,7 @@ msgstr "TEMP" msgid "Target Branch" msgstr "Target Branch" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Tethering Password" msgstr "Tethering Password" @@ -665,11 +669,11 @@ msgstr "When enabled, pressing the accelerator pedal will disengage openpilot." msgid "Wi-Fi" msgstr "Wi-Fi" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Wi-Fi Network Metered" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wrong password" msgstr "Wrong password" @@ -693,7 +697,7 @@ msgstr "checking..." msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "default" msgstr "default" @@ -713,7 +717,7 @@ msgstr "failed to check for update" msgid "finalizing update..." msgstr "finalizing update..." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "for \"{}\"" msgstr "for \"{}\"" @@ -721,7 +725,7 @@ msgstr "for \"{}\"" msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "leave blank for automatic configuration" @@ -729,7 +733,7 @@ msgstr "leave blank for automatic configuration" msgid "left" msgstr "left" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "metered" msgstr "metered" @@ -765,7 +769,7 @@ msgstr "openpilot requires the device to be mounted within 4° left or right and msgid "right" msgstr "right" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "unmetered" msgstr "unmetered" @@ -809,17 +813,7 @@ msgid_plural "{} minutes ago" msgstr[0] "{} minute ago" msgstr[1] "{} minutes ago" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "{} segment of your driving is in the training dataset so far." -msgstr[1] "{} segments of your driving is in the training dataset so far." - #: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ SUBSCRIBED" -#: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 Firehose Mode 🔥" - diff --git a/openpilot/selfdrive/ui/translations/app_es.po b/openpilot/selfdrive/ui/translations/app_es.po index 707816bc00..e1c74f53c2 100644 --- a/openpilot/selfdrive/ui/translations/app_es.po +++ b/openpilot/selfdrive/ui/translations/app_es.po @@ -52,7 +52,7 @@ msgstr "

    La calibración del retraso de dirección está completa en un { msgid "ADD" msgstr "AÑADIR" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "APN Setting" msgstr "Configuración de APN" @@ -60,7 +60,7 @@ msgstr "Configuración de APN" msgid "Acknowledge Excessive Actuation" msgstr "Reconocer actuación excesiva" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Advanced" msgstr "Avanzado" @@ -93,7 +93,7 @@ msgid "Are you sure you want to uninstall?" msgstr "¿Seguro que quieres desinstalar?" #: openpilot/selfdrive/ui/layouts/onboarding.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Back" msgstr "Atrás" @@ -118,22 +118,22 @@ msgid "CHILL MODE ON" msgstr "MODO CHILL ACTIVADO" #: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECT" msgstr "CONECTAR" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECTING..." msgstr "CONECTAR" -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/keyboard.py -#: system/ui/widgets/network.py -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/keyboard.py +#: openpilot/system/ui/widgets/network.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "Cancelar" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Cellular Metered" msgstr "Medición celular" @@ -213,7 +213,7 @@ msgstr "Cámara del conductor" msgid "Driving Personality" msgstr "Estilo de conducción" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "EDIT" msgstr "EDITAR" @@ -242,7 +242,7 @@ msgstr "Activar ADB" msgid "Enable Lane Departure Warnings" msgstr "Activar advertencias de salida de carril" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Roaming" msgstr "Activar roaming" @@ -250,7 +250,7 @@ msgstr "Activar roaming" msgid "Enable SSH" msgstr "Activar SSH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Tethering" msgstr "Activar anclaje" @@ -266,19 +266,19 @@ msgstr "Activar openpilot" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Activa el interruptor de control longitudinal de openpilot (alpha) para permitir el modo Experimental." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter APN" msgstr "Introduce APN" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter SSID" msgstr "Introduzca SSID" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "Ingrese una nueva contraseña de anclaje a red" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter password" msgstr "Introduce la contraseña" @@ -286,7 +286,7 @@ msgstr "Introduce la contraseña" msgid "Enter your GitHub username" msgstr "Introduce tu nombre de usuario de GitHub" -#: system/ui/widgets/list_view.py +#: openpilot/system/ui/widgets/list_view.py msgid "Error" msgstr "Fallo" @@ -298,7 +298,7 @@ msgstr "Modo experimental" msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "El modo experimental no está disponible actualmente en este coche, ya que se usa el ACC de fábrica para el control longitudinal." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "FORGETTING..." msgstr "OLVIDAR..." @@ -311,14 +311,15 @@ msgid "Firehose" msgstr "Flujo masivo" #: openpilot/selfdrive/ui/layouts/settings/firehose.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Firehose Mode" msgstr "Modo Firehose" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget" msgstr "Olvidar" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "¿Olvidaste la red Wi-Fi \"{}\"?" @@ -334,7 +335,7 @@ msgstr "Ve a https://connect.comma.ai en tu teléfono" msgid "HIGH" msgstr "ALTO" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Hidden Network" msgstr "Red oculta" @@ -342,7 +343,7 @@ msgstr "Red oculta" msgid "INSTALL" msgstr "INSTALAR" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "IP Address" msgstr "Dirección IP" @@ -362,6 +363,10 @@ msgstr "CARGANDO" msgid "LTE" msgstr "LTE" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Modo de maniobra longitudinal" @@ -398,9 +403,8 @@ msgstr "No hay notas de versión disponibles." msgid "OFFLINE" msgstr "SIN CONEXIÓN" -#: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/html_render.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/html_render.py msgid "OK" msgstr "OK" @@ -453,11 +457,11 @@ msgstr "Conéctate a Wi‑Fi para completar el emparejamiento inicial" msgid "Power Off" msgstr "Apagar" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Evite grandes cargas de datos cuando esté en una conexión Wi-Fi medida" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Evite grandes cargas de datos cuando esté en una conexión celular medida" @@ -549,11 +553,11 @@ msgstr "SELECCIONAR" msgid "SSH Keys" msgstr "Claves SSH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Escaneando redes Wi-Fi..." -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Select" msgstr "Seleccionar" @@ -597,7 +601,7 @@ msgstr "TEMP." msgid "Target Branch" msgstr "Rama objetivo" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Tethering Password" msgstr "Contraseña de anclaje" @@ -665,11 +669,11 @@ msgstr "Cuando está activado, al presionar el pedal del acelerador se desactiva msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Red Wi-Fi medida" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wrong password" msgstr "Contraseña incorrecta" @@ -693,7 +697,7 @@ msgstr "de cheques..." msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "default" msgstr "por defecto" @@ -713,7 +717,7 @@ msgstr "Error al buscar actualizaciones" msgid "finalizing update..." msgstr "finalizando actualización..." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "for \"{}\"" msgstr "para \"{}\"" @@ -721,7 +725,7 @@ msgstr "para \"{}\"" msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "dejar en blanco para configuración automática" @@ -729,7 +733,7 @@ msgstr "dejar en blanco para configuración automática" msgid "left" msgstr "izquierda" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "metered" msgstr "medido" @@ -765,7 +769,7 @@ msgstr "openpilot requiere que el dispositivo esté montado dentro de 4° a izqu msgid "right" msgstr "derecha" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "unmetered" msgstr "sin medir" @@ -809,17 +813,7 @@ msgid_plural "{} minutes ago" msgstr[0] "hace {} minuto" msgstr[1] "hace {} minutos" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "{} segmento de tu conducción está en el conjunto de entrenamiento hasta ahora." -msgstr[1] "{} segmentos de tu conducción están en el conjunto de entrenamiento hasta ahora." - #: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ SUSCRITO" -#: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 Modo Firehose 🔥" - diff --git a/openpilot/selfdrive/ui/translations/app_fr.po b/openpilot/selfdrive/ui/translations/app_fr.po index 7c0aecc9ec..b76018394f 100644 --- a/openpilot/selfdrive/ui/translations/app_fr.po +++ b/openpilot/selfdrive/ui/translations/app_fr.po @@ -52,7 +52,7 @@ msgstr "

    L'étalonnage du délai de réponse de la direction est terminé msgid "ADD" msgstr "AJOUTER" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "APN Setting" msgstr "Paramètres APN" @@ -60,7 +60,7 @@ msgstr "Paramètres APN" msgid "Acknowledge Excessive Actuation" msgstr "Accuser réception d'actionnement excessif" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Advanced" msgstr "Avancé" @@ -93,7 +93,7 @@ msgid "Are you sure you want to uninstall?" msgstr "Êtes-vous sûr de vouloir désinstaller ?" #: openpilot/selfdrive/ui/layouts/onboarding.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Back" msgstr "Retour" @@ -118,22 +118,22 @@ msgid "CHILL MODE ON" msgstr "MODE CHILL ACTIVÉ" #: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECT" msgstr "CONNECTER" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECTING..." msgstr "CONNECTER..." -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/keyboard.py -#: system/ui/widgets/network.py -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/keyboard.py +#: openpilot/system/ui/widgets/network.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "Annuler" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Cellular Metered" msgstr "Données cellulaire limitées" @@ -213,7 +213,7 @@ msgstr "Caméra conducteur" msgid "Driving Personality" msgstr "Personnalité de conduite" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "EDIT" msgstr "EDITER" @@ -242,7 +242,7 @@ msgstr "Activer ADB" msgid "Enable Lane Departure Warnings" msgstr "Activer les alertes de sortie de voie" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Roaming" msgstr "Activer openpilot" @@ -250,7 +250,7 @@ msgstr "Activer openpilot" msgid "Enable SSH" msgstr "Activer SSH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Tethering" msgstr "Activer les alertes de sortie de voie" @@ -266,19 +266,19 @@ msgstr "Activer openpilot" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Activez l'option de contrôle longitudinal openpilot (alpha) pour autoriser le mode expérimental." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter APN" msgstr "Saisir l'APN" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter SSID" msgstr "Entrer le SSID" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "Saisir le mot de passe du partage de connexion" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter password" msgstr "Saisir le mot de passe" @@ -286,7 +286,7 @@ msgstr "Saisir le mot de passe" msgid "Enter your GitHub username" msgstr "Entrez votre nom d'utilisateur GitHub" -#: system/ui/widgets/list_view.py +#: openpilot/system/ui/widgets/list_view.py msgid "Error" msgstr "Erreur" @@ -298,7 +298,7 @@ msgstr "Mode expérimental" msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "Le mode expérimental est actuellement indisponible sur cette voiture car l'ACC d'origine est utilisé pour le contrôle longitudinal." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "FORGETTING..." msgstr "OUBLIER..." @@ -311,14 +311,15 @@ msgid "Firehose" msgstr "Flux continu" #: openpilot/selfdrive/ui/layouts/settings/firehose.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Firehose Mode" msgstr "Mode Firehose" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget" msgstr "Oublier" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Oublier le réseau Wi-Fi \"{}\" ?" @@ -334,7 +335,7 @@ msgstr "Allez sur https://connect.comma.ai sur votre téléphone" msgid "HIGH" msgstr "ÉLEVÉ" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Hidden Network" msgstr "Réseau" @@ -342,7 +343,7 @@ msgstr "Réseau" msgid "INSTALL" msgstr "INSTALLER" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "IP Address" msgstr "Adresse IP" @@ -362,6 +363,10 @@ msgstr "CHARGEMENT" msgid "LTE" msgstr "LTE" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Mode de manœuvre longitudinale" @@ -398,9 +403,8 @@ msgstr "Aucune note de version disponible." msgid "OFFLINE" msgstr "HORS LIGNE" -#: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/html_render.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/html_render.py msgid "OK" msgstr "OK" @@ -453,11 +457,11 @@ msgstr "Veuillez vous connecter au Wi‑Fi pour terminer l'association initiale" msgid "Power Off" msgstr "Éteindre" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Eviter les transferts de données volumineux lorsque vous êtes connecté à un réseau Wi-Fi limité" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Eviter les transferts de données volumineux lors d'une connexion à un réseau cellulaire limité" @@ -549,11 +553,11 @@ msgstr "SELECTIONNER" msgid "SSH Keys" msgstr "Clefs SSH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Analyse des réseaux Wi-Fi..." -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Select" msgstr "Sélectionner" @@ -597,7 +601,7 @@ msgstr "TEMPÉRATURE" msgid "Target Branch" msgstr "Branche cible" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Tethering Password" msgstr "Mot de passe du partage de connexion" @@ -665,11 +669,11 @@ msgstr "Lorsque activé, appuyer sur la pédale d'accélérateur désengagera op msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Réseau Wi-Fi limité" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wrong password" msgstr "Mauvais mot de passe" @@ -693,7 +697,7 @@ msgstr "vérification..." msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "default" msgstr "défaut" @@ -713,7 +717,7 @@ msgstr "échec de la vérification de mise à jour" msgid "finalizing update..." msgstr "finalisation de la mise à jour..." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "for \"{}\"" msgstr "pour \"{}\"" @@ -721,7 +725,7 @@ msgstr "pour \"{}\"" msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "ne pas remplir pour une configuration automatique" @@ -729,7 +733,7 @@ msgstr "ne pas remplir pour une configuration automatique" msgid "left" msgstr "gauche" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "metered" msgstr "limité" @@ -765,7 +769,7 @@ msgstr "openpilot exige que l'appareil soit monté à moins de 4° à gauche ou msgid "right" msgstr "droite" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "unmetered" msgstr "non limité" @@ -809,17 +813,7 @@ msgid_plural "{} minutes ago" msgstr[0] "il y a {} minute" msgstr[1] "il y a {} minutes" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "{} segment de votre conduite est dans l'ensemble d'entraînement jusqu'à présent." -msgstr[1] "{} segments de votre conduite sont dans l'ensemble d'entraînement jusqu'à présent." - #: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ ABONNÉ" -#: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 Mode Firehose 🔥" - diff --git a/openpilot/selfdrive/ui/translations/app_ja.po b/openpilot/selfdrive/ui/translations/app_ja.po index 78d3cf17c6..4fbdb6fe80 100644 --- a/openpilot/selfdrive/ui/translations/app_ja.po +++ b/openpilot/selfdrive/ui/translations/app_ja.po @@ -52,7 +52,7 @@ msgstr "

    ステアリング遅延のキャリブレーションは{}%完 msgid "ADD" msgstr "追加" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "APN Setting" msgstr "APN設定" @@ -60,7 +60,7 @@ msgstr "APN設定" msgid "Acknowledge Excessive Actuation" msgstr "過度な作動を承認" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Advanced" msgstr "詳細設定" @@ -93,7 +93,7 @@ msgid "Are you sure you want to uninstall?" msgstr "本当にアンインストールしますか?" #: openpilot/selfdrive/ui/layouts/onboarding.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Back" msgstr "戻る" @@ -118,22 +118,22 @@ msgid "CHILL MODE ON" msgstr "チルモードON" #: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECT" msgstr "接続" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECTING..." msgstr "接続中..." -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/keyboard.py -#: system/ui/widgets/network.py -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/keyboard.py +#: openpilot/system/ui/widgets/network.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "キャンセル" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Cellular Metered" msgstr "従量課金の携帯回線" @@ -213,7 +213,7 @@ msgstr "ドライバーカメラ" msgid "Driving Personality" msgstr "走行性格" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "EDIT" msgstr "編集" @@ -242,7 +242,7 @@ msgstr "ADBを有効化" msgid "Enable Lane Departure Warnings" msgstr "車線逸脱警報を有効化" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Roaming" msgstr "ローミングを有効化" @@ -250,7 +250,7 @@ msgstr "ローミングを有効化" msgid "Enable SSH" msgstr "SSHを有効化" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Tethering" msgstr "テザリングを有効化" @@ -266,19 +266,19 @@ msgstr "openpilotを有効化" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "openpilot縦制御(アルファ)のトグルを有効にすると実験モードが使用できます。" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter APN" msgstr "APNを入力" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter SSID" msgstr "SSIDを入力" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "新しいテザリングのパスワードを入力" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter password" msgstr "パスワードを入力" @@ -286,7 +286,7 @@ msgstr "パスワードを入力" msgid "Enter your GitHub username" msgstr "GitHubユーザー名を入力" -#: system/ui/widgets/list_view.py +#: openpilot/system/ui/widgets/list_view.py msgid "Error" msgstr "エラー" @@ -298,7 +298,7 @@ msgstr "実験モード" msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "この車では縦制御に純正ACCを使用するため、現在実験モードは利用できません。" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "FORGETTING..." msgstr "削除中..." @@ -311,14 +311,15 @@ msgid "Firehose" msgstr "大量配信" #: openpilot/selfdrive/ui/layouts/settings/firehose.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Firehose Mode" msgstr "Firehoseモード" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget" msgstr "削除" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Wi‑Fiネットワーク「{}」を削除しますか?" @@ -334,7 +335,7 @@ msgstr "スマートフォンで https://connect.comma.ai にアクセス" msgid "HIGH" msgstr "高温" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Hidden Network" msgstr "非公開ネットワーク" @@ -342,7 +343,7 @@ msgstr "非公開ネットワーク" msgid "INSTALL" msgstr "インストール" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "IP Address" msgstr "IPアドレス" @@ -362,6 +363,10 @@ msgstr "読み込み中" msgid "LTE" msgstr "LTE" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "縦制御マヌーバーモード" @@ -398,9 +403,8 @@ msgstr "リリースノートはありません。" msgid "OFFLINE" msgstr "オフライン" -#: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/html_render.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/html_render.py msgid "OK" msgstr "OK" @@ -453,11 +457,11 @@ msgstr "初回ペアリングを完了するにはWi‑Fiに接続してくだ msgid "Power Off" msgstr "電源オフ" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "従量課金のWi‑Fi接続時は大きなデータのアップロードを抑制" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "従量課金の携帯回線接続時は大きなデータのアップロードを抑制" @@ -549,11 +553,11 @@ msgstr "選択" msgid "SSH Keys" msgstr "SSH鍵" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fiネットワークを検索中..." -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Select" msgstr "選択" @@ -597,7 +601,7 @@ msgstr "温度" msgid "Target Branch" msgstr "対象ブランチ" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Tethering Password" msgstr "テザリングのパスワード" @@ -665,11 +669,11 @@ msgstr "有効にすると、アクセルを踏むとopenpilotが解除されま msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Wi‑Fiネットワーク(従量課金)" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wrong password" msgstr "パスワードが違います" @@ -693,7 +697,7 @@ msgstr "チェック中..." msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "default" msgstr "既定" @@ -713,7 +717,7 @@ msgstr "アップデートの確認に失敗しました" msgid "finalizing update..." msgstr "アップデートを終了しています..." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "for \"{}\"" msgstr "「{}」向け" @@ -721,7 +725,7 @@ msgstr "「{}」向け" msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "自動設定の場合は空欄のままにしてください" @@ -729,7 +733,7 @@ msgstr "自動設定の場合は空欄のままにしてください" msgid "left" msgstr "左" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "metered" msgstr "従量" @@ -765,7 +769,7 @@ msgstr "openpilotでは、デバイスの取り付け角度が左右±4°、上 msgid "right" msgstr "右" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "unmetered" msgstr "非従量" @@ -805,16 +809,7 @@ msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{}分前" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "これまでにあなたの走行の{}セグメントが学習データセットに含まれています。" - #: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ 登録済み" -#: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 Firehoseモード 🔥" - diff --git a/openpilot/selfdrive/ui/translations/app_ko.po b/openpilot/selfdrive/ui/translations/app_ko.po index 24306ae02a..29d297be88 100644 --- a/openpilot/selfdrive/ui/translations/app_ko.po +++ b/openpilot/selfdrive/ui/translations/app_ko.po @@ -52,7 +52,7 @@ msgstr "

    스티어링 지연 보정이 {}% 완료되었습니다." msgid "ADD" msgstr "추가" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "APN Setting" msgstr "APN 설정" @@ -60,7 +60,7 @@ msgstr "APN 설정" msgid "Acknowledge Excessive Actuation" msgstr "과도한 작동을 확인" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Advanced" msgstr "고급" @@ -93,7 +93,7 @@ msgid "Are you sure you want to uninstall?" msgstr "정말 제거하시겠습니까?" #: openpilot/selfdrive/ui/layouts/onboarding.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Back" msgstr "뒤로" @@ -118,22 +118,22 @@ msgid "CHILL MODE ON" msgstr "안정적 모드 켜짐" #: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECT" msgstr "연결" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECTING..." msgstr "연결 중..." -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/keyboard.py -#: system/ui/widgets/network.py -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/keyboard.py +#: openpilot/system/ui/widgets/network.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "취소" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Cellular Metered" msgstr "종량제 셀룰러" @@ -213,7 +213,7 @@ msgstr "운전자 카메라" msgid "Driving Personality" msgstr "주행 성향" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "EDIT" msgstr "편집" @@ -242,7 +242,7 @@ msgstr "ADB 사용" msgid "Enable Lane Departure Warnings" msgstr "차선 이탈 경고 사용" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Roaming" msgstr "로밍 사용" @@ -250,7 +250,7 @@ msgstr "로밍 사용" msgid "Enable SSH" msgstr "SSH 사용" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Tethering" msgstr "테더링 사용" @@ -266,19 +266,19 @@ msgstr "openpilot 사용" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "실험 모드를 사용하려면 openpilot 롱컨 제어(알파) 토글을 켜세요." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter APN" msgstr "APN 입력" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter SSID" msgstr "SSID 입력" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "새 테더링 비밀번호 입력" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter password" msgstr "비밀번호 입력" @@ -286,7 +286,7 @@ msgstr "비밀번호 입력" msgid "Enter your GitHub username" msgstr "GitHub 사용자 이름 입력" -#: system/ui/widgets/list_view.py +#: openpilot/system/ui/widgets/list_view.py msgid "Error" msgstr "오류" @@ -298,7 +298,7 @@ msgstr "실험 모드" msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "이 차량은 롱컨 제어에 순정 ACC를 사용하므로 현재 실험 모드를 사용할 수 없습니다." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "FORGETTING..." msgstr "삭제 중..." @@ -311,14 +311,15 @@ msgid "Firehose" msgstr "파이어호스" #: openpilot/selfdrive/ui/layouts/settings/firehose.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Firehose Mode" msgstr "파이어호스 모드" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget" msgstr "삭제" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Wi‑Fi 네트워크 \"{}\"를 삭제하시겠습니까?" @@ -334,7 +335,7 @@ msgstr "휴대폰에서 https://connect.comma.ai 에 접속하세요" msgid "HIGH" msgstr "높음" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Hidden Network" msgstr "숨겨진 네트워크" @@ -342,7 +343,7 @@ msgstr "숨겨진 네트워크" msgid "INSTALL" msgstr "설치" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "IP Address" msgstr "IP 주소" @@ -362,6 +363,10 @@ msgstr "로딩 중" msgid "LTE" msgstr "LTE" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "롱컨 기동 모드" @@ -398,9 +403,8 @@ msgstr "릴리스 노트가 없습니다." msgid "OFFLINE" msgstr "오프라인" -#: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/html_render.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/html_render.py msgid "OK" msgstr "확인" @@ -453,11 +457,11 @@ msgstr "초기 페어링을 완료하려면 Wi‑Fi에 연결하세요" msgid "Power Off" msgstr "전원 끄기" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "종량제 Wi‑Fi 연결 시 대용량 업로드 방지" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "종량제 셀룰러 연결 시 대용량 업로드 방지" @@ -549,11 +553,11 @@ msgstr "선택" msgid "SSH Keys" msgstr "SSH 키" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fi 네트워크 검색 중..." -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Select" msgstr "선택" @@ -597,7 +601,7 @@ msgstr "온도" msgid "Target Branch" msgstr "대상 브랜치" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Tethering Password" msgstr "테더링 비밀번호" @@ -665,11 +669,11 @@ msgstr "이 옵션을 켜면 가속 페달을 밟을 때 openpilot이 해제됩 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 네트워크 종량제" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wrong password" msgstr "비밀번호가 올바르지 않습니다" @@ -693,7 +697,7 @@ msgstr "확인 중..." msgid "comma prime" msgstr "comma 프라임" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "default" msgstr "기본값" @@ -713,7 +717,7 @@ msgstr "업데이트 확인 실패" msgid "finalizing update..." msgstr "업데이트 마무리 중..." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "for \"{}\"" msgstr "\"{}\"용" @@ -721,7 +725,7 @@ msgstr "\"{}\"용" msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "자동 구성을 사용하려면 비워 두세요" @@ -729,7 +733,7 @@ msgstr "자동 구성을 사용하려면 비워 두세요" msgid "left" msgstr "왼쪽" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "metered" msgstr "종량제" @@ -765,7 +769,7 @@ msgstr "openpilot은 장치를 좌우 4°, 위쪽 5°, 아래쪽 9° 이내로 msgid "right" msgstr "오른쪽" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "unmetered" msgstr "비종량제" @@ -805,16 +809,7 @@ msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{}분 전" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "현재까지 귀하의 주행 {}구간이 학습 데이터셋에 포함되었습니다." - #: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ 구독됨" -#: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 파이어호스 모드 🔥" - diff --git a/openpilot/selfdrive/ui/translations/app_pt-BR.po b/openpilot/selfdrive/ui/translations/app_pt-BR.po index 58f2094479..29a95b22fd 100644 --- a/openpilot/selfdrive/ui/translations/app_pt-BR.po +++ b/openpilot/selfdrive/ui/translations/app_pt-BR.po @@ -52,7 +52,7 @@ msgstr "

    A calibração da latência da direção está {}% concluída." msgid "ADD" msgstr "ADICIONAR" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "APN Setting" msgstr "Configuração de APN" @@ -60,7 +60,7 @@ msgstr "Configuração de APN" msgid "Acknowledge Excessive Actuation" msgstr "Reconhecer Atuação Excessiva" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Advanced" msgstr "Avançado" @@ -93,7 +93,7 @@ msgid "Are you sure you want to uninstall?" msgstr "Tem certeza de que deseja desinstalar?" #: openpilot/selfdrive/ui/layouts/onboarding.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Back" msgstr "Voltar" @@ -118,22 +118,22 @@ msgid "CHILL MODE ON" msgstr "MODO CHILL ATIVO" #: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECT" msgstr "CONECTAR" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECTING..." msgstr "CONECTANDO..." -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/keyboard.py -#: system/ui/widgets/network.py -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/keyboard.py +#: openpilot/system/ui/widgets/network.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "Cancelar" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Cellular Metered" msgstr "Dados móveis limitados" @@ -213,7 +213,7 @@ msgstr "Câmera do Motorista" msgid "Driving Personality" msgstr "Personalidade" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "EDIT" msgstr "EDITAR" @@ -242,7 +242,7 @@ msgstr "Ativar ADB" msgid "Enable Lane Departure Warnings" msgstr "Ativar alertas de saída de faixa" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Roaming" msgstr "Ativar roaming" @@ -250,7 +250,7 @@ msgstr "Ativar roaming" msgid "Enable SSH" msgstr "Ativar SSH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Tethering" msgstr "Ativar compartilhamento" @@ -266,19 +266,19 @@ msgstr "Ativar openpilot" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Ative a opção de controle longitudinal do openpilot (alpha) para permitir o Modo Experimental." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter APN" msgstr "Digite APN" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter SSID" msgstr "Digite SSID" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "Digite nova senha tethering" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter password" msgstr "Digite a senha" @@ -286,7 +286,7 @@ msgstr "Digite a senha" msgid "Enter your GitHub username" msgstr "Digite seu nome de usuário do GitHub" -#: system/ui/widgets/list_view.py +#: openpilot/system/ui/widgets/list_view.py msgid "Error" msgstr "Erro" @@ -298,7 +298,7 @@ msgstr "Modo Experimental" msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "O Modo Experimental está indisponível neste carro pois o ACC original do carro é usado para controle longitudinal." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "FORGETTING..." msgstr "ESQUECENDO..." @@ -311,14 +311,15 @@ msgid "Firehose" msgstr "Fluxo contínuo" #: openpilot/selfdrive/ui/layouts/settings/firehose.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Firehose Mode" msgstr "Modo Firehose" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget" msgstr "Esquecer" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Esquecer rede Wi-Fi \"{}\"?" @@ -334,7 +335,7 @@ msgstr "Acesse https://connect.comma.ai no seu telefone" msgid "HIGH" msgstr "ALTO" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Hidden Network" msgstr "Rede oculta" @@ -342,7 +343,7 @@ msgstr "Rede oculta" msgid "INSTALL" msgstr "INSTALAR" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "IP Address" msgstr "Endereço IP" @@ -362,6 +363,10 @@ msgstr "CARREGANDO" msgid "LTE" msgstr "LTE" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Modo de Manobra Longitudinal" @@ -398,9 +403,8 @@ msgstr "Sem notas de versão disponíveis." msgid "OFFLINE" msgstr "OFF-LINE" -#: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/html_render.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/html_render.py msgid "OK" msgstr "OK" @@ -453,11 +457,11 @@ msgstr "Conecte-se ao Wi‑Fi para concluir o emparelhamento inicial" msgid "Power Off" msgstr "Desligar" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Evitar uploads grandes de dados em conexões Wi-Fi limitadas" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Evitar uploads grandes de dados em conexões móveis limitadas" @@ -549,11 +553,11 @@ msgstr "SELECIONAR" msgid "SSH Keys" msgstr "Chaves SSH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Procurando redes Wi-Fi..." -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Select" msgstr "Selecione" @@ -597,7 +601,7 @@ msgstr "TEMP." msgid "Target Branch" msgstr "Branch Alvo" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Tethering Password" msgstr "Senha Tethering" @@ -665,11 +669,11 @@ msgstr "Quando ativado, pressionar o pedal do acelerador desengajará o openpilo msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Rede Wi-Fi limitada" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wrong password" msgstr "Senha errada" @@ -693,7 +697,7 @@ msgstr "verificando..." msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "default" msgstr "padrão" @@ -713,7 +717,7 @@ msgstr "falha ao verificar atualização" msgid "finalizing update..." msgstr "finalizando atualização..." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "for \"{}\"" msgstr "para \"{}\"" @@ -721,7 +725,7 @@ msgstr "para \"{}\"" msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "deixe em branco para configuração automática" @@ -729,7 +733,7 @@ msgstr "deixe em branco para configuração automática" msgid "left" msgstr "à esquerda" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "metered" msgstr "limitados" @@ -765,7 +769,7 @@ msgstr "o openpilot requer que o dispositivo seja montado dentro de 4° para a e msgid "right" msgstr "à direita" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "unmetered" msgstr "ilimitados" @@ -809,17 +813,7 @@ msgid_plural "{} minutes ago" msgstr[0] "{} minuto atrás" msgstr[1] "{} minutos atrás" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "{} segmento da sua condução está no conjunto de treinamento até agora." -msgstr[1] "{} segmentos da sua condução estão no conjunto de treinamento até agora." - #: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ ASSINADO" -#: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 Modo Firehose 🔥" - diff --git a/openpilot/selfdrive/ui/translations/app_th.po b/openpilot/selfdrive/ui/translations/app_th.po index 4e45cae14b..59cc8df33d 100644 --- a/openpilot/selfdrive/ui/translations/app_th.po +++ b/openpilot/selfdrive/ui/translations/app_th.po @@ -52,7 +52,7 @@ msgstr "

    การปรับเทียบความล่าช้ msgid "ADD" msgstr "เพิ่ม" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "APN Setting" msgstr "การตั้งค่า APN" @@ -60,7 +60,7 @@ msgstr "การตั้งค่า APN" msgid "Acknowledge Excessive Actuation" msgstr "รับทราบการดำเนินการที่มากเกินไป" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Advanced" msgstr "ขั้นสูง" @@ -93,7 +93,7 @@ msgid "Are you sure you want to uninstall?" msgstr "คุณแน่ใจหรือไม่ว่าต้องการถอนการติดตั้ง?" #: openpilot/selfdrive/ui/layouts/onboarding.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Back" msgstr "กลับ" @@ -118,22 +118,22 @@ msgid "CHILL MODE ON" msgstr "เปิดโหมด Chill" #: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECT" msgstr "เชื่อมต่อ" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECTING..." msgstr "กำลังเชื่อมต่อ..." -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/keyboard.py -#: system/ui/widgets/network.py -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/keyboard.py +#: openpilot/system/ui/widgets/network.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "ยกเลิก" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Cellular Metered" msgstr "เซลล์วัดแสง" @@ -213,7 +213,7 @@ msgstr "กล้องไดร์เวอร์" msgid "Driving Personality" msgstr "บุคลิกภาพในการขับขี่" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "EDIT" msgstr "แก้ไข" @@ -242,7 +242,7 @@ msgstr "เปิดใช้งาน ADB" msgid "Enable Lane Departure Warnings" msgstr "เปิดใช้งานคำเตือนการออกนอกเลน" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Roaming" msgstr "เปิดใช้งานโรมมิ่ง" @@ -250,7 +250,7 @@ msgstr "เปิดใช้งานโรมมิ่ง" msgid "Enable SSH" msgstr "เปิดใช้งาน SSH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Tethering" msgstr "เปิดใช้งานการปล่อยสัญญาณ" @@ -266,19 +266,19 @@ msgstr "เปิดใช้งานโอเพ่นไพลอต" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "เปิดใช้งานการสลับการควบคุมตามยาวของ openpilot (อัลฟา) เพื่ออนุญาตโหมดการทดลอง" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter APN" msgstr "ป้อน APN" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter SSID" msgstr "ป้อน SSID" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "ป้อนรหัสผ่านการปล่อยสัญญาณใหม่" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter password" msgstr "ใส่รหัสผ่าน" @@ -286,7 +286,7 @@ msgstr "ใส่รหัสผ่าน" msgid "Enter your GitHub username" msgstr "ป้อนชื่อผู้ใช้ GitHub ของคุณ" -#: system/ui/widgets/list_view.py +#: openpilot/system/ui/widgets/list_view.py msgid "Error" msgstr "ข้อผิดพลาด" @@ -298,7 +298,7 @@ msgstr "โหมดทดลอง" msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "ขณะนี้โหมดทดลองไม่สามารถใช้งานได้บนรถคันนี้ เนื่องจาก ACC ในสต็อกของรถใช้สำหรับการควบคุมตามยาว" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "FORGETTING..." msgstr "กำลังลืม..." @@ -311,14 +311,15 @@ msgid "Firehose" msgstr "สายดับเพลิง" #: openpilot/selfdrive/ui/layouts/settings/firehose.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Firehose Mode" msgstr "โหมดสายดับเพลิง" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget" msgstr "ลืม" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "ลืมเครือข่าย Wi-Fi \"{}\" หรือไม่" @@ -334,7 +335,7 @@ msgstr "ไปที่ https://connect.comma.ai บนโทรศัพท์ msgid "HIGH" msgstr "สูง" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Hidden Network" msgstr "เครือข่ายที่ซ่อนอยู่" @@ -342,7 +343,7 @@ msgstr "เครือข่ายที่ซ่อนอยู่" msgid "INSTALL" msgstr "ติดตั้ง" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "IP Address" msgstr "ที่อยู่ IP" @@ -362,6 +363,10 @@ msgstr "กำลังโหลด" msgid "LTE" msgstr "แอลทีที" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "โหมดการซ้อมรบตามยาว" @@ -398,9 +403,8 @@ msgstr "ไม่มีบันทึกประจำรุ่น" msgid "OFFLINE" msgstr "ออฟไลน์" -#: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/html_render.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/html_render.py msgid "OK" msgstr "ตกลง" @@ -453,11 +457,11 @@ msgstr "โปรดเชื่อมต่อ Wi-Fi เพื่อทำก msgid "Power Off" msgstr "ปิดเครื่อง" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "ป้องกันการอัปโหลดข้อมูลขนาดใหญ่เมื่อใช้การเชื่อมต่อ Wi-Fi แบบมิเตอร์" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "ป้องกันการอัพโหลดข้อมูลขนาดใหญ่เมื่อใช้การเชื่อมต่อมือถือแบบคิดค่าบริการตามปริมาณข้อมูล" @@ -549,11 +553,11 @@ msgstr "เลือก" msgid "SSH Keys" msgstr "คีย์ SSH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "กำลังสแกนเครือข่าย Wi-Fi..." -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Select" msgstr "เลือก" @@ -597,7 +601,7 @@ msgstr "อุณหภูมิ" msgid "Target Branch" msgstr "สาขาเป้าหมาย" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Tethering Password" msgstr "รหัสผ่านการแชร์อินเทอร์เน็ต" @@ -665,11 +669,11 @@ msgstr "เมื่อเปิดใช้งาน การกดแป้ msgid "Wi-Fi" msgstr "อินเตอร์เน็ตไร้สาย" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "เครือข่าย Wi-Fi มีการตรวจวัด" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wrong password" msgstr "รหัสผ่านผิด" @@ -693,7 +697,7 @@ msgstr "กำลังตรวจสอบ..." msgid "comma prime" msgstr "เครื่องหมายลูกน้ำเฉพาะ" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "default" msgstr "ค่าเริ่มต้น" @@ -713,7 +717,7 @@ msgstr "ไม่สามารถตรวจสอบการอัปเด msgid "finalizing update..." msgstr "กำลังสรุปการอัปเดต..." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "for \"{}\"" msgstr "สำหรับ \"{}\"" @@ -721,7 +725,7 @@ msgstr "สำหรับ \"{}\"" msgid "km/h" msgstr "กม./ชม" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "เว้นว่างไว้เพื่อกำหนดค่าอัตโนมัติ" @@ -729,7 +733,7 @@ msgstr "เว้นว่างไว้เพื่อกำหนดค่า msgid "left" msgstr "ซ้าย" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "metered" msgstr "คิดค่าบริการตามปริมาณ" @@ -765,7 +769,7 @@ msgstr "openpilot ต้องติดตั้งอุปกรณ์ให msgid "right" msgstr "ขวา" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "unmetered" msgstr "ไม่จำกัดปริมาณ" @@ -805,16 +809,7 @@ msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} นาทีที่แล้ว" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "ขณะนี้มีช่วงการขับขี่ของคุณ {} ช่วงอยู่ในชุดข้อมูลฝึกสอนแล้ว" - #: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ สมัครแล้ว" -#: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 โหมด Firehose 🔥" - diff --git a/openpilot/selfdrive/ui/translations/app_tr.po b/openpilot/selfdrive/ui/translations/app_tr.po index dbb5b325a6..cf15e2c806 100644 --- a/openpilot/selfdrive/ui/translations/app_tr.po +++ b/openpilot/selfdrive/ui/translations/app_tr.po @@ -52,7 +52,7 @@ msgstr "

    Direksiyon gecikmesi kalibrasyonu %{} tamamlandı." msgid "ADD" msgstr "EKLE" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "APN Setting" msgstr "APN Ayarı" @@ -60,7 +60,7 @@ msgstr "APN Ayarı" msgid "Acknowledge Excessive Actuation" msgstr "Aşırı Müdahaleyi Onayla" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Advanced" msgstr "Gelişmiş" @@ -93,7 +93,7 @@ msgid "Are you sure you want to uninstall?" msgstr "Kaldırmak istediğinizden emin misiniz?" #: openpilot/selfdrive/ui/layouts/onboarding.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Back" msgstr "Geri" @@ -118,22 +118,22 @@ msgid "CHILL MODE ON" msgstr "CHILL MODU AÇIK" #: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECT" msgstr "BAĞLAN" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECTING..." msgstr "BAĞLAN" -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/keyboard.py -#: system/ui/widgets/network.py -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/keyboard.py +#: openpilot/system/ui/widgets/network.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "İptal" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Cellular Metered" msgstr "Ölçülü Hücresel" @@ -213,7 +213,7 @@ msgstr "Sürücü Kamerası" msgid "Driving Personality" msgstr "Sürüş Kişiliği" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "EDIT" msgstr "DÜZENLE" @@ -242,7 +242,7 @@ msgstr "ADB'yi Etkinleştir" msgid "Enable Lane Departure Warnings" msgstr "Şerit Terk Uyarılarını Etkinleştir" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Roaming" msgstr "Dolaşımı Etkinleştir" @@ -250,7 +250,7 @@ msgstr "Dolaşımı Etkinleştir" msgid "Enable SSH" msgstr "SSH'yi Etkinleştir" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Tethering" msgstr "İnternet Paylaşımını Etkinleştir" @@ -266,19 +266,19 @@ msgstr "openpilot'u etkinleştir" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Deneysel modu etkinleştirmek için openpilot boylamsal kontrolünü (alfa) açın." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter APN" msgstr "APN girin" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter SSID" msgstr "SSID girin" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "Yeni internet paylaşımı şifresini girin" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter password" msgstr "Şifre girin" @@ -286,7 +286,7 @@ msgstr "Şifre girin" msgid "Enter your GitHub username" msgstr "GitHub kullanıcı adınızı girin" -#: system/ui/widgets/list_view.py +#: openpilot/system/ui/widgets/list_view.py msgid "Error" msgstr "Hata" @@ -298,7 +298,7 @@ msgstr "Deneysel Mod" msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "Bu araçta boylamsal kontrol için stok ACC kullanıldığından şu anda Deneysel mod kullanılamıyor." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "FORGETTING..." msgstr "UNUTULUYOR..." @@ -311,14 +311,15 @@ msgid "Firehose" msgstr "Yoğun veri akışı" #: openpilot/selfdrive/ui/layouts/settings/firehose.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Firehose Mode" msgstr "Firehose Modu" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget" msgstr "Unut" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "\"{}\" Wi‑Fi ağı unutulsun mu?" @@ -334,7 +335,7 @@ msgstr "Telefonunuzda https://connect.comma.ai adresine gidin" msgid "HIGH" msgstr "YÜKSEK" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Hidden Network" msgstr "Gizli Ağ" @@ -342,7 +343,7 @@ msgstr "Gizli Ağ" msgid "INSTALL" msgstr "YÜKLE" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "IP Address" msgstr "IP Adresi" @@ -362,6 +363,10 @@ msgstr "YÜKLENİYOR" msgid "LTE" msgstr "LTE" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Boylamsal Manevra Modu" @@ -398,9 +403,8 @@ msgstr "Sürüm notu mevcut değil." msgid "OFFLINE" msgstr "ÇEVRİMDIŞI" -#: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/html_render.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/html_render.py msgid "OK" msgstr "OK" @@ -453,11 +457,11 @@ msgstr "İlk eşleştirmeyi tamamlamak için lütfen Wi‑Fi'a bağlanın" msgid "Power Off" msgstr "Kapat" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Ölçülü bir Wi‑Fi bağlantısındayken büyük veri yüklemelerini engelle" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Ölçülü bir hücresel bağlantıdayken büyük veri yüklemelerini engelle" @@ -549,11 +553,11 @@ msgstr "SEÇ" msgid "SSH Keys" msgstr "SSH Anahtarları" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fi ağları taranıyor..." -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Select" msgstr "Seç" @@ -597,7 +601,7 @@ msgstr "SIC." msgid "Target Branch" msgstr "Hedef Dal" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Tethering Password" msgstr "İnternet Paylaşımı Şifresi" @@ -665,11 +669,11 @@ msgstr "Etkinleştirildiğinde, gaz pedalına basmak openpilot'u devreden çıka msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Ölçülü Wi‑Fi Ağı" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wrong password" msgstr "Yanlış şifre" @@ -693,7 +697,7 @@ msgstr "kontrol ediliyor..." msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "default" msgstr "varsayılan" @@ -713,7 +717,7 @@ msgstr "güncelleme kontrolü başarısız" msgid "finalizing update..." msgstr "güncelleme tamamlanıyor..." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "for \"{}\"" msgstr "\"{}\" için" @@ -721,7 +725,7 @@ msgstr "\"{}\" için" msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "otomatik yapılandırma için boş bırakın" @@ -729,7 +733,7 @@ msgstr "otomatik yapılandırma için boş bırakın" msgid "left" msgstr "sol" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "metered" msgstr "ölçülü" @@ -765,7 +769,7 @@ msgstr "openpilot, cihazın sağa/sola 4° ve yukarı 5° veya aşağı 9° içi msgid "right" msgstr "sağ" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "unmetered" msgstr "ölçüsüz" @@ -809,17 +813,7 @@ msgid_plural "{} minutes ago" msgstr[0] "{} dakika önce" msgstr[1] "{} dakika önce" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "{} segment sürüşünüz eğitim veri setinde." -msgstr[1] "{} segment sürüşünüz eğitim veri setinde." - #: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ ABONE" -#: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 Firehose Modu 🔥" - diff --git a/openpilot/selfdrive/ui/translations/app_uk.po b/openpilot/selfdrive/ui/translations/app_uk.po index 3f3d186657..24fab4f2e8 100644 --- a/openpilot/selfdrive/ui/translations/app_uk.po +++ b/openpilot/selfdrive/ui/translations/app_uk.po @@ -52,7 +52,7 @@ msgstr "

    Калібрування затримки кермування msgid "ADD" msgstr "ДОДАТИ" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "APN Setting" msgstr "Налаштування APN" @@ -60,7 +60,7 @@ msgstr "Налаштування APN" msgid "Acknowledge Excessive Actuation" msgstr "Визнайте надмірне спрацьовування" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Advanced" msgstr "Розширені" @@ -93,7 +93,7 @@ msgid "Are you sure you want to uninstall?" msgstr "Ви впевнені, що хочете видалити?" #: openpilot/selfdrive/ui/layouts/onboarding.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Back" msgstr "Назад" @@ -118,22 +118,22 @@ msgid "CHILL MODE ON" msgstr "СПОКІЙНИЙ РЕЖИМ" #: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECT" msgstr "ПІДКЛЮЧИТИ" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECTING..." msgstr "ПІДКЛЮЧА..." -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/keyboard.py -#: system/ui/widgets/network.py -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/keyboard.py +#: openpilot/system/ui/widgets/network.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "Скасувати" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Cellular Metered" msgstr "Лімітне стільникове з'єднання" @@ -213,7 +213,7 @@ msgstr "Камера водія" msgid "Driving Personality" msgstr "Стиль водіння" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "EDIT" msgstr "РЕДАГ." @@ -242,7 +242,7 @@ msgstr "Увімкнути ADB" msgid "Enable Lane Departure Warnings" msgstr "Увімкнути попередження про виїзд зі смуги" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Roaming" msgstr "Увімкнути роумінг" @@ -250,7 +250,7 @@ msgstr "Увімкнути роумінг" msgid "Enable SSH" msgstr "Увімкнути SSH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Tethering" msgstr "Увімкнути точку доступу" @@ -266,19 +266,19 @@ msgstr "Увімкнути openpilot" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Увімкніть перемикач поздовжнього керування openpilot (альфа), щоб увімкнути експериментальний режим." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter APN" msgstr "Введіть APN" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter SSID" msgstr "Введіть SSID" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "Введіть новий пароль для модему" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter password" msgstr "Введіть пароль" @@ -286,7 +286,7 @@ msgstr "Введіть пароль" msgid "Enter your GitHub username" msgstr "Введіть ваш логін GitHub" -#: system/ui/widgets/list_view.py +#: openpilot/system/ui/widgets/list_view.py msgid "Error" msgstr "Помилка" @@ -298,7 +298,7 @@ msgstr "Експериментальний режим" msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "Експериментальний режим наразі недоступний для цього автомобіля, оскільки для поздовжнього керування використовується штатний адаптивний круїз-контроль (ACC)." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "FORGETTING..." msgstr "ЗАБУВАЮ..." @@ -311,14 +311,15 @@ msgid "Firehose" msgstr "Злива" #: openpilot/selfdrive/ui/layouts/settings/firehose.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Firehose Mode" msgstr "Режим зливи" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget" msgstr "Забути" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Забути мережу Wi-Fi \"{}\"?" @@ -334,7 +335,7 @@ msgstr "Перейдіть на сайт https://connect.comma.ai на своє msgid "HIGH" msgstr "ВИСОКА" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Hidden Network" msgstr "Прихована мережа" @@ -342,7 +343,7 @@ msgstr "Прихована мережа" msgid "INSTALL" msgstr "ВСТАНОВ." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "IP Address" msgstr "IP-адреса" @@ -362,6 +363,10 @@ msgstr "ЗАВАНТАЖЕННЯ" msgid "LTE" msgstr "LTE" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Режим поздовжнього маневрування" @@ -398,9 +403,8 @@ msgstr "Інформація про випуск відсутня." msgid "OFFLINE" msgstr "ОФЛАЙН" -#: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/html_render.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/html_render.py msgid "OK" msgstr "OK" @@ -453,11 +457,11 @@ msgstr "Будь ласка, підключіться до Wi-Fi, щоб зав msgid "Power Off" msgstr "Вимкнути" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Запобігайте завантаженню великих обсягів даних під час використання Wi-Fi-з'єднання з обмеженим трафіком" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Запобігати великим завантаженням даних під час лімітного стільникового з'єднання" @@ -549,11 +553,11 @@ msgstr "ВИБРАТИ" msgid "SSH Keys" msgstr "SSH ключі" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Пошук мереж..." -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Select" msgstr "Вибрати" @@ -597,7 +601,7 @@ msgstr "ТЕМП" msgid "Target Branch" msgstr "Цільова гілка" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Tethering Password" msgstr "Пароль для точки доступу" @@ -665,11 +669,11 @@ msgstr "Якщо увімкнено, натискання на педаль ак msgid "Wi-Fi" msgstr "Wi-Fi" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Трафік Wi-Fi" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wrong password" msgstr "Невірний пароль" @@ -693,7 +697,7 @@ msgstr "перевіряю..." msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "default" msgstr "замовч." @@ -713,7 +717,7 @@ msgstr "не вдалося перевірити оновлення" msgid "finalizing update..." msgstr "завершую..." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "for \"{}\"" msgstr "для \"{}\"" @@ -721,7 +725,7 @@ msgstr "для \"{}\"" msgid "km/h" msgstr "км/год" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "залиште порожнім для автоматичного налаштування" @@ -729,7 +733,7 @@ msgstr "залиште порожнім для автоматичного нал msgid "left" msgstr "вліво" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "metered" msgstr "обмеж." @@ -765,7 +769,7 @@ msgstr "Для роботи openpilot потрібно, щоб пристрій msgid "right" msgstr "вправо" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "unmetered" msgstr "необмеж." @@ -813,18 +817,7 @@ msgstr[0] "{} хвилина тому" msgstr[1] "{} хвилини тому" msgstr[2] "{} хвилин тому" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "{} сегмент вашого водіння на даний момент містяться в тренувальному наборі даних." -msgstr[1] "{} сегменти вашого водіння на даний момент містяться в тренувальному наборі даних." -msgstr[2] "{} сегментів вашого водіння на даний момент містяться в тренувальному наборі даних." - #: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ ПІДПИСАНО" -#: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" -msgstr "🌧️ Режим зливи 🌧️" - diff --git a/openpilot/selfdrive/ui/translations/app_zh-CHS.po b/openpilot/selfdrive/ui/translations/app_zh-CHS.po index 55a7c329f6..2028b6ac1f 100644 --- a/openpilot/selfdrive/ui/translations/app_zh-CHS.po +++ b/openpilot/selfdrive/ui/translations/app_zh-CHS.po @@ -52,7 +52,7 @@ msgstr "

    转向延迟校准已完成 {}%。" msgid "ADD" msgstr "添加" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "APN Setting" msgstr "APN 设置" @@ -60,7 +60,7 @@ msgstr "APN 设置" msgid "Acknowledge Excessive Actuation" msgstr "确认过度作动" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Advanced" msgstr "高级" @@ -93,7 +93,7 @@ msgid "Are you sure you want to uninstall?" msgstr "确定要卸载吗?" #: openpilot/selfdrive/ui/layouts/onboarding.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Back" msgstr "返回" @@ -118,22 +118,22 @@ msgid "CHILL MODE ON" msgstr "安稳模式已开启" #: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECT" msgstr "连接" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECTING..." msgstr "连接中..." -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/keyboard.py -#: system/ui/widgets/network.py -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/keyboard.py +#: openpilot/system/ui/widgets/network.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "取消" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Cellular Metered" msgstr "蜂窝计量" @@ -213,7 +213,7 @@ msgstr "车内摄像头" msgid "Driving Personality" msgstr "驾驶风格" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "EDIT" msgstr "编辑" @@ -242,7 +242,7 @@ msgstr "启用 ADB" msgid "Enable Lane Departure Warnings" msgstr "启用车道偏离警示" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Roaming" msgstr "启用漫游" @@ -250,7 +250,7 @@ msgstr "启用漫游" msgid "Enable SSH" msgstr "启用 SSH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Tethering" msgstr "启用网络共享" @@ -266,19 +266,19 @@ msgstr "启用 openpilot" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "启用 openpilot 纵向控制(alpha)开关,以使用实验模式。" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter APN" msgstr "输入 APN" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter SSID" msgstr "输入 SSID" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "输入新的网络共享密码" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter password" msgstr "输入密码" @@ -286,7 +286,7 @@ msgstr "输入密码" msgid "Enter your GitHub username" msgstr "输入您的 GitHub 用户名" -#: system/ui/widgets/list_view.py +#: openpilot/system/ui/widgets/list_view.py msgid "Error" msgstr "错误" @@ -298,7 +298,7 @@ msgstr "实验模式" msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "此车型当前无法使用实验模式,因为纵向控制使用的是原厂 ACC。" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "FORGETTING..." msgstr "正在遗忘..." @@ -311,14 +311,15 @@ msgid "Firehose" msgstr "数据洪流" #: openpilot/selfdrive/ui/layouts/settings/firehose.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Firehose Mode" msgstr "Firehose 模式" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget" msgstr "忘记" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "要忘记 Wi‑Fi 网络“{}”吗?" @@ -334,7 +335,7 @@ msgstr "在手机上前往 https://connect.comma.ai" msgid "HIGH" msgstr "高" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Hidden Network" msgstr "隐藏网络" @@ -342,7 +343,7 @@ msgstr "隐藏网络" msgid "INSTALL" msgstr "安装" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "IP Address" msgstr "IP 地址" @@ -362,6 +363,10 @@ msgstr "加载中" msgid "LTE" msgstr "LTE" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "纵向操作模式" @@ -398,9 +403,8 @@ msgstr "暂无发行说明。" msgid "OFFLINE" msgstr "离线" -#: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/html_render.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/html_render.py msgid "OK" msgstr "确定" @@ -453,11 +457,11 @@ msgstr "请连接 Wi‑Fi 以完成初始配对" msgid "Power Off" msgstr "关机" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "在计量制 Wi‑Fi 连接时避免大量上传" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在计量制蜂窝网络时避免大量上传" @@ -549,11 +553,11 @@ msgstr "选择" msgid "SSH Keys" msgstr "SSH 密钥" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "正在扫描 Wi‑Fi 网络…" -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Select" msgstr "选择" @@ -597,7 +601,7 @@ msgstr "温度" msgid "Target Branch" msgstr "目标分支" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Tethering Password" msgstr "网络共享密码" @@ -665,11 +669,11 @@ msgstr "启用后,踩下加速踏板将会脱离 openpilot。" msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 计量网络" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wrong password" msgstr "密码错误" @@ -693,7 +697,7 @@ msgstr "检查中..." msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "default" msgstr "默认" @@ -713,7 +717,7 @@ msgstr "检查更新失败" msgid "finalizing update..." msgstr "正在完成更新..." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "for \"{}\"" msgstr "用于“{}”" @@ -721,7 +725,7 @@ msgstr "用于“{}”" msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "留空以自动配置" @@ -729,7 +733,7 @@ msgstr "留空以自动配置" msgid "left" msgstr "左" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "metered" msgstr "计量" @@ -765,7 +769,7 @@ msgstr "openpilot 要求设备安装在左右 4°、上 5° 或下 9° 以内。 msgid "right" msgstr "右" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "unmetered" msgstr "不限流量" @@ -809,17 +813,7 @@ msgid_plural "{} minutes ago" msgstr[0] "{} 分钟前" msgstr[1] "{} 分钟前" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" -msgstr[1] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" - #: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ 已订阅" -#: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 Firehose 模式 🔥" - diff --git a/openpilot/selfdrive/ui/translations/app_zh-CHT.po b/openpilot/selfdrive/ui/translations/app_zh-CHT.po index 93f9b9ed8e..39a67b432d 100644 --- a/openpilot/selfdrive/ui/translations/app_zh-CHT.po +++ b/openpilot/selfdrive/ui/translations/app_zh-CHT.po @@ -52,7 +52,7 @@ msgstr "

    轉向延遲校正已完成 {}%。" msgid "ADD" msgstr "新增" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "APN Setting" msgstr "APN 設定" @@ -60,7 +60,7 @@ msgstr "APN 設定" msgid "Acknowledge Excessive Actuation" msgstr "確認過度作動" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Advanced" msgstr "進階" @@ -93,7 +93,7 @@ msgid "Are you sure you want to uninstall?" msgstr "確定要解除安裝嗎?" #: openpilot/selfdrive/ui/layouts/onboarding.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Back" msgstr "返回" @@ -118,22 +118,22 @@ msgid "CHILL MODE ON" msgstr "安穩模式已開啟" #: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECT" msgstr "連線" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "CONNECTING..." msgstr "連線中..." -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/keyboard.py -#: system/ui/widgets/network.py -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/keyboard.py +#: openpilot/system/ui/widgets/network.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "取消" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Cellular Metered" msgstr "行動網路計量" @@ -213,7 +213,7 @@ msgstr "車內鏡頭" msgid "Driving Personality" msgstr "駕駛風格" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "EDIT" msgstr "編輯" @@ -242,7 +242,7 @@ msgstr "啟用 ADB" msgid "Enable Lane Departure Warnings" msgstr "啟用偏離車道警示" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Roaming" msgstr "啟用漫遊" @@ -250,7 +250,7 @@ msgstr "啟用漫遊" msgid "Enable SSH" msgstr "啟用 SSH" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enable Tethering" msgstr "啟用網路共享" @@ -266,19 +266,19 @@ msgstr "啟用 openpilot" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "啟用 openpilot 縱向控制(alpha)切換,以使用實驗模式。" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter APN" msgstr "輸入 APN" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter SSID" msgstr "輸入 SSID" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "輸入新的網路共享密碼" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Enter password" msgstr "輸入密碼" @@ -286,7 +286,7 @@ msgstr "輸入密碼" msgid "Enter your GitHub username" msgstr "輸入您的 GitHub 使用者名稱" -#: system/ui/widgets/list_view.py +#: openpilot/system/ui/widgets/list_view.py msgid "Error" msgstr "錯誤" @@ -298,7 +298,7 @@ msgstr "實驗模式" msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "此車款目前無法使用實驗模式,因為縱向控制使用的是原廠 ACC。" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "FORGETTING..." msgstr "正在遺忘..." @@ -311,14 +311,15 @@ msgid "Firehose" msgstr "資料洪流" #: openpilot/selfdrive/ui/layouts/settings/firehose.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Firehose Mode" msgstr "Firehose 模式" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget" msgstr "忘記" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "要忘記 Wi‑Fi 網路「{}」嗎?" @@ -334,7 +335,7 @@ msgstr "在手機上前往 https://connect.comma.ai" msgid "HIGH" msgstr "高" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Hidden Network" msgstr "隱藏網路" @@ -342,7 +343,7 @@ msgstr "隱藏網路" msgid "INSTALL" msgstr "安裝" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "IP Address" msgstr "IP 位址" @@ -362,6 +363,10 @@ msgstr "載入中" msgid "LTE" msgstr "LTE" +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Lateral Maneuver Mode" +msgstr "" + #: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "縱向操作模式" @@ -398,9 +403,8 @@ msgstr "無可用發行說明。" msgid "OFFLINE" msgstr "離線" -#: openpilot/selfdrive/ui/layouts/sidebar.py -#: system/ui/widgets/confirm_dialog.py -#: system/ui/widgets/html_render.py +#: openpilot/system/ui/widgets/confirm_dialog.py +#: openpilot/system/ui/widgets/html_render.py msgid "OK" msgstr "確定" @@ -453,11 +457,11 @@ msgstr "請連線至 Wi‑Fi 以完成初始化配對" msgid "Power Off" msgstr "關機" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "在計量制 Wi‑Fi 連線時避免大量上傳" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在計量制行動網路時避免大量上傳" @@ -549,11 +553,11 @@ msgstr "選取" msgid "SSH Keys" msgstr "SSH 金鑰" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "正在掃描 Wi‑Fi 網路…" -#: system/ui/widgets/option_dialog.py +#: openpilot/system/ui/widgets/option_dialog.py msgid "Select" msgstr "選取" @@ -597,7 +601,7 @@ msgstr "溫度" msgid "Target Branch" msgstr "目標分支" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Tethering Password" msgstr "網路共享密碼" @@ -665,11 +669,11 @@ msgstr "啟用後,踩下加速踏板將會脫離 openpilot。" msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 計量網路" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "Wrong password" msgstr "密碼錯誤" @@ -693,7 +697,7 @@ msgstr "檢查中..." msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "default" msgstr "預設" @@ -713,7 +717,7 @@ msgstr "檢查更新失敗" msgid "finalizing update..." msgstr "正在完成更新..." -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "for \"{}\"" msgstr "適用於「{}」" @@ -721,7 +725,7 @@ msgstr "適用於「{}」" msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "留空以自動設定" @@ -729,7 +733,7 @@ msgstr "留空以自動設定" msgid "left" msgstr "左" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "metered" msgstr "計量" @@ -765,7 +769,7 @@ msgstr "openpilot 要求裝置安裝在左右 4°、上 5° 或下 9° 以內。 msgid "right" msgstr "右" -#: system/ui/widgets/network.py +#: openpilot/system/ui/widgets/network.py msgid "unmetered" msgstr "不限流量" @@ -809,17 +813,7 @@ msgid_plural "{} minutes ago" msgstr[0] "{} 分鐘前" msgstr[1] "{} 分鐘前" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "目前已有 {} 個您的駕駛片段納入訓練資料集。" -msgstr[1] "目前已有 {} 個您的駕駛片段納入訓練資料集。" - #: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ 已訂閱" -#: openpilot/selfdrive/ui/widgets/setup.py -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 Firehose 模式 🔥" - From 9738db0e392f58a78359cfa4da6727783b7ce742 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 8 Aug 2026 09:32:47 -0700 Subject: [PATCH 145/170] Move camera stream types out of msgq (#38591) --- msgq_repo | 2 +- openpilot/cereal/visionipc.py | 8 ++++++++ openpilot/cereal/visionstream.h | 10 ++++++++++ openpilot/selfdrive/modeld/dmonitoringmodeld.py | 3 ++- openpilot/selfdrive/modeld/modeld.py | 3 ++- openpilot/selfdrive/selfdrived/selfdrived.py | 3 ++- openpilot/selfdrive/test/process_replay/vision_meta.py | 2 +- .../selfdrive/ui/mici/onroad/augmented_road_view.py | 2 +- openpilot/selfdrive/ui/mici/onroad/cameraview.py | 6 ++++-- .../selfdrive/ui/mici/onroad/driver_camera_dialog.py | 2 +- openpilot/selfdrive/ui/onroad/augmented_road_view.py | 2 +- openpilot/selfdrive/ui/onroad/cameraview.py | 6 ++++-- openpilot/selfdrive/ui/onroad/driver_camera_dialog.py | 2 +- openpilot/selfdrive/ui/tests/profile_onroad.py | 3 ++- openpilot/selfdrive/ui/watch3.py | 2 +- openpilot/system/camerad/cameras/hw.h | 1 + openpilot/system/camerad/snapshot.py | 3 ++- openpilot/system/camerad/webcam/camerad.py | 3 ++- openpilot/system/loggerd/loggerd.h | 1 + openpilot/system/loggerd/tests/test_loggerd.py | 3 ++- openpilot/tools/cabana/cameraview.h | 1 + openpilot/tools/camerastream/compressed_vipc.py | 3 ++- openpilot/tools/clip/run.py | 3 ++- openpilot/tools/replay/camera.h | 1 + openpilot/tools/replay/ui.py | 2 +- openpilot/tools/sim/lib/camerad.py | 3 ++- 26 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 openpilot/cereal/visionipc.py create mode 100644 openpilot/cereal/visionstream.h diff --git a/msgq_repo b/msgq_repo index deecc5d246..0e266c1dbc 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit deecc5d246e79d91b62874d08b43be0cc023b241 +Subproject commit 0e266c1dbcf7328beee3e57b4a8688555387c877 diff --git a/openpilot/cereal/visionipc.py b/openpilot/cereal/visionipc.py new file mode 100644 index 0000000000..d3c842e58e --- /dev/null +++ b/openpilot/cereal/visionipc.py @@ -0,0 +1,8 @@ +from enum import IntEnum + + +class VisionStreamType(IntEnum): + VISION_STREAM_ROAD = 0 + VISION_STREAM_DRIVER = 1 + VISION_STREAM_WIDE_ROAD = 2 + VISION_STREAM_MAP = 3 diff --git a/openpilot/cereal/visionstream.h b/openpilot/cereal/visionstream.h new file mode 100644 index 0000000000..36105ee046 --- /dev/null +++ b/openpilot/cereal/visionstream.h @@ -0,0 +1,10 @@ +#pragma once + +#include "msgq/visionipc/visionbuf.h" + +enum VisionStreamValues : VisionStreamType { + VISION_STREAM_ROAD = 0, + VISION_STREAM_DRIVER = 1, + VISION_STREAM_WIDE_ROAD = 2, + VISION_STREAM_MAP = 3, +}; diff --git a/openpilot/selfdrive/modeld/dmonitoringmodeld.py b/openpilot/selfdrive/modeld/dmonitoringmodeld.py index 554407a223..1ccc0b2a89 100755 --- a/openpilot/selfdrive/modeld/dmonitoringmodeld.py +++ b/openpilot/selfdrive/modeld/dmonitoringmodeld.py @@ -8,7 +8,8 @@ import numpy as np from openpilot.cereal import messaging from openpilot.cereal.messaging import PubMaster, SubMaster -from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcClient, VisionBuf from openpilot.common.swaglog import cloudlog from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics diff --git a/openpilot/selfdrive/modeld/modeld.py b/openpilot/selfdrive/modeld/modeld.py index be08c9c4c7..ef61c58333 100755 --- a/openpilot/selfdrive/modeld/modeld.py +++ b/openpilot/selfdrive/modeld/modeld.py @@ -13,7 +13,8 @@ from openpilot.cereal import log from opendbc.car.structs import car from openpilot.cereal.messaging import PubMaster, SubMaster from openpilot.cereal.services import SERVICE_LIST -from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcClient, VisionBuf from opendbc.car.car_helpers import get_demo_car_params from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index 6df72666c0..2ca462a9e6 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -7,7 +7,8 @@ import openpilot.cereal.messaging as messaging from openpilot.cereal import log from opendbc.car.structs import car -from msgq.visionipc import VisionIpcClient, VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcClient from openpilot.common.params import Params diff --git a/openpilot/selfdrive/test/process_replay/vision_meta.py b/openpilot/selfdrive/test/process_replay/vision_meta.py index 12deb58724..28f2829650 100644 --- a/openpilot/selfdrive/test/process_replay/vision_meta.py +++ b/openpilot/selfdrive/test/process_replay/vision_meta.py @@ -1,5 +1,5 @@ from collections import namedtuple -from msgq.visionipc import VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType from openpilot.common.realtime import DT_MDL, DT_DMON from openpilot.common.transformations.camera import DEVICE_CAMERAS diff --git a/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py b/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py index 25061f12ae..92f6b835e3 100644 --- a/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -2,7 +2,7 @@ import numpy as np import pyray as rl from openpilot.cereal import log from opendbc.car.structs import car -from msgq.visionipc import VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.selfdrive.ui.mici.onroad import SIDE_PANEL_WIDTH from openpilot.selfdrive.ui.mici.onroad.alert_renderer import AlertRenderer diff --git a/openpilot/selfdrive/ui/mici/onroad/cameraview.py b/openpilot/selfdrive/ui/mici/onroad/cameraview.py index 4ccfb04687..64dd6d023d 100644 --- a/openpilot/selfdrive/ui/mici/onroad/cameraview.py +++ b/openpilot/selfdrive/ui/mici/onroad/cameraview.py @@ -2,7 +2,8 @@ import platform import numpy as np import pyray as rl -from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcClient, VisionBuf from openpilot.common.swaglog import cloudlog from openpilot.common.hardware import COMMA_HARDWARE from openpilot.system.ui.lib.application import gui_app @@ -110,7 +111,7 @@ class CameraView(Widget): self._name = name # Primary stream self.client = VisionIpcClient(name, stream_type, conflate=True) - self._stream_type = stream_type + self._stream_type: VisionStreamType = stream_type self.available_streams: list[VisionStreamType] = [] # Target stream for switching @@ -370,6 +371,7 @@ class CameraView(Widget): del self.client # Switch to target + assert self._target_client is not None and self._target_stream_type is not None self.client = self._target_client self._stream_type = self._target_stream_type self._texture_needs_update = True diff --git a/openpilot/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/openpilot/selfdrive/ui/mici/onroad/driver_camera_dialog.py index e81877b402..24276c42b8 100644 --- a/openpilot/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/openpilot/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -1,6 +1,6 @@ import pyray as rl from openpilot.cereal import log, messaging -from msgq.visionipc import VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.ui_state import ui_state, device diff --git a/openpilot/selfdrive/ui/onroad/augmented_road_view.py b/openpilot/selfdrive/ui/onroad/augmented_road_view.py index 1b0c84f0fe..ff0cfb5eea 100644 --- a/openpilot/selfdrive/ui/onroad/augmented_road_view.py +++ b/openpilot/selfdrive/ui/onroad/augmented_road_view.py @@ -1,7 +1,7 @@ import numpy as np import pyray as rl from openpilot.cereal import log -from msgq.visionipc import VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType from openpilot.selfdrive.ui import UI_BORDER_SIZE from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.selfdrive.ui.onroad.alert_renderer import AlertRenderer diff --git a/openpilot/selfdrive/ui/onroad/cameraview.py b/openpilot/selfdrive/ui/onroad/cameraview.py index 1fed2dd683..3b6a2fcd30 100644 --- a/openpilot/selfdrive/ui/onroad/cameraview.py +++ b/openpilot/selfdrive/ui/onroad/cameraview.py @@ -2,7 +2,8 @@ import platform import numpy as np import pyray as rl -from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcClient, VisionBuf from openpilot.common.swaglog import cloudlog from openpilot.common.hardware import COMMA_HARDWARE from openpilot.system.ui.lib.application import gui_app @@ -71,7 +72,7 @@ class CameraView(Widget): self._name = name # Primary stream self.client = VisionIpcClient(name, stream_type, conflate=True) - self._stream_type = stream_type + self._stream_type: VisionStreamType = stream_type self.available_streams: list[VisionStreamType] = [] # Target stream for switching @@ -323,6 +324,7 @@ class CameraView(Widget): del self.client # Switch to target + assert self._target_client is not None and self._target_stream_type is not None self.client = self._target_client self._stream_type = self._target_stream_type self._texture_needs_update = True diff --git a/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py b/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py index 7e07e44210..8735774458 100644 --- a/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +++ b/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py @@ -1,6 +1,6 @@ import numpy as np import pyray as rl -from msgq.visionipc import VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType from openpilot.selfdrive.ui.onroad.cameraview import CameraView from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.ui_state import ui_state, device diff --git a/openpilot/selfdrive/ui/tests/profile_onroad.py b/openpilot/selfdrive/ui/tests/profile_onroad.py index ec15e71c35..7c73bfa97b 100755 --- a/openpilot/selfdrive/ui/tests/profile_onroad.py +++ b/openpilot/selfdrive/ui/tests/profile_onroad.py @@ -5,7 +5,8 @@ import cProfile import pyray as rl import numpy as np -from msgq.visionipc import VisionIpcServer, VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcServer from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout from openpilot.system.ui.lib.application import gui_app diff --git a/openpilot/selfdrive/ui/watch3.py b/openpilot/selfdrive/ui/watch3.py index bb64cdc4d5..162df82658 100755 --- a/openpilot/selfdrive/ui/watch3.py +++ b/openpilot/selfdrive/ui/watch3.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import pyray as rl -from msgq.visionipc import VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.onroad.cameraview import CameraView diff --git a/openpilot/system/camerad/cameras/hw.h b/openpilot/system/camerad/cameras/hw.h index be0bea872d..45546258a9 100644 --- a/openpilot/system/camerad/cameras/hw.h +++ b/openpilot/system/camerad/cameras/hw.h @@ -2,6 +2,7 @@ #include "common/util.h" #include "openpilot/cereal/gen/cpp/log.capnp.h" +#include "openpilot/cereal/visionstream.h" #include "msgq/visionipc/visionipc_server.h" #include "media/cam_isp_ife.h" diff --git a/openpilot/system/camerad/snapshot.py b/openpilot/system/camerad/snapshot.py index 8383865fce..f865a452ff 100755 --- a/openpilot/system/camerad/snapshot.py +++ b/openpilot/system/camerad/snapshot.py @@ -3,7 +3,8 @@ import numpy as np import openpilot.cereal.messaging as messaging -from msgq.visionipc import VisionIpcClient, VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcClient from openpilot.common.realtime import DT_MDL diff --git a/openpilot/system/camerad/webcam/camerad.py b/openpilot/system/camerad/webcam/camerad.py index c46482360d..bba982f897 100755 --- a/openpilot/system/camerad/webcam/camerad.py +++ b/openpilot/system/camerad/webcam/camerad.py @@ -4,7 +4,8 @@ import os import platform from collections import namedtuple -from msgq.visionipc import VisionIpcServer, VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcServer from openpilot.cereal import messaging from openpilot.system.camerad.webcam.camera import Camera diff --git a/openpilot/system/loggerd/loggerd.h b/openpilot/system/loggerd/loggerd.h index 4d33a77095..c6c7b41af1 100644 --- a/openpilot/system/loggerd/loggerd.h +++ b/openpilot/system/loggerd/loggerd.h @@ -5,6 +5,7 @@ #include "openpilot/cereal/messaging/messaging.h" #include "openpilot/cereal/services.h" +#include "openpilot/cereal/visionstream.h" #include "msgq/visionipc/visionipc_client.h" #include "common/hardware/hw.h" #include "common/params.h" diff --git a/openpilot/system/loggerd/tests/test_loggerd.py b/openpilot/system/loggerd/tests/test_loggerd.py index 039bb17064..333891ad91 100644 --- a/openpilot/system/loggerd/tests/test_loggerd.py +++ b/openpilot/system/loggerd/tests/test_loggerd.py @@ -25,7 +25,8 @@ from openpilot.system.manager.process_config import managed_processes from openpilot.common.version import get_version from openpilot.tools.lib.helpers import RE from openpilot.tools.lib.logreader import LogReader -from msgq.visionipc import VisionIpcServer, VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcServer SentinelType = log.Sentinel.SentinelType diff --git a/openpilot/tools/cabana/cameraview.h b/openpilot/tools/cabana/cameraview.h index 40d3776005..3b55dd9ed9 100644 --- a/openpilot/tools/cabana/cameraview.h +++ b/openpilot/tools/cabana/cameraview.h @@ -10,6 +10,7 @@ #include #include +#include "openpilot/cereal/visionstream.h" #include "msgq/visionipc/visionipc_client.h" class CameraWidget : public QWidget { diff --git a/openpilot/tools/camerastream/compressed_vipc.py b/openpilot/tools/camerastream/compressed_vipc.py index 56ed12889f..2ec538981b 100755 --- a/openpilot/tools/camerastream/compressed_vipc.py +++ b/openpilot/tools/camerastream/compressed_vipc.py @@ -8,7 +8,8 @@ from collections import deque import openpilot.cereal.messaging as messaging -from msgq.visionipc import VisionIpcServer, VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcServer from openpilot.tools.camerastream.ffmpeg_decoder import Decoder, FFmpegError V4L2_BUF_FLAG_KEYFRAME = 8 diff --git a/openpilot/tools/clip/run.py b/openpilot/tools/clip/run.py index 594383f7fd..6c847ca342 100755 --- a/openpilot/tools/clip/run.py +++ b/openpilot/tools/clip/run.py @@ -22,7 +22,8 @@ from openpilot.tools.lib.framereader import FrameReader, ffprobe from openpilot.selfdrive.test.process_replay.migration import migrate_all from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.utils import Timer -from msgq.visionipc import VisionIpcServer, VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcServer FRAMERATE = 20 DEMO_ROUTE, DEMO_START, DEMO_END = '5beb9b58bd12b691/0000010a--a51155e496', 90, 105 diff --git a/openpilot/tools/replay/camera.h b/openpilot/tools/replay/camera.h index 9433018848..35fab5f7d3 100644 --- a/openpilot/tools/replay/camera.h +++ b/openpilot/tools/replay/camera.h @@ -5,6 +5,7 @@ #include #include +#include "openpilot/cereal/visionstream.h" #include "msgq/visionipc/visionipc_server.h" #include "common/queue.h" #include "tools/replay/framereader.h" diff --git a/openpilot/tools/replay/ui.py b/openpilot/tools/replay/ui.py index 4fbede2be9..2c253a1364 100755 --- a/openpilot/tools/replay/ui.py +++ b/openpilot/tools/replay/ui.py @@ -21,7 +21,7 @@ from openpilot.tools.replay.lib.ui_helpers import ( plot_lead, plot_model, ) -from msgq.visionipc import VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView os.environ['BASEDIR'] = BASEDIR diff --git a/openpilot/tools/sim/lib/camerad.py b/openpilot/tools/sim/lib/camerad.py index 8efb3e5dab..d4825eada7 100644 --- a/openpilot/tools/sim/lib/camerad.py +++ b/openpilot/tools/sim/lib/camerad.py @@ -1,6 +1,7 @@ import numpy as np -from msgq.visionipc import VisionIpcServer, VisionStreamType +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcServer from openpilot.cereal import messaging from openpilot.tools.sim.lib.common import W, H From fedea9fe8d489fd73f3a93ef707656bf10c94eb9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 8 Aug 2026 09:44:21 -0700 Subject: [PATCH 146/170] camera names that make sense (#38585) --- docs/CONTRIBUTING.md | 2 +- docs/LIMITATIONS.md | 4 +- docs/concepts/logs.md | 6 +-- docs/contributing/feedback.md | 6 +-- openpilot/cereal/log.capnp | 24 ++++----- openpilot/cereal/services.py | 24 ++++----- openpilot/cereal/visionipc.py | 4 +- openpilot/cereal/visionstream.h | 4 +- openpilot/common/transformations/camera.py | 8 +-- .../selfdrive/modeld/dmonitoringmodeld.py | 4 +- openpilot/selfdrive/modeld/modeld.py | 20 +++---- openpilot/selfdrive/monitoring/policy.py | 12 ++--- openpilot/selfdrive/pandad/pandad.cc | 18 +++---- openpilot/selfdrive/selfdrived/events.py | 2 +- openpilot/selfdrive/selfdrived/selfdrived.py | 12 ++--- .../selfdrive/test/process_replay/README.md | 4 +- .../test/process_replay/migration.py | 8 +-- .../test/process_replay/model_replay.py | 11 ++-- .../test/process_replay/process_replay.py | 20 +++---- .../test/process_replay/vision_meta.py | 10 ++-- openpilot/selfdrive/test/test_onroad.py | 18 +++---- openpilot/selfdrive/test/test_power_draw.py | 2 +- .../selfdrive/ui/layouts/settings/device.py | 8 +-- .../selfdrive/ui/layouts/settings/toggles.py | 4 +- .../selfdrive/ui/mici/layouts/onboarding.py | 8 +-- .../ui/mici/layouts/settings/device.py | 10 ++-- .../ui/mici/layouts/settings/toggles.py | 2 +- .../ui/mici/onroad/augmented_road_view.py | 18 +++---- ...amera_dialog.py => cabin_camera_dialog.py} | 16 +++--- .../selfdrive/ui/mici/onroad/cameraview.py | 8 +-- .../ui/mici/tests/test_widget_leaks.py | 12 ++--- .../ui/onroad/augmented_road_view.py | 18 +++---- ...amera_dialog.py => cabin_camera_dialog.py} | 12 ++--- openpilot/selfdrive/ui/onroad/cameraview.py | 6 +-- .../selfdrive/ui/tests/diff/replay_script.py | 2 +- .../selfdrive/ui/tests/profile_onroad.py | 4 +- openpilot/selfdrive/ui/translations/app.pot | 16 +++--- openpilot/selfdrive/ui/translations/app_de.po | 22 ++++---- openpilot/selfdrive/ui/translations/app_en.po | 22 ++++---- openpilot/selfdrive/ui/translations/app_es.po | 22 ++++---- openpilot/selfdrive/ui/translations/app_fr.po | 22 ++++---- openpilot/selfdrive/ui/translations/app_ja.po | 22 ++++---- openpilot/selfdrive/ui/translations/app_ko.po | 22 ++++---- .../selfdrive/ui/translations/app_pt-BR.po | 22 ++++---- openpilot/selfdrive/ui/translations/app_th.po | 22 ++++---- openpilot/selfdrive/ui/translations/app_tr.po | 22 ++++---- openpilot/selfdrive/ui/translations/app_uk.po | 22 ++++---- .../selfdrive/ui/translations/app_zh-CHS.po | 22 ++++---- .../selfdrive/ui/translations/app_zh-CHT.po | 22 ++++---- openpilot/selfdrive/ui/ui_state.py | 2 +- openpilot/selfdrive/ui/watch3.py | 4 +- .../system/camerad/cameras/camera_qcom2.cc | 4 +- openpilot/system/camerad/cameras/hw.h | 18 +++---- openpilot/system/camerad/snapshot.py | 6 +-- openpilot/system/camerad/test/test_camerad.py | 14 ++--- openpilot/system/camerad/webcam/camerad.py | 6 +-- openpilot/system/loggerd/clip_encoder.cc | 4 +- openpilot/system/loggerd/loggerd.h | 54 +++++++++---------- .../system/loggerd/tests/test_encoder.py | 4 +- .../system/loggerd/tests/test_loggerd.py | 12 ++--- openpilot/system/webrtc/device/video.py | 4 +- .../webrtc/tests/test_stream_session.py | 2 +- openpilot/tools/cabana/README.md | 4 +- openpilot/tools/cabana/cabana.cc | 20 +++---- openpilot/tools/cabana/cameraview.cc | 4 +- .../tools/cabana/streams/replaystream.cc | 6 +-- openpilot/tools/cabana/tools/routeinfo.cc | 6 +-- openpilot/tools/cabana/videowidget.cc | 2 +- .../tools/camerastream/compressed_vipc.py | 8 +-- openpilot/tools/clip/run.py | 4 +- openpilot/tools/jotpluggler/app.h | 4 +- openpilot/tools/jotpluggler/common.h | 2 +- .../jotpluggler/layouts/camera-timings.json | 2 +- .../jotpluggler/layouts/cameras-and-map.json | 2 +- .../layouts/driver-monitoring-debug.json | 2 +- openpilot/tools/jotpluggler/runtime.cc | 22 ++++---- openpilot/tools/jotpluggler/sketch_layout.cc | 20 +++---- openpilot/tools/lib/log_time_series.py | 4 +- .../plotjuggler/layouts/camera-timings.xml | 37 +++++++------ openpilot/tools/replay/README.md | 8 +-- openpilot/tools/replay/camera.h | 4 +- openpilot/tools/replay/logreader.cc | 4 +- openpilot/tools/replay/logreader.h | 2 +- openpilot/tools/replay/main.cc | 16 +++--- openpilot/tools/replay/replay.cc | 6 +-- openpilot/tools/replay/replay.h | 4 +- openpilot/tools/replay/route.cc | 12 ++--- openpilot/tools/replay/route.h | 4 +- openpilot/tools/replay/ui.py | 10 ++-- openpilot/tools/replay/util.h | 4 +- openpilot/tools/sim/lib/camerad.py | 6 +-- tools/scripts/cycle_alerts.py | 4 +- 92 files changed, 502 insertions(+), 496 deletions(-) rename openpilot/selfdrive/ui/mici/onroad/{driver_camera_dialog.py => cabin_camera_dialog.py} (96%) rename openpilot/selfdrive/ui/onroad/{driver_camera_dialog.py => cabin_camera_dialog.py} (92%) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index cbeb5f6d3a..1d9338e457 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -58,7 +58,7 @@ A good pull request has all of the following: * Report bugs in GitHub issues. * Report driving issues in the `#driving-feedback` Discord channel. -* Consider opting into driver camera uploads to improve the driver monitoring model. +* Consider opting into cabin camera uploads to improve the driver monitoring model. * Connect your device to Wi-Fi regularly, so that we can pull data for training better driving models. * Run the `nightly` branch and report issues. This branch is like `master` but it's built just like a release. * Annotate images in the [comma10k dataset](https://github.com/commaai/comma10k). diff --git a/docs/LIMITATIONS.md b/docs/LIMITATIONS.md index 8c112659c2..5f9b7517bd 100644 --- a/docs/LIMITATIONS.md +++ b/docs/LIMITATIONS.md @@ -52,7 +52,7 @@ Many factors can impact the performance of openpilot DM, causing it to be unable * Low light conditions, such as driving at night or in dark tunnels. * Bright light (due to oncoming headlights, direct sunlight, etc.). -* The driver's face is partially or completely outside field of view of the driver facing camera. -* The driver facing camera is obstructed, covered, or damaged. +* The driver's face is partially or completely outside field of view of the cabin camera. +* The cabin camera is obstructed, covered, or damaged. The list above does not represent an exhaustive list of situations that may interfere with proper operation of openpilot components. A driver should not rely on openpilot DM to assess their level of attention. diff --git a/docs/concepts/logs.md b/docs/concepts/logs.md index dd954d4c76..a1e655935d 100644 --- a/docs/concepts/logs.md +++ b/docs/concepts/logs.md @@ -10,13 +10,13 @@ For each segment, openpilot records the following log types: rlogs contain all the messages passed amongst openpilot's processes. See [openpilot/cereal/services.py](https://github.com/commaai/openpilot/blob/master/openpilot/cereal/services.py) for a list of all the logged services. They're a zstd archive of the serialized [Cap’n Proto](https://capnproto.org/) messages. -## {f,e,d}camera.hevc +## camera video files Each camera stream is H.265 encoded and written to its respective file. -* `fcamera.hevc` is the road camera +* `fcamera.hevc` is the narrow road camera (the main forward camera) * `ecamera.hevc` is the wide road camera -* `dcamera.hevc` is the driver camera +* `dcamera.hevc` is the cabin camera ## qlog.zst & qcamera.ts diff --git a/docs/contributing/feedback.md b/docs/contributing/feedback.md index 335d24e13a..587816d140 100644 --- a/docs/contributing/feedback.md +++ b/docs/contributing/feedback.md @@ -21,14 +21,14 @@ In general, driver monitoring feedback is very actionable, and we can fix your c To post your feedback: 1. Join the [community Discord](https://discord.comma.ai). -2. If driver camera recording is toggled off, temporarily enable driver camera recording in the settings until you reproduce the issue. -3. Using comma connect, identify the relevant segment and upload the segment's logs and driver camera. +2. If cabin camera recording is toggled off, temporarily enable cabin camera recording in the settings until you reproduce the issue. +3. Using comma connect, identify the relevant segment and upload the segment's logs and cabin camera. 4. Post the segment in the `#openpilot-experience` channel on Discord with a good description. Before posting feedback, please ensure: - **openpilot is up to date** you should be on the latest openpilot release or nightly -- **the driver camera has a clear view of the driver** ensure nothing blocks view of the driver (e.g. a cable), the lens is clean, etc. +- **the cabin camera has a clear view of the driver** ensure nothing blocks view of the driver (e.g. a cable), the lens is clean, etc. - **your device is mounted properly** your device must be mounted horizontally center and relatively high on the windshield ## Other bugs diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp index da700b58a7..fa185aa483 100644 --- a/openpilot/cereal/log.capnp +++ b/openpilot/cereal/log.capnp @@ -2562,17 +2562,17 @@ struct Event { driverStateV2 @92 :DriverStateV2; # camera stuff, each camera state has a matching encode idx - roadCameraState @2 :FrameData; - driverCameraState @70: FrameData; + narrowRoadCameraState @2 :FrameData; + cabinCameraState @70: FrameData; wideRoadCameraState @74: FrameData; - roadEncodeIdx @15 :EncodeIndex; - driverEncodeIdx @76 :EncodeIndex; + narrowRoadEncodeIdx @15 :EncodeIndex; + cabinEncodeIdx @76 :EncodeIndex; wideRoadEncodeIdx @77 :EncodeIndex; - qRoadEncodeIdx @90 :EncodeIndex; + qNarrowRoadEncodeIdx @90 :EncodeIndex; - livestreamRoadEncodeIdx @117 :EncodeIndex; + livestreamNarrowRoadEncodeIdx @117 :EncodeIndex; livestreamWideRoadEncodeIdx @118 :EncodeIndex; - livestreamDriverEncodeIdx @119 :EncodeIndex; + livestreamCabinEncodeIdx @119 :EncodeIndex; # microphone data soundPressure @103 :SoundPressure; @@ -2602,15 +2602,15 @@ struct Event { # *********** debug *********** testJoystick @52 :Joystick; - roadEncodeData @86 :EncodeData; - driverEncodeData @87 :EncodeData; + narrowRoadEncodeData @86 :EncodeData; + cabinEncodeData @87 :EncodeData; wideRoadEncodeData @88 :EncodeData; - qRoadEncodeData @89 :EncodeData; + qNarrowRoadEncodeData @89 :EncodeData; alertDebug @133 :DebugAlert; - livestreamRoadEncodeData @120 :EncodeData; + livestreamNarrowRoadEncodeData @120 :EncodeData; livestreamWideRoadEncodeData @121 :EncodeData; - livestreamDriverEncodeData @122 :EncodeData; + livestreamCabinEncodeData @122 :EncodeData; # *********** Custom: reserved for forks *********** diff --git a/openpilot/cereal/services.py b/openpilot/cereal/services.py index a41a890f97..61fb8fcbd2 100755 --- a/openpilot/cereal/services.py +++ b/openpilot/cereal/services.py @@ -33,7 +33,7 @@ _services: dict[str, tuple] = { "pandaStates": (True, 10., 1), "peripheralState": (True, 2., 1), "radarState": (True, 20., 5), - "roadEncodeIdx": (False, 20., 1), + "narrowRoadEncodeIdx": (False, 20., 1), "liveTracks": (True, 20.), "sendcan": (True, 100., 139, QueueSize.MEDIUM), "logMessage": (True, 0., None, QueueSize.BIG), @@ -61,9 +61,9 @@ _services: dict[str, tuple] = { "thumbnail": (True, 1 / 60., 1), "onroadEvents": (True, 1., 1), "carParams": (True, 0.02, 1), - "roadCameraState": (True, 20., 20), - "driverCameraState": (True, 20., 20), - "driverEncodeIdx": (False, 20., 1), + "narrowRoadCameraState": (True, 20., 20), + "cabinCameraState": (True, 20., 20), + "cabinEncodeIdx": (False, 20., 1), "driverStateV2": (True, 20., 10), "driverMonitoringState": (True, 20., 10), "wideRoadEncodeIdx": (False, 20., 1), @@ -71,26 +71,26 @@ _services: dict[str, tuple] = { "drivingModelData": (True, 20., 10), "modelV2": (True, 20., None, QueueSize.BIG), "managerState": (True, 2., 1), - "qRoadEncodeIdx": (False, 20.), + "qNarrowRoadEncodeIdx": (False, 20.), "userBookmark": (True, 0., 1), "soundPressure": (True, 10., 10), "rawAudioData": (False, 20.), "bookmarkButton": (True, 0., 1), - "roadEncodeData": (False, 20., None, QueueSize.BIG), - "driverEncodeData": (False, 20., None, QueueSize.BIG), + "narrowRoadEncodeData": (False, 20., None, QueueSize.BIG), + "cabinEncodeData": (False, 20., None, QueueSize.BIG), "wideRoadEncodeData": (False, 20., None, QueueSize.BIG), - "qRoadEncodeData": (False, 20., None, QueueSize.BIG), + "qNarrowRoadEncodeData": (False, 20., None, QueueSize.BIG), # debug "uiDebug": (True, 0., 1), "testJoystick": (True, 0.), "alertDebug": (True, 20., 5), "livestreamWideRoadEncodeIdx": (False, 20.), - "livestreamRoadEncodeIdx": (False, 20.), - "livestreamDriverEncodeIdx": (False, 20.), + "livestreamNarrowRoadEncodeIdx": (False, 20.), + "livestreamCabinEncodeIdx": (False, 20.), "livestreamWideRoadEncodeData": (False, 20., None, QueueSize.MEDIUM), - "livestreamRoadEncodeData": (False, 20., None, QueueSize.MEDIUM), - "livestreamDriverEncodeData": (False, 20., None, QueueSize.MEDIUM), + "livestreamNarrowRoadEncodeData": (False, 20., None, QueueSize.MEDIUM), + "livestreamCabinEncodeData": (False, 20., None, QueueSize.MEDIUM), "customReservedRawData0": (True, 0.), } SERVICE_LIST = {name: Service(*vals) for diff --git a/openpilot/cereal/visionipc.py b/openpilot/cereal/visionipc.py index d3c842e58e..74439babb9 100644 --- a/openpilot/cereal/visionipc.py +++ b/openpilot/cereal/visionipc.py @@ -2,7 +2,7 @@ from enum import IntEnum class VisionStreamType(IntEnum): - VISION_STREAM_ROAD = 0 - VISION_STREAM_DRIVER = 1 + VISION_STREAM_NARROW_ROAD = 0 + VISION_STREAM_CABIN = 1 VISION_STREAM_WIDE_ROAD = 2 VISION_STREAM_MAP = 3 diff --git a/openpilot/cereal/visionstream.h b/openpilot/cereal/visionstream.h index 36105ee046..adaff7b477 100644 --- a/openpilot/cereal/visionstream.h +++ b/openpilot/cereal/visionstream.h @@ -3,8 +3,8 @@ #include "msgq/visionipc/visionbuf.h" enum VisionStreamValues : VisionStreamType { - VISION_STREAM_ROAD = 0, - VISION_STREAM_DRIVER = 1, + VISION_STREAM_NARROW_ROAD = 0, + VISION_STREAM_CABIN = 1, VISION_STREAM_WIDE_ROAD = 2, VISION_STREAM_MAP = 3, }; diff --git a/openpilot/common/transformations/camera.py b/openpilot/common/transformations/camera.py index ada9c5b398..17ac226cdb 100644 --- a/openpilot/common/transformations/camera.py +++ b/openpilot/common/transformations/camera.py @@ -37,12 +37,12 @@ class _NoneCameraConfig(CameraConfig): @dataclass(frozen=True) class DeviceCameraConfig: - fcam: CameraConfig - dcam: CameraConfig - ecam: CameraConfig + narrow_road: CameraConfig + cabin: CameraConfig + wide_road: CameraConfig def all_cams(self): - for cam in ['fcam', 'dcam', 'ecam']: + for cam in ['narrow_road', 'cabin', 'wide_road']: if not isinstance(getattr(self, cam), _NoneCameraConfig): yield cam, getattr(self, cam) diff --git a/openpilot/selfdrive/modeld/dmonitoringmodeld.py b/openpilot/selfdrive/modeld/dmonitoringmodeld.py index 1ccc0b2a89..02391aeee3 100755 --- a/openpilot/selfdrive/modeld/dmonitoringmodeld.py +++ b/openpilot/selfdrive/modeld/dmonitoringmodeld.py @@ -110,8 +110,8 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t def main(): config_realtime_process(7, 5) - cloudlog.warning("connecting to driver stream") - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) + cloudlog.warning("connecting to cabin stream") + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_CABIN, True) while not vipc_client.connect(False): time.sleep(0.1) assert vipc_client.is_connected() diff --git a/openpilot/selfdrive/modeld/modeld.py b/openpilot/selfdrive/modeld/modeld.py index ef61c58333..f724e530e2 100755 --- a/openpilot/selfdrive/modeld/modeld.py +++ b/openpilot/selfdrive/modeld/modeld.py @@ -215,12 +215,12 @@ def main(demo=False): while True: available_streams = VisionIpcClient.available_streams("camerad", block=False) if available_streams: - use_extra_client = VisionStreamType.VISION_STREAM_WIDE_ROAD in available_streams and VisionStreamType.VISION_STREAM_ROAD in available_streams - main_wide_camera = VisionStreamType.VISION_STREAM_ROAD not in available_streams + use_extra_client = VisionStreamType.VISION_STREAM_WIDE_ROAD in available_streams and VisionStreamType.VISION_STREAM_NARROW_ROAD in available_streams + main_wide_camera = VisionStreamType.VISION_STREAM_NARROW_ROAD not in available_streams break time.sleep(.1) - vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD + vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_NARROW_ROAD vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True) vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False) cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") @@ -262,7 +262,7 @@ def main(demo=False): # messaging pub_socks = ["modelV2", "drivingModelData", "cameraOdometry"] + (["chestnutState"] if USBGPU else []) pm = PubMaster(pub_socks) - sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"]) + sm = SubMaster(["deviceState", "carState", "narrowRoadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"]) publish_state = PublishState() params = Params() @@ -330,15 +330,17 @@ def main(demo=False): sm.update(0) desire = DH.desire is_rhd = sm["driverMonitoringState"].isRHD - frame_id = sm["roadCameraState"].frameId + frame_id = sm["narrowRoadCameraState"].frameId v_ego = max(sm["carState"].vEgo, 0.) lat_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS - if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: + if sm.updated["liveCalibration"] and sm.seen['narrowRoadCameraState'] and sm.seen['deviceState']: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) - dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] - model_transform_main = get_warp_matrix(device_from_calib_euler, dc.ecam.intrinsics if main_wide_camera else dc.fcam.intrinsics, False).astype(np.float32) + dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['narrowRoadCameraState'].sensor))] + main_intrinsics = dc.wide_road.intrinsics if main_wide_camera else dc.narrow_road.intrinsics + model_transform_main = get_warp_matrix(device_from_calib_euler, main_intrinsics, False).astype(np.float32) has_wide_camera = use_extra_client or main_wide_camera - model_transform_extra = get_warp_matrix(device_from_calib_euler, dc.ecam.intrinsics if has_wide_camera else dc.fcam.intrinsics, True).astype(np.float32) + extra_intrinsics = dc.wide_road.intrinsics if has_wide_camera else dc.narrow_road.intrinsics + model_transform_extra = get_warp_matrix(device_from_calib_euler, extra_intrinsics, True).astype(np.float32) live_calib_seen = True traffic_convention = np.zeros(2) diff --git a/openpilot/selfdrive/monitoring/policy.py b/openpilot/selfdrive/monitoring/policy.py index 1ce9f03b3e..b58f1e66dc 100644 --- a/openpilot/selfdrive/monitoring/policy.py +++ b/openpilot/selfdrive/monitoring/policy.py @@ -107,17 +107,17 @@ class DriverBlink: self.right = 0. # model output refers to center of undistorted+leveled image -ref_undistorted_cam = DEVICE_CAMERAS[("tici", "ar0231")].dcam -dcam_undistorted_FL = 598.0 -dcam_undistorted_W, dcam_undistorted_H = (ref_undistorted_cam.width, ref_undistorted_cam.height) +ref_undistorted_cam = DEVICE_CAMERAS[("tici", "ar0231")].cabin +cabin_undistorted_FL = 598.0 +cabin_undistorted_W, cabin_undistorted_H = (ref_undistorted_cam.width, ref_undistorted_cam.height) def face_orientation_from_model(orient_model, pos_model, rpy_calib): pitch_model = orient_model[0] yaw_model = orient_model[1] - face_pixel_position = ((pos_model[0]+0.5)*dcam_undistorted_W, (pos_model[1]+0.5)*dcam_undistorted_H) - yaw_focal_angle = atan2(face_pixel_position[0] - dcam_undistorted_W//2, dcam_undistorted_FL) - pitch_focal_angle = atan2(face_pixel_position[1] - dcam_undistorted_H//2, dcam_undistorted_FL) + face_pixel_position = ((pos_model[0]+0.5)*cabin_undistorted_W, (pos_model[1]+0.5)*cabin_undistorted_H) + yaw_focal_angle = atan2(face_pixel_position[0] - cabin_undistorted_W//2, cabin_undistorted_FL) + pitch_focal_angle = atan2(face_pixel_position[1] - cabin_undistorted_H//2, cabin_undistorted_FL) pitch = pitch_model + pitch_focal_angle yaw = -yaw_model + yaw_focal_angle diff --git a/openpilot/selfdrive/pandad/pandad.cc b/openpilot/selfdrive/pandad/pandad.cc index 68d74f81a1..78d12bd2ed 100644 --- a/openpilot/selfdrive/pandad/pandad.cc +++ b/openpilot/selfdrive/pandad/pandad.cc @@ -278,9 +278,9 @@ void process_panda_state(Panda *panda, PubMaster *pm, bool engaged, bool is_onro void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control, bool is_onroad) { static Params params; - static SubMaster sm({"deviceState", "driverCameraState"}); + static SubMaster sm({"deviceState", "cabinCameraState"}); - static uint64_t last_driver_camera_t = 0; + static uint64_t last_cabin_camera_t = 0; static uint16_t prev_fan_speed = 999; static int ir_pwr = 0; static int prev_ir_pwr = 999; @@ -304,20 +304,20 @@ void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control, } } - if (sm.updated("driverCameraState")) { - auto event = sm["driverCameraState"]; - int cur_integ_lines = event.getDriverCameraState().getIntegLines(); + if (sm.updated("cabinCameraState")) { + auto event = sm["cabinCameraState"]; + int cur_integ_lines = event.getCabinCameraState().getIntegLines(); // reset the filter when camerad restarts - if (event.getDriverCameraState().getFrameId() < prev_frame_id) { + if (event.getCabinCameraState().getFrameId() < prev_frame_id) { integ_lines_filter.reset(0); integ_lines_filter_driver_view.reset(0); driver_view = params.getBool("IsDriverViewEnabled"); } - prev_frame_id = event.getDriverCameraState().getFrameId(); + prev_frame_id = event.getCabinCameraState().getFrameId(); cur_integ_lines = (driver_view ? integ_lines_filter_driver_view : integ_lines_filter).update(cur_integ_lines); - last_driver_camera_t = event.getLogMonoTime(); + last_cabin_camera_t = event.getLogMonoTime(); if (cur_integ_lines <= CUTOFF_IL) { ir_pwr = 0; @@ -329,7 +329,7 @@ void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control, } // Disable IR on input timeout - if (nanos_since_boot() - last_driver_camera_t > 1e9) { + if (nanos_since_boot() - last_cabin_camera_t > 1e9) { ir_pwr = 0; } diff --git a/openpilot/selfdrive/selfdrived/events.py b/openpilot/selfdrive/selfdrived/events.py index c9bae1f1d3..0464647c3b 100755 --- a/openpilot/selfdrive/selfdrived/events.py +++ b/openpilot/selfdrive/selfdrived/events.py @@ -297,7 +297,7 @@ def comm_issue_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaste def camera_malfunction_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: - all_cams = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState') + all_cams = ('narrowRoadCameraState', 'cabinCameraState', 'wideRoadCameraState') bad_cams = [s.replace('State', '') for s in all_cams if s in sm.data.keys() and not sm.all_checks([s, ])] return NormalPermanentAlert("Camera Malfunction", ', '.join(bad_cams)) diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index 2ca462a9e6..59c5f2695e 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -77,17 +77,17 @@ class SelfdriveD: self.gps_location_service = get_gps_location_service(self.params) self.gps_packets = [self.gps_location_service] self.sensor_packets = ["accelerometer", "gyroscope"] - self.camera_packets = ["roadCameraState", "driverCameraState", "wideRoadCameraState"] + self.camera_packets = ["narrowRoadCameraState", "cabinCameraState", "wideRoadCameraState"] # 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'] if SIMULATION: - ignore += ['driverCameraState', 'managerState'] + ignore += ['cabinCameraState', 'managerState'] if REPLAY: # no vipc in replay will make them ignored anyways - ignore += ['roadCameraState', 'wideRoadCameraState'] + ignore += ['narrowRoadCameraState', 'wideRoadCameraState'] self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', 'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay', 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', @@ -467,9 +467,9 @@ class SelfdriveD: timed_out = self.sm.frame * DT_CTRL > 6. if all_valid or timed_out or (SIMULATION and not REPLAY): available_streams = VisionIpcClient.available_streams("camerad", block=False) - if VisionStreamType.VISION_STREAM_ROAD not in available_streams: - self.sm.ignore_alive.append('roadCameraState') - self.sm.ignore_valid.append('roadCameraState') + if VisionStreamType.VISION_STREAM_NARROW_ROAD not in available_streams: + self.sm.ignore_alive.append('narrowRoadCameraState') + self.sm.ignore_valid.append('narrowRoadCameraState') if VisionStreamType.VISION_STREAM_WIDE_ROAD not in available_streams: self.sm.ignore_alive.append('wideRoadCameraState') self.sm.ignore_valid.append('wideRoadCameraState') diff --git a/openpilot/selfdrive/test/process_replay/README.md b/openpilot/selfdrive/test/process_replay/README.md index 28f3b7cd2a..f39333a3c6 100644 --- a/openpilot/selfdrive/test/process_replay/README.md +++ b/openpilot/selfdrive/test/process_replay/README.md @@ -104,9 +104,9 @@ Replaying processes that use VisionIPC (e.g. modeld, dmonitoringmodeld) require from openpilot.tools.lib.framereader import FrameReader frs = { - 'roadCameraState': FrameReader(...), + 'narrowRoadCameraState': FrameReader(...), 'wideRoadCameraState': FrameReader(...), - 'driverCameraState': FrameReader(...), + 'cabinCameraState': FrameReader(...), } output_logs = replay_process_with_name(['modeld', 'dmonitoringmodeld'], lr, frs=frs) diff --git a/openpilot/selfdrive/test/process_replay/migration.py b/openpilot/selfdrive/test/process_replay/migration.py index e38d1e1042..ff98389e2c 100644 --- a/openpilot/selfdrive/test/process_replay/migration.py +++ b/openpilot/selfdrive/test/process_replay/migration.py @@ -361,7 +361,7 @@ def migrate_peripheralState(msgs): return [], add_ops, [] -@migration(inputs=["roadEncodeIdx", "wideRoadEncodeIdx", "driverEncodeIdx", "roadCameraState", "wideRoadCameraState", "driverCameraState"]) +@migration(inputs=["narrowRoadEncodeIdx", "wideRoadEncodeIdx", "cabinEncodeIdx", "narrowRoadCameraState", "wideRoadCameraState", "cabinCameraState"]) def migrate_cameraStates(msgs): add_ops, del_ops = [], [] frame_to_encode_id = defaultdict(dict) @@ -369,7 +369,7 @@ def migrate_cameraStates(msgs): min_frame_id = defaultdict(lambda: float('inf')) for _, msg in msgs: - if msg.which() not in ["roadEncodeIdx", "wideRoadEncodeIdx", "driverEncodeIdx"]: + if msg.which() not in ["narrowRoadEncodeIdx", "wideRoadEncodeIdx", "cabinEncodeIdx"]: continue encode_index = getattr(msg, msg.which()) @@ -379,7 +379,7 @@ def migrate_cameraStates(msgs): frame_to_encode_id[meta.camera_state][encode_index.frameId] = encode_index.segmentId for index, msg in msgs: - if msg.which() not in ["roadCameraState", "wideRoadCameraState", "driverCameraState"]: + if msg.which() not in ["narrowRoadCameraState", "wideRoadCameraState", "cabinCameraState"]: continue camera_state = getattr(msg, msg.which()) @@ -392,7 +392,7 @@ def migrate_cameraStates(msgs): del_ops.append(index) continue - # fallback mechanism for logs without encodeIdx (e.g. logs from before 2022 with dcamera recording disabled) + # fallback mechanism for logs without encodeIdx (e.g. logs from before 2022 with driver recording disabled) # try to fake encode_id by subtracting lowest frameId encode_id = camera_state.frameId - min_frame_id[msg.which()] print(f"Faking encodeId to {encode_id} for camera feed {msg.which()} with frameId: {camera_state.frameId}") diff --git a/openpilot/selfdrive/test/process_replay/model_replay.py b/openpilot/selfdrive/test/process_replay/model_replay.py index 3e5616e702..3b5038b24c 100755 --- a/openpilot/selfdrive/test/process_replay/model_replay.py +++ b/openpilot/selfdrive/test/process_replay/model_replay.py @@ -146,9 +146,10 @@ def trim_logs(logs, start_frame, end_frame, frs_types, include_all_types): def model_replay(lr, frs): # modeld is using frame pairs - modeld_logs = trim_logs(lr, START_FRAME, END_FRAME, {"roadCameraState", "wideRoadCameraState"}, - {"roadEncodeIdx", "wideRoadEncodeIdx", "carParams", "carState", "carControl", "can"}) - dmodeld_logs = trim_logs(lr, START_FRAME, END_FRAME, {"driverCameraState"}, {"driverEncodeIdx", "carParams", "can"}) + camera_states = {"narrowRoadCameraState", "wideRoadCameraState"} + modeld_logs = trim_logs(lr, START_FRAME, END_FRAME, camera_states, + {"narrowRoadEncodeIdx", "wideRoadEncodeIdx", "carParams", "carState", "carControl", "can"}) + dmodeld_logs = trim_logs(lr, START_FRAME, END_FRAME, {"cabinCameraState"}, {"cabinEncodeIdx", "carParams", "can"}) if not SEND_EXTRA_INPUTS: modeld_logs = [msg for msg in modeld_logs if msg.which() != 'liveCalibration'] @@ -209,8 +210,8 @@ def get_frames(): print(f"Failed to load frames from cache {cache_name}: {e}") frs = { - 'roadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "fcamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME), - 'driverCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "dcamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME), + 'narrowRoadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "fcamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME), + 'cabinCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "dcamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME), 'wideRoadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "ecamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME), } for fr in frs.values(): diff --git a/openpilot/selfdrive/test/process_replay/process_replay.py b/openpilot/selfdrive/test/process_replay/process_replay.py index 5d2e5a22fa..346d532140 100755 --- a/openpilot/selfdrive/test/process_replay/process_replay.py +++ b/openpilot/selfdrive/test/process_replay/process_replay.py @@ -396,7 +396,7 @@ class ModeldCameraSyncRcvCallback: def __call__(self, msg, cfg, frame): self.is_dual_camera = len(cfg.vision_pubs) == 2 - if msg.which() == "roadCameraState": + if msg.which() == "narrowRoadCameraState": self.road_present = True elif msg.which() == "wideRoadCameraState": self.wide_road_present = True @@ -434,7 +434,7 @@ CONFIGS = [ pubs=[ "carState", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState", "longitudinalPlan", "livePose", "liveDelay", "liveParameters", "radarState", "modelV2", - "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState", "liveTorqueParameters", + "cabinCameraState", "narrowRoadCameraState", "wideRoadCameraState", "managerState", "liveTorqueParameters", "accelerometer", "gyroscope", "carOutput", "gpsLocationExternal", "gpsLocation", "controlsState", "carControl", "driverAssistance", "alertDebug", ], @@ -549,28 +549,28 @@ CONFIGS = [ ), ProcessConfig( proc_name="modeld", - pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "liveDelay", "driverMonitoringState", "carState", "carControl"], + pubs=["deviceState", "narrowRoadCameraState", "wideRoadCameraState", "liveCalibration", "liveDelay", "driverMonitoringState", "carState", "carControl"], subs=["modelV2", "drivingModelData", "cameraOdometry"], ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime", "drivingModelData.frameDropPerc", "drivingModelData.modelExecutionTime"], should_recv_callback=ModeldCameraSyncRcvCallback(), tolerance=NUMPY_TOLERANCE, processing_time=0.020, - main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("roadCameraState").stream), - vision_pubs=["roadCameraState", "wideRoadCameraState"], + main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("narrowRoadCameraState").stream), + vision_pubs=["narrowRoadCameraState", "wideRoadCameraState"], ignore_alive_pubs=["wideRoadCameraState"], init_callback=get_car_params_callback, ), ProcessConfig( proc_name="dmonitoringmodeld", - pubs=["liveCalibration", "driverCameraState"], + pubs=["liveCalibration", "cabinCameraState"], subs=["driverStateV2"], ignore=["logMonoTime", "driverStateV2.modelExecutionTime", "driverStateV2.gpuExecutionTime"], - should_recv_callback=MessageBasedRcvCallback("driverCameraState"), + should_recv_callback=MessageBasedRcvCallback("cabinCameraState"), tolerance=NUMPY_TOLERANCE, processing_time=0.020, - main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("driverCameraState").stream), - vision_pubs=["driverCameraState"], - ignore_alive_pubs=["driverCameraState"], + main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("cabinCameraState").stream), + vision_pubs=["cabinCameraState"], + ignore_alive_pubs=["cabinCameraState"], ), ] diff --git a/openpilot/selfdrive/test/process_replay/vision_meta.py b/openpilot/selfdrive/test/process_replay/vision_meta.py index 28f2829650..6942d88367 100644 --- a/openpilot/selfdrive/test/process_replay/vision_meta.py +++ b/openpilot/selfdrive/test/process_replay/vision_meta.py @@ -4,14 +4,14 @@ from openpilot.common.realtime import DT_MDL, DT_DMON from openpilot.common.transformations.camera import DEVICE_CAMERAS VideoStreamMeta = namedtuple("VideoStreamMeta", ["camera_state", "encode_index", "stream", "dt", "frame_sizes"]) -ROAD_CAMERA_FRAME_SIZES = {k: (v.dcam.width, v.dcam.height) for k, v in DEVICE_CAMERAS.items()} -WIDE_ROAD_CAMERA_FRAME_SIZES = {k: (v.ecam.width, v.ecam.height) for k, v in DEVICE_CAMERAS.items() if v.ecam is not None} -DRIVER_CAMERA_FRAME_SIZES = {k: (v.dcam.width, v.dcam.height) for k, v in DEVICE_CAMERAS.items()} +NARROW_ROAD_CAMERA_FRAME_SIZES = {k: (v.narrow_road.width, v.narrow_road.height) for k, v in DEVICE_CAMERAS.items()} +WIDE_ROAD_CAMERA_FRAME_SIZES = {k: (v.wide_road.width, v.wide_road.height) for k, v in DEVICE_CAMERAS.items() if v.wide_road is not None} +CABIN_CAMERA_FRAME_SIZES = {k: (v.cabin.width, v.cabin.height) for k, v in DEVICE_CAMERAS.items()} VIPC_STREAM_METADATA = [ # metadata: (state_msg_type, encode_msg_type, stream_type, dt, frame_sizes) - ("roadCameraState", "roadEncodeIdx", VisionStreamType.VISION_STREAM_ROAD, DT_MDL, ROAD_CAMERA_FRAME_SIZES), + ("narrowRoadCameraState", "narrowRoadEncodeIdx", VisionStreamType.VISION_STREAM_NARROW_ROAD, DT_MDL, NARROW_ROAD_CAMERA_FRAME_SIZES), ("wideRoadCameraState", "wideRoadEncodeIdx", VisionStreamType.VISION_STREAM_WIDE_ROAD, DT_MDL, WIDE_ROAD_CAMERA_FRAME_SIZES), - ("driverCameraState", "driverEncodeIdx", VisionStreamType.VISION_STREAM_DRIVER, DT_DMON, DRIVER_CAMERA_FRAME_SIZES), + ("cabinCameraState", "cabinEncodeIdx", VisionStreamType.VISION_STREAM_CABIN, DT_DMON, CABIN_CAMERA_FRAME_SIZES), ] diff --git a/openpilot/selfdrive/test/test_onroad.py b/openpilot/selfdrive/test/test_onroad.py index 2ee876a6ef..fb8cd1393f 100755 --- a/openpilot/selfdrive/test/test_onroad.py +++ b/openpilot/selfdrive/test/test_onroad.py @@ -83,8 +83,8 @@ TIMINGS = { "controlsState": [2.5, 0.35], "longitudinalPlan": [2.5, 0.5], "driverAssistance": [2.5, 0.5], - "roadCameraState": [2.5, 0.35], - "driverCameraState": [2.5, 0.35], + "narrowRoadCameraState": [2.5, 0.35], + "cabinCameraState": [2.5, 0.35], "modelV2": [2.5, 0.35], "driverStateV2": [2.5, 0.40], "livePose": [2.5, 0.35], @@ -304,7 +304,7 @@ class TestOnroad(OpenpilotTestCase): result += "------------------------------------------------\n" result += "----------------- SOF Timing ------------------\n" result += "------------------------------------------------\n" - for name in ['roadCameraState', 'wideRoadCameraState', 'driverCameraState']: + for name in ['narrowRoadCameraState', 'wideRoadCameraState', 'cabinCameraState']: ts = self.ts[name]['timestampSof'] d_ms = np.diff(ts) / 1e6 d50 = np.abs(d_ms-50) @@ -317,8 +317,8 @@ class TestOnroad(OpenpilotTestCase): print(result) def test_camera_sync(self, subtests): - cam_states = ['roadCameraState', 'wideRoadCameraState', 'driverCameraState'] - encode_cams = ['roadEncodeIdx', 'wideRoadEncodeIdx', 'driverEncodeIdx'] + cam_states = ['narrowRoadCameraState', 'wideRoadCameraState', 'cabinCameraState'] + encode_cams = ['narrowRoadEncodeIdx', 'wideRoadEncodeIdx', 'cabinEncodeIdx'] for cams in (cam_states, encode_cams): with subtests.test(cams=cams): # sanity checks within a single cam @@ -349,15 +349,15 @@ class TestOnroad(OpenpilotTestCase): diff = (max(ts.values()) - min(ts.values())) assert diff < 2, f"Cameras not synced properly: frame_id={start+i}, {diff=:.1f}ms, {ts=}" - # driver camera should be staggered ~25ms from road camera + # cabin camera should be staggered ~25ms from road camera offset_ms = abs(self.ts[cams[2]]['timestampSof'][i] - self.ts[cams[0]]['timestampSof'][i]) / 1e6 - assert 20 < offset_ms < 30, f"driver camera stagger out of range at frame {start+i}: {offset_ms:.1f}ms" + assert 20 < offset_ms < 30, f"cabin camera stagger out of range at frame {start+i}: {offset_ms:.1f}ms" def test_camera_encoder_matches(self, subtests): # sanity check that the frame metadata is consistent with the encoded frames - pairs = [('roadCameraState', 'roadEncodeIdx'), + pairs = [('narrowRoadCameraState', 'narrowRoadEncodeIdx'), ('wideRoadCameraState', 'wideRoadEncodeIdx'), - ('driverCameraState', 'driverEncodeIdx')] + ('cabinCameraState', 'cabinEncodeIdx')] for cam, enc in pairs: with subtests.test(camera=cam, encoder=enc): cam_frames = {fid: (sof, eof) for fid, sof, eof in zip( diff --git a/openpilot/selfdrive/test/test_power_draw.py b/openpilot/selfdrive/test/test_power_draw.py index 4fba2e8c99..8cad68978f 100755 --- a/openpilot/selfdrive/test/test_power_draw.py +++ b/openpilot/selfdrive/test/test_power_draw.py @@ -34,7 +34,7 @@ class Proc: PROCS = [ - Proc(['camerad'], 1.65, atol=0.4, msgs=['roadCameraState', 'wideRoadCameraState', 'driverCameraState']), + Proc(['camerad'], 1.65, atol=0.4, msgs=['narrowRoadCameraState', 'wideRoadCameraState', 'cabinCameraState']), Proc(['modeld'], 1.5, atol=0.2, msgs=['modelV2']), Proc(['dmonitoringmodeld'], 0.65, atol=0.35, msgs=['driverStateV2']), Proc(['encoderd'], 0.23, msgs=[]), diff --git a/openpilot/selfdrive/ui/layouts/settings/device.py b/openpilot/selfdrive/ui/layouts/settings/device.py index 22853b0bd9..ddc5c23160 100644 --- a/openpilot/selfdrive/ui/layouts/settings/device.py +++ b/openpilot/selfdrive/ui/layouts/settings/device.py @@ -5,7 +5,7 @@ from openpilot.cereal import messaging, log from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog +from openpilot.selfdrive.ui.onroad.cabin_camera_dialog import CabinCameraDialog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.layouts.onboarding import TrainingGuide from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog @@ -21,7 +21,7 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller # Description constants DESCRIPTIONS = { 'pair_device': tr_noop("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."), - 'driver_camera': tr_noop("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)"), + 'cabin_camera': tr_noop("Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)"), 'reset_calibration': tr_noop("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."), 'review_guide': tr_noop("Review the rules, features, and limitations of openpilot"), } @@ -57,8 +57,8 @@ class DeviceLayout(Widget): text_item(lambda: tr("Dongle ID"), self._params.get("DongleId") or (lambda: tr("N/A"))), text_item(lambda: tr("Serial"), self._params.get("HardwareSerial") or (lambda: tr("N/A"))), self._pair_device_btn, - button_item(lambda: tr("Driver Camera"), lambda: tr("PREVIEW"), lambda: tr(DESCRIPTIONS['driver_camera']), - callback=lambda: gui_app.push_widget(DriverCameraDialog()), enabled=ui_state.is_offroad), + button_item(lambda: tr("Cabin Camera"), lambda: tr("PREVIEW"), lambda: tr(DESCRIPTIONS['cabin_camera']), + callback=lambda: gui_app.push_widget(CabinCameraDialog()), enabled=ui_state.is_offroad), self._reset_calib_btn, button_item(lambda: tr("Review Training Guide"), lambda: tr("REVIEW"), lambda: tr(DESCRIPTIONS['review_guide']), self._on_review_training_guide, enabled=ui_state.is_offroad), diff --git a/openpilot/selfdrive/ui/layouts/settings/toggles.py b/openpilot/selfdrive/ui/layouts/settings/toggles.py index b03ecd10ec..090c49f14e 100644 --- a/openpilot/selfdrive/ui/layouts/settings/toggles.py +++ b/openpilot/selfdrive/ui/layouts/settings/toggles.py @@ -28,7 +28,7 @@ DESCRIPTIONS = { "without a turn signal activated while driving over 31 mph (50 km/h)." ), "AlwaysOnDM": tr_noop("Enable driver monitoring even when openpilot is not engaged."), - 'RecordFront': tr_noop("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), + 'RecordFront': tr_noop("Upload data from the cabin camera and help improve the driver monitoring algorithm."), "IsMetric": tr_noop("Display speed in km/h instead of mph."), "RecordAudio": tr_noop("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."), } @@ -73,7 +73,7 @@ class TogglesLayout(Widget): False, ), "RecordFront": ( - lambda: tr("Record and Upload Driver Camera"), + lambda: tr("Record and Upload Cabin Camera"), DESCRIPTIONS["RecordFront"], "monitoring.png", True, diff --git a/openpilot/selfdrive/ui/mici/layouts/onboarding.py b/openpilot/selfdrive/ui/mici/layouts/onboarding.py index f1ebd3bacd..2b448ac73d 100644 --- a/openpilot/selfdrive/ui/mici/layouts/onboarding.py +++ b/openpilot/selfdrive/ui/mici/layouts/onboarding.py @@ -16,10 +16,10 @@ from openpilot.common.version import terms_version, training_version from openpilot.selfdrive.ui.ui_state import ui_state, device from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationCircleButton from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer -from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog +from openpilot.selfdrive.ui.mici.onroad.cabin_camera_dialog import BaseCabinCameraDialog -class DriverCameraSetupDialog(BaseDriverCameraDialog): +class CabinCameraSetupDialog(BaseCabinCameraDialog): def __init__(self): super().__init__() self.driver_state_renderer = DriverStateRenderer(inset=True) @@ -104,7 +104,7 @@ class TrainingGuideDMTutorial(NavWidget): self._good_button.set_enabled(False) self._progress = FirstOrderFilter(0.0, 0.5, 1 / gui_app.target_fps) - self._dialog = DriverCameraSetupDialog() + self._dialog = CabinCameraSetupDialog() self._bad_face_page = DMBadFaceDetected() # Disable driver monitoring model when device times out for inactivity @@ -231,7 +231,7 @@ class TrainingGuideRecordFront(NavScroller): exit_on_confirm=False) self._scroller.add_widgets([ - GreyBigButton("driver camera data", "do you want to share video data for training?", + GreyBigButton("cabin camera data", "do you want to share video data for training?", gui_app.texture("icons_mici/setup/green_dm.png", 64, 64)), GreyBigButton("", "Sharing your data with comma helps improve openpilot for everyone."), self._accept_button, diff --git a/openpilot/selfdrive/ui/mici/layouts/settings/device.py b/openpilot/selfdrive/ui/mici/layouts/settings/device.py index 7f4a9ab0b8..6038ff7d35 100644 --- a/openpilot/selfdrive/ui/mici/layouts/settings/device.py +++ b/openpilot/selfdrive/ui/mici/layouts/settings/device.py @@ -9,7 +9,7 @@ from openpilot.system.ui.widgets.scroller import NavRawScrollPanel, NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigCircleButton from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog -from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog +from openpilot.selfdrive.ui.mici.onroad.cabin_camera_dialog import CabinCameraDialog from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide, TermsPage from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr @@ -189,9 +189,9 @@ class DeviceLayoutMici(NavScroller): regulatory_btn = BigButton("regulatory info", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) regulatory_btn.set_click_callback(self._on_regulatory) - driver_cam_btn = BigButton("driver\ncamera preview", "", gui_app.texture("icons_mici/settings/device/cameras.png", 64, 64)) - driver_cam_btn.set_click_callback(lambda: gui_app.push_widget(DriverCameraDialog())) - driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) + cabin_cam_btn = BigButton("driver\ncamera preview", "", gui_app.texture("icons_mici/settings/device/cameras.png", 64, 64)) + cabin_cam_btn.set_click_callback(lambda: gui_app.push_widget(CabinCameraDialog())) + cabin_cam_btn.set_enabled(lambda: ui_state.is_offroad()) review_training_guide_btn = BigButton("review\ntraining guide", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTrainingGuide(completed_callback=lambda: gui_app.pop_widgets_to(self)))) @@ -204,7 +204,7 @@ class DeviceLayoutMici(NavScroller): DeviceInfoLayoutMici(), PairBigButton(), review_training_guide_btn, - driver_cam_btn, + cabin_cam_btn, terms_btn, regulatory_btn, reset_calibration_btn, diff --git a/openpilot/selfdrive/ui/mici/layouts/settings/toggles.py b/openpilot/selfdrive/ui/mici/layouts/settings/toggles.py index 57cbf9410b..4d5113448d 100644 --- a/openpilot/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/openpilot/selfdrive/ui/mici/layouts/settings/toggles.py @@ -47,7 +47,7 @@ class TogglesLayoutMici(NavScroller): is_metric_toggle = BigParamControl("use metric units", "IsMetric") ldw_toggle = BigParamControl("lane departure warnings", "IsLdwEnabled") always_on_dm_toggle = BigParamControl("always-on driver monitor", "AlwaysOnDM") - record_front = BigParamControl("record & upload driver camera", "RecordFront", toggle_callback=restart_needed_callback) + record_front = BigParamControl("record & upload cabin camera", "RecordFront", toggle_callback=restart_needed_callback) record_mic = BigParamControl("record & upload mic audio", "RecordAudio", toggle_callback=restart_needed_callback) enable_openpilot = BigParamControl("enable openpilot", "OpenpilotEnabledToggle", toggle_callback=restart_needed_callback) diff --git a/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py b/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py index 92f6b835e3..46348a9f16 100644 --- a/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -21,7 +21,7 @@ from enum import IntEnum OpState = log.SelfdriveState.OpenpilotState CALIBRATED = log.LiveCalibrationData.Status.calibrated -ROAD_CAM = VisionStreamType.VISION_STREAM_ROAD +NARROW_ROAD_CAM = VisionStreamType.VISION_STREAM_NARROW_ROAD WIDE_CAM = VisionStreamType.VISION_STREAM_WIDE_ROAD DEFAULT_DEVICE_CAMERA = DEVICE_CAMERAS["tici", "ar0231"] @@ -130,7 +130,7 @@ class BookmarkIcon(Widget): class AugmentedRoadView(CameraView): - def __init__(self, bookmark_callback=None, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD): + def __init__(self, bookmark_callback=None, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_NARROW_ROAD): super().__init__("camerad", stream_type) self._bookmark_callback = bookmark_callback self._set_placeholder_color(rl.BLACK) @@ -250,12 +250,12 @@ class AugmentedRoadView(CameraView): if v_ego < WIDE_CAM_MAX_SPEED: target = WIDE_CAM elif v_ego > ROAD_CAM_MIN_SPEED: - target = ROAD_CAM + target = NARROW_ROAD_CAM else: # Hysteresis zone - keep current stream target = self.stream_type else: - target = ROAD_CAM + target = NARROW_ROAD_CAM if self.stream_type != target: self.switch_stream(target) @@ -263,8 +263,8 @@ class AugmentedRoadView(CameraView): def _update_calibration(self): # Update device camera if not already set sm = ui_state.sm - if not self.device_camera and sm.seen['roadCameraState'] and sm.seen['deviceState']: - self.device_camera = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] + if not self.device_camera and sm.seen['narrowRoadCameraState'] and sm.seen['deviceState']: + self.device_camera = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['narrowRoadCameraState'].sensor))] # Check if live calibration data is available and valid if not (sm.updated["liveCalibration"] and sm.valid['liveCalibration']): @@ -298,7 +298,7 @@ class AugmentedRoadView(CameraView): # Get camera configuration device_camera = self.device_camera or DEFAULT_DEVICE_CAMERA is_wide_camera = self.stream_type == WIDE_CAM - intrinsic = device_camera.ecam.intrinsics if is_wide_camera else device_camera.fcam.intrinsics + intrinsic = device_camera.wide_road.intrinsics if is_wide_camera else device_camera.narrow_road.intrinsics calibration = self.view_from_wide_calib if is_wide_camera else self.view_from_calib if is_wide_camera: zoom = 0.7 * 1.5 @@ -353,14 +353,14 @@ class AugmentedRoadView(CameraView): if __name__ == "__main__": gui_app.init_window("OnRoad Camera View") - road_camera_view = AugmentedRoadView(lambda: None, stream_type=ROAD_CAM) + road_camera_view = AugmentedRoadView(lambda: None, stream_type=NARROW_ROAD_CAM) print("***press space to switch camera view***") try: for _ in gui_app.render(): ui_state.update() if rl.is_key_released(rl.KeyboardKey.KEY_SPACE): if WIDE_CAM in road_camera_view.available_streams: - stream = ROAD_CAM if road_camera_view.stream_type == WIDE_CAM else WIDE_CAM + stream = NARROW_ROAD_CAM if road_camera_view.stream_type == WIDE_CAM else WIDE_CAM road_camera_view.switch_stream(stream) road_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) finally: diff --git a/openpilot/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/openpilot/selfdrive/ui/mici/onroad/cabin_camera_dialog.py similarity index 96% rename from openpilot/selfdrive/ui/mici/onroad/driver_camera_dialog.py rename to openpilot/selfdrive/ui/mici/onroad/cabin_camera_dialog.py index 24276c42b8..e86c0aa739 100644 --- a/openpilot/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/openpilot/selfdrive/ui/mici/onroad/cabin_camera_dialog.py @@ -11,7 +11,7 @@ from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.label import gui_label -class DriverCameraView(CameraView): +class CabinCameraView(CameraView): def _calc_frame_matrix(self, rect: rl.Rectangle): base = super()._calc_frame_matrix(rect) driver_view_ratio = 1.5 @@ -20,11 +20,11 @@ class DriverCameraView(CameraView): return base -class BaseDriverCameraDialog(Widget): +class BaseCabinCameraDialog(Widget): # Not a NavWidget so training guide can use this without back navigation def __init__(self): super().__init__() - self._camera_view = DriverCameraView("camerad", VisionStreamType.VISION_STREAM_DRIVER) + self._camera_view = CabinCameraView("camerad", VisionStreamType.VISION_STREAM_CABIN) self.driver_state_renderer = DriverStateRenderer(lines=True) self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 200, 200)) self.driver_state_renderer.load_icons() @@ -229,7 +229,7 @@ class BaseDriverCameraDialog(Widget): rl.draw_texture_v(self._glasses_texture, glasses_pos, rl.Color(70, 80, 161, int(255 * glasses_prob))) -class DriverCameraDialog(NavWidget, BaseDriverCameraDialog): +class CabinCameraDialog(NavWidget, BaseCabinCameraDialog): def __init__(self): super().__init__() # TODO: this can grow unbounded, should be given some thought @@ -237,12 +237,12 @@ class DriverCameraDialog(NavWidget, BaseDriverCameraDialog): if __name__ == "__main__": - gui_app.init_window("Driver Camera View (mici)") + gui_app.init_window("Cabin Camera View (mici)") - driver_camera_view = DriverCameraDialog() - gui_app.push_widget(driver_camera_view) + cabin_camera_view = CabinCameraDialog() + gui_app.push_widget(cabin_camera_view) try: for _ in gui_app.render(): ui_state.update() finally: - driver_camera_view.close() + cabin_camera_view.close() diff --git a/openpilot/selfdrive/ui/mici/onroad/cameraview.py b/openpilot/selfdrive/ui/mici/onroad/cameraview.py index 64dd6d023d..8a3e2cbed5 100644 --- a/openpilot/selfdrive/ui/mici/onroad/cameraview.py +++ b/openpilot/selfdrive/ui/mici/onroad/cameraview.py @@ -126,7 +126,7 @@ class CameraView(Widget): self._engaged_loc = rl.get_shader_location(self.shader, "engaged") self._engaged_val = rl.ffi.new("int[1]", [1]) self._enhance_driver_loc = rl.get_shader_location(self.shader, "enhance_driver") - self._enhance_driver_val = rl.ffi.new("int[1]", [1 if stream_type == VisionStreamType.VISION_STREAM_DRIVER else 0]) + self._enhance_driver_val = rl.ffi.new("int[1]", [1 if stream_type == VisionStreamType.VISION_STREAM_CABIN else 0]) self.frame: VisionBuf | None = None self.texture_y: rl.Texture | None = None @@ -243,8 +243,8 @@ class CameraView(Widget): transform = self._calc_frame_matrix(rect) src_rect = rl.Rectangle(0, 0, float(self.frame.width), float(self.frame.height)) - # Flip driver camera horizontally - if self._stream_type == VisionStreamType.VISION_STREAM_DRIVER: + # Flip cabin camera horizontally + if self._stream_type == VisionStreamType.VISION_STREAM_CABIN: src_rect.width = -src_rect.width # Calculate scale @@ -410,6 +410,6 @@ class CameraView(Widget): if __name__ == "__main__": gui_app.init_window("camera view") - road = CameraView("camerad", VisionStreamType.VISION_STREAM_ROAD) + road = CameraView("camerad", VisionStreamType.VISION_STREAM_NARROW_ROAD) for _ in gui_app.render(): road.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) diff --git a/openpilot/selfdrive/ui/mici/tests/test_widget_leaks.py b/openpilot/selfdrive/ui/mici/tests/test_widget_leaks.py index 271cb2704f..c0f1ac1510 100755 --- a/openpilot/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/openpilot/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -4,13 +4,13 @@ import unittest # FIXME: known small leaks not worth worrying about at the moment KNOWN_LEAKS = { - "openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog.DriverCameraView", + "openpilot.selfdrive.ui.mici.onroad.cabin_camera_dialog.CabinCameraView", "openpilot.selfdrive.ui.mici.layouts.onboarding.TermsPage", "openpilot.selfdrive.ui.mici.layouts.onboarding.TrainingGuide", "openpilot.selfdrive.ui.mici.layouts.onboarding.DeclinePage", "openpilot.selfdrive.ui.mici.layouts.onboarding.OnboardingWindow", "openpilot.selfdrive.ui.onroad.driver_state.DriverStateRenderer", - "openpilot.selfdrive.ui.onroad.driver_camera_dialog.DriverCameraDialog", + "openpilot.selfdrive.ui.onroad.cabin_camera_dialog.CabinCameraDialog", "openpilot.selfdrive.ui.layouts.onboarding.TermsPage", "openpilot.selfdrive.ui.layouts.onboarding.DeclinePage", "openpilot.selfdrive.ui.layouts.onboarding.OnboardingWindow", @@ -51,13 +51,13 @@ class TestWidgetLeaks(OpenpilotTestCase): # mici dialogs from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow - from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog + from openpilot.selfdrive.ui.mici.onroad.cabin_camera_dialog import CabinCameraDialog as MiciCabinCameraDialog from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog, BigInputDialog from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal # tici dialogs - from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog as TiciDriverCameraDialog + from openpilot.selfdrive.ui.onroad.cabin_camera_dialog import CabinCameraDialog as TiciCabinCameraDialog from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow as TiciOnboardingWindow from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog as TiciPairingDialog from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog @@ -71,7 +71,7 @@ class TestWidgetLeaks(OpenpilotTestCase): for ctor in ( # mici - MiciDriverCameraDialog, MiciPairingDialog, + MiciCabinCameraDialog, MiciPairingDialog, lambda: MiciTrainingGuide(lambda: None), lambda: MiciOnboardingWindow(lambda: None), lambda: BigDialog("test", "test"), @@ -79,7 +79,7 @@ class TestWidgetLeaks(OpenpilotTestCase): lambda: BigInputDialog("test"), lambda: MiciFccModal(text="test"), # tici - TiciDriverCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard, + TiciCabinCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard, lambda: ConfirmDialog("test", "ok"), lambda: MultiOptionDialog("test", ["a", "b"]), lambda: HtmlModal(text="test"), diff --git a/openpilot/selfdrive/ui/onroad/augmented_road_view.py b/openpilot/selfdrive/ui/onroad/augmented_road_view.py index ff0cfb5eea..44d8910a03 100644 --- a/openpilot/selfdrive/ui/onroad/augmented_road_view.py +++ b/openpilot/selfdrive/ui/onroad/augmented_road_view.py @@ -15,7 +15,7 @@ from openpilot.common.transformations.orientation import rot_from_euler OpState = log.SelfdriveState.OpenpilotState CALIBRATED = log.LiveCalibrationData.Status.calibrated -ROAD_CAM = VisionStreamType.VISION_STREAM_ROAD +NARROW_ROAD_CAM = VisionStreamType.VISION_STREAM_NARROW_ROAD WIDE_CAM = VisionStreamType.VISION_STREAM_WIDE_ROAD DEFAULT_DEVICE_CAMERA = DEVICE_CAMERAS["tici", "ar0231"] @@ -31,7 +31,7 @@ INF_POINT = np.array([1000.0, 0.0, 0.0]) class AugmentedRoadView(CameraView): - def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD): + def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_NARROW_ROAD): super().__init__("camerad", stream_type) self._set_placeholder_color(BORDER_COLORS[UIStatus.DISENGAGED]) @@ -115,12 +115,12 @@ class AugmentedRoadView(CameraView): if v_ego < WIDE_CAM_MAX_SPEED: target = WIDE_CAM elif v_ego > ROAD_CAM_MIN_SPEED: - target = ROAD_CAM + target = NARROW_ROAD_CAM else: # Hysteresis zone - keep current stream target = self.stream_type else: - target = ROAD_CAM + target = NARROW_ROAD_CAM if self.stream_type != target: self.switch_stream(target) @@ -128,8 +128,8 @@ class AugmentedRoadView(CameraView): def _update_calibration(self): # Update device camera if not already set sm = ui_state.sm - if not self.device_camera and sm.seen['roadCameraState'] and sm.seen['deviceState']: - self.device_camera = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))] + if not self.device_camera and sm.seen['narrowRoadCameraState'] and sm.seen['deviceState']: + self.device_camera = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['narrowRoadCameraState'].sensor))] # Check if live calibration data is available and valid if not (sm.updated["liveCalibration"] and sm.valid['liveCalibration']): @@ -162,7 +162,7 @@ class AugmentedRoadView(CameraView): # Get camera configuration device_camera = self.device_camera or DEFAULT_DEVICE_CAMERA is_wide_camera = self.stream_type == WIDE_CAM - intrinsic = device_camera.ecam.intrinsics if is_wide_camera else device_camera.fcam.intrinsics + intrinsic = device_camera.wide_road.intrinsics if is_wide_camera else device_camera.narrow_road.intrinsics calibration = self.view_from_wide_calib if is_wide_camera else self.view_from_calib zoom = 2.0 if is_wide_camera else 1.1 @@ -213,7 +213,7 @@ class AugmentedRoadView(CameraView): if __name__ == "__main__": gui_app.init_window("OnRoad Camera View") - road_camera_view = AugmentedRoadView(ROAD_CAM) + road_camera_view = AugmentedRoadView(NARROW_ROAD_CAM) gui_app.push_widget(road_camera_view) print("***press space to switch camera view***") try: @@ -221,7 +221,7 @@ if __name__ == "__main__": ui_state.update() if rl.is_key_released(rl.KeyboardKey.KEY_SPACE): if WIDE_CAM in road_camera_view.available_streams: - stream = ROAD_CAM if road_camera_view.stream_type == WIDE_CAM else WIDE_CAM + stream = NARROW_ROAD_CAM if road_camera_view.stream_type == WIDE_CAM else WIDE_CAM road_camera_view.switch_stream(stream) finally: road_camera_view.close() diff --git a/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py b/openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py similarity index 92% rename from openpilot/selfdrive/ui/onroad/driver_camera_dialog.py rename to openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py index 8735774458..7bb1917c35 100644 --- a/openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +++ b/openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py @@ -9,9 +9,9 @@ from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets.label import gui_label -class DriverCameraDialog(CameraView): +class CabinCameraDialog(CameraView): def __init__(self): - super().__init__("camerad", VisionStreamType.VISION_STREAM_DRIVER) + super().__init__("camerad", VisionStreamType.VISION_STREAM_CABIN) self.driver_state_renderer = DriverStateRenderer() # TODO: this can grow unbounded, should be given some thought device.add_interactive_timeout_callback(gui_app.pop_widget) @@ -100,12 +100,12 @@ class DriverCameraDialog(CameraView): if __name__ == "__main__": - gui_app.init_window("Driver Camera View") + gui_app.init_window("Cabin Camera View") - driver_camera_view = DriverCameraDialog() - gui_app.push_widget(driver_camera_view) + cabin_camera_view = CabinCameraDialog() + gui_app.push_widget(cabin_camera_view) try: for _ in gui_app.render(): ui_state.update() finally: - driver_camera_view.close() + cabin_camera_view.close() diff --git a/openpilot/selfdrive/ui/onroad/cameraview.py b/openpilot/selfdrive/ui/onroad/cameraview.py index 3b6a2fcd30..aa4f2271dc 100644 --- a/openpilot/selfdrive/ui/onroad/cameraview.py +++ b/openpilot/selfdrive/ui/onroad/cameraview.py @@ -203,8 +203,8 @@ class CameraView(Widget): transform = self._calc_frame_matrix(rect) src_rect = rl.Rectangle(0, 0, float(self.frame.width), float(self.frame.height)) - # Flip driver camera horizontally - if self._stream_type == VisionStreamType.VISION_STREAM_DRIVER: + # Flip cabin camera horizontally + if self._stream_type == VisionStreamType.VISION_STREAM_CABIN: src_rect.width = -src_rect.width # Calculate scale @@ -363,6 +363,6 @@ class CameraView(Widget): if __name__ == "__main__": gui_app.init_window("camera view") - road = CameraView("camerad", VisionStreamType.VISION_STREAM_ROAD) + road = CameraView("camerad", VisionStreamType.VISION_STREAM_NARROW_ROAD) for _ in gui_app.render(): road.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) diff --git a/openpilot/selfdrive/ui/tests/diff/replay_script.py b/openpilot/selfdrive/ui/tests/diff/replay_script.py index 91e6d3b921..30e811dbc9 100644 --- a/openpilot/selfdrive/ui/tests/diff/replay_script.py +++ b/openpilot/selfdrive/ui/tests/diff/replay_script.py @@ -321,7 +321,7 @@ def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None: lambda: swipe_left(width * 2), click, # first page, click next lambda: swipe_left(width * 2), swipe_down # second page, go back (TODO: make driver cam preview work) ), - None, # TODO: preview driver camera; enabling this causes MultiplePublishersError later in onroad alert tests + None, # TODO: preview cabin camera; enabling this causes MultiplePublishersError later in onroad alert tests lambda: explore_setting(swipe_left), # terms & conditions (swipe to view QR code) lambda: explore_setting(lambda: swipe_up(height * 3), lambda: swipe_down(height * 3)), # regulatory info lambda: run_actions(click, lambda: swipe_left(width)), # reset calibration confirm (goes back automatically) diff --git a/openpilot/selfdrive/ui/tests/profile_onroad.py b/openpilot/selfdrive/ui/tests/profile_onroad.py index 7c73bfa97b..98816f9ddf 100755 --- a/openpilot/selfdrive/ui/tests/profile_onroad.py +++ b/openpilot/selfdrive/ui/tests/profile_onroad.py @@ -90,7 +90,7 @@ if __name__ == "__main__": W, H = 2048, 1216 vipc = VisionIpcServer("camerad") - vipc.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 5, W, H) + vipc.create_buffers(VisionStreamType.VISION_STREAM_NARROW_ROAD, 5, W, H) vipc.start_listener() yuv_buffer_size = W * H + (W // 2) * (H // 2) * 2 yuv_data = np.random.default_rng().integers(0, 256, yuv_buffer_size, dtype=np.uint8).tobytes() @@ -100,7 +100,7 @@ if __name__ == "__main__": break if ui_state.sm.frame % 3 == 0: eof = int((ui_state.sm.frame % 3) * 0.05 * 1e9) - vipc.send(VisionStreamType.VISION_STREAM_ROAD, yuv_data, ui_state.sm.frame % 3, eof, eof) + vipc.send(VisionStreamType.VISION_STREAM_NARROW_ROAD, yuv_data, ui_state.sm.frame % 3, eof, eof) ui_state.update() pr.dump_stats(f'{args.output}_deterministic.stats') diff --git a/openpilot/selfdrive/ui/translations/app.pot b/openpilot/selfdrive/ui/translations/app.pot index cb0491e107..cdd0af4c86 100644 --- a/openpilot/selfdrive/ui/translations/app.pot +++ b/openpilot/selfdrive/ui/translations/app.pot @@ -381,7 +381,7 @@ msgid "ERROR" msgstr "" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "" #: openpilot/selfdrive/ui/layouts/settings/device.py @@ -449,7 +449,7 @@ msgid "Serial" msgstr "" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" +msgid "Cabin Camera" msgstr "" #: openpilot/selfdrive/ui/layouts/settings/device.py @@ -704,7 +704,7 @@ msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py @@ -748,7 +748,7 @@ msgid "Always-On Driver Monitoring" msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" +msgid "Record and Upload Cabin Camera" msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py @@ -779,10 +779,6 @@ msgstr "" msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "" -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py -msgid "camera starting" -msgstr "" - #: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" msgstr "" @@ -795,6 +791,10 @@ msgstr "" msgid "mph" msgstr "" +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py +msgid "camera starting" +msgstr "" + #: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" msgstr "" diff --git a/openpilot/selfdrive/ui/translations/app_de.po b/openpilot/selfdrive/ui/translations/app_de.po index 43baa133ae..1a3cd51ac4 100644 --- a/openpilot/selfdrive/ui/translations/app_de.po +++ b/openpilot/selfdrive/ui/translations/app_de.po @@ -126,6 +126,10 @@ msgstr "VERBINDUNG" msgid "CONNECTING..." msgstr "VERBINDUNG" +#: openpilot/selfdrive/ui/layouts/settings/device.py +msgid "Cabin Camera" +msgstr "" + #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/keyboard.py #: openpilot/system/ui/widgets/network.py @@ -205,10 +209,6 @@ msgstr "Dongle-ID" msgid "Download" msgstr "Herunterladen" -#: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" -msgstr "Fahrerkamera" - #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Fahrstil" @@ -466,8 +466,8 @@ msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Verhindern Sie das Hochladen großer Datenmengen bei einer getakteten Mobilfunkverbindung" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "Vorschau der Fahrer‑Kamera, um sicherzustellen, dass die Fahrerüberwachung gute Sicht hat. (Fahrzeug muss ausgeschaltet sein)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" #: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" @@ -498,8 +498,8 @@ msgid "Reboot and Update" msgstr "Neustarten und aktualisieren" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" -msgstr "Fahrerkamera aufzeichnen und hochladen" +msgid "Record and Upload Cabin Camera" +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" @@ -638,8 +638,8 @@ msgid "Upgrade Now" msgstr "Jetzt abonnieren" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "Daten von der Fahrer‑Kamera hochladen und den Fahrerüberwachungs‑Algorithmus verbessern." +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" @@ -685,7 +685,7 @@ msgstr "Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwend msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden. Lesen Sie die aktuellen Bedingungen unter https://comma.ai/terms, bevor Sie fortfahren." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py msgid "camera starting" msgstr "Kamera startet" diff --git a/openpilot/selfdrive/ui/translations/app_en.po b/openpilot/selfdrive/ui/translations/app_en.po index 3e578de6a4..c45da337eb 100644 --- a/openpilot/selfdrive/ui/translations/app_en.po +++ b/openpilot/selfdrive/ui/translations/app_en.po @@ -126,6 +126,10 @@ msgstr "CONNECT" msgid "CONNECTING..." msgstr "CONNECTING..." +#: openpilot/selfdrive/ui/layouts/settings/device.py +msgid "Cabin Camera" +msgstr "" + #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/keyboard.py #: openpilot/system/ui/widgets/network.py @@ -205,10 +209,6 @@ msgstr "Dongle ID" msgid "Download" msgstr "Download" -#: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" -msgstr "Driver Camera" - #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Driving Personality" @@ -466,8 +466,8 @@ msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Prevent large data uploads when on a metered cellular connection" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" #: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" @@ -498,8 +498,8 @@ msgid "Reboot and Update" msgstr "Reboot and Update" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" -msgstr "Record and Upload Driver Camera" +msgid "Record and Upload Cabin Camera" +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" @@ -638,8 +638,8 @@ msgid "Upgrade Now" msgstr "Upgrade Now" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" @@ -685,7 +685,7 @@ msgstr "You must accept the Terms and Conditions in order to use openpilot." msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py msgid "camera starting" msgstr "camera starting" diff --git a/openpilot/selfdrive/ui/translations/app_es.po b/openpilot/selfdrive/ui/translations/app_es.po index e1c74f53c2..55ba293128 100644 --- a/openpilot/selfdrive/ui/translations/app_es.po +++ b/openpilot/selfdrive/ui/translations/app_es.po @@ -126,6 +126,10 @@ msgstr "CONECTAR" msgid "CONNECTING..." msgstr "CONECTAR" +#: openpilot/selfdrive/ui/layouts/settings/device.py +msgid "Cabin Camera" +msgstr "" + #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/keyboard.py #: openpilot/system/ui/widgets/network.py @@ -205,10 +209,6 @@ msgstr "ID del dongle" msgid "Download" msgstr "Descargar" -#: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" -msgstr "Cámara del conductor" - #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Estilo de conducción" @@ -466,8 +466,8 @@ msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Evite grandes cargas de datos cuando esté en una conexión celular medida" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "Previsualiza la cámara hacia el conductor para asegurarte de que la supervisión del conductor tenga buena visibilidad. (el vehículo debe estar apagado)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" #: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" @@ -498,8 +498,8 @@ msgid "Reboot and Update" msgstr "Reiniciar y actualizar" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" -msgstr "Grabar y subir cámara del conductor" +msgid "Record and Upload Cabin Camera" +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" @@ -638,8 +638,8 @@ msgid "Upgrade Now" msgstr "Mejorar ahora" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "Sube datos de la cámara orientada al conductor y ayuda a mejorar el algoritmo de supervisión del conductor." +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" @@ -685,7 +685,7 @@ msgstr "Debes aceptar los Términos y Condiciones para poder usar openpilot." msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "Debes aceptar los Términos y Condiciones para usar openpilot. Lee los términos más recientes en https://comma.ai/terms antes de continuar." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py msgid "camera starting" msgstr "iniciando cámara" diff --git a/openpilot/selfdrive/ui/translations/app_fr.po b/openpilot/selfdrive/ui/translations/app_fr.po index b76018394f..1e0f370dcd 100644 --- a/openpilot/selfdrive/ui/translations/app_fr.po +++ b/openpilot/selfdrive/ui/translations/app_fr.po @@ -126,6 +126,10 @@ msgstr "CONNECTER" msgid "CONNECTING..." msgstr "CONNECTER..." +#: openpilot/selfdrive/ui/layouts/settings/device.py +msgid "Cabin Camera" +msgstr "" + #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/keyboard.py #: openpilot/system/ui/widgets/network.py @@ -205,10 +209,6 @@ msgstr "ID du dongle" msgid "Download" msgstr "Télécharger" -#: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" -msgstr "Caméra conducteur" - #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Personnalité de conduite" @@ -466,8 +466,8 @@ msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Eviter les transferts de données volumineux lors d'une connexion à un réseau cellulaire limité" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "Prévisualisez la caméra orientée conducteur pour vous assurer que la surveillance du conducteur a une bonne visibilité. (le véhicule doit être éteint)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" #: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" @@ -498,8 +498,8 @@ msgid "Reboot and Update" msgstr "Redémarrer et mettre à jour" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" -msgstr "Enregistrer et téléverser la caméra conducteur" +msgid "Record and Upload Cabin Camera" +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" @@ -638,8 +638,8 @@ msgid "Upgrade Now" msgstr "Mettre à niveau maintenant" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "Téléverser les données de la caméra orientée conducteur et aider à améliorer l'algorithme de surveillance du conducteur." +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" @@ -685,7 +685,7 @@ msgstr "Vous devez accepter les conditions générales pour utiliser openpilot." msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "Vous devez accepter les conditions générales pour utiliser openpilot. Lisez les dernières conditions sur https://comma.ai/terms avant de continuer." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py msgid "camera starting" msgstr "démarrage de la caméra" diff --git a/openpilot/selfdrive/ui/translations/app_ja.po b/openpilot/selfdrive/ui/translations/app_ja.po index 4fbdb6fe80..2a2f76ae15 100644 --- a/openpilot/selfdrive/ui/translations/app_ja.po +++ b/openpilot/selfdrive/ui/translations/app_ja.po @@ -126,6 +126,10 @@ msgstr "接続" msgid "CONNECTING..." msgstr "接続中..." +#: openpilot/selfdrive/ui/layouts/settings/device.py +msgid "Cabin Camera" +msgstr "" + #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/keyboard.py #: openpilot/system/ui/widgets/network.py @@ -205,10 +209,6 @@ msgstr "ドングルID" msgid "Download" msgstr "ダウンロード" -#: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" -msgstr "ドライバーカメラ" - #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "走行性格" @@ -466,8 +466,8 @@ msgid "Prevent large data uploads when on a metered cellular connection" msgstr "従量課金の携帯回線接続時は大きなデータのアップロードを抑制" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "ドライバー向きカメラのプレビューでモニタリングの視界を確認します。(車両は停止状態である必要があります)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" #: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" @@ -498,8 +498,8 @@ msgid "Reboot and Update" msgstr "再起動して更新" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" -msgstr "ドライバーカメラを記録してアップロード" +msgid "Record and Upload Cabin Camera" +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" @@ -638,8 +638,8 @@ msgid "Upgrade Now" msgstr "今すぐアップグレード" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "ドライバー向きカメラのデータをアップロードしてモニタリングアルゴリズムの改善に協力してください。" +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" @@ -685,7 +685,7 @@ msgstr "openpilotを使用するには、利用規約に同意する必要があ msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "openpilotを使用するには利用規約に同意する必要があります。続行する前に https://comma.ai/terms の最新の規約をお読みください。" -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py msgid "camera starting" msgstr "カメラを起動中" diff --git a/openpilot/selfdrive/ui/translations/app_ko.po b/openpilot/selfdrive/ui/translations/app_ko.po index 29d297be88..863986c0b6 100644 --- a/openpilot/selfdrive/ui/translations/app_ko.po +++ b/openpilot/selfdrive/ui/translations/app_ko.po @@ -126,6 +126,10 @@ msgstr "연결" msgid "CONNECTING..." msgstr "연결 중..." +#: openpilot/selfdrive/ui/layouts/settings/device.py +msgid "Cabin Camera" +msgstr "" + #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/keyboard.py #: openpilot/system/ui/widgets/network.py @@ -205,10 +209,6 @@ msgstr "동글 ID" msgid "Download" msgstr "다운로드" -#: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" -msgstr "운전자 카메라" - #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "주행 성향" @@ -466,8 +466,8 @@ msgid "Prevent large data uploads when on a metered cellular connection" msgstr "종량제 셀룰러 연결 시 대용량 업로드 방지" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "운전자 모니터링의 가시성을 확인하기 위해 운전자 카메라를 미리 봅니다. (차량은 꺼져 있어야 합니다)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" #: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" @@ -498,8 +498,8 @@ msgid "Reboot and Update" msgstr "재시작 및 업데이트" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" -msgstr "운전자 카메라 기록 및 업로드" +msgid "Record and Upload Cabin Camera" +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" @@ -638,8 +638,8 @@ msgid "Upgrade Now" msgstr "지금 업그레이드" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "운전자 방향 카메라 데이터를 업로드하여 운전자 모니터링 알고리즘 개선에 도움을 주세요." +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" @@ -685,7 +685,7 @@ msgstr "openpilot을 사용하려면 약관에 동의해야 합니다." msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "openpilot을 사용하려면 약관에 동의해야 합니다. 계속하기 전에 https://comma.ai/terms 에서 최신 약관을 읽어주세요." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py msgid "camera starting" msgstr "카메라 시작 중" diff --git a/openpilot/selfdrive/ui/translations/app_pt-BR.po b/openpilot/selfdrive/ui/translations/app_pt-BR.po index 29a95b22fd..a478ccc931 100644 --- a/openpilot/selfdrive/ui/translations/app_pt-BR.po +++ b/openpilot/selfdrive/ui/translations/app_pt-BR.po @@ -126,6 +126,10 @@ msgstr "CONECTAR" msgid "CONNECTING..." msgstr "CONECTANDO..." +#: openpilot/selfdrive/ui/layouts/settings/device.py +msgid "Cabin Camera" +msgstr "" + #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/keyboard.py #: openpilot/system/ui/widgets/network.py @@ -205,10 +209,6 @@ msgstr "ID do Dongle" msgid "Download" msgstr "Baixar" -#: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" -msgstr "Câmera do Motorista" - #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Personalidade" @@ -466,8 +466,8 @@ msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Evitar uploads grandes de dados em conexões móveis limitadas" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "Pré-visualize a câmera voltada para o motorista para garantir que o monitoramento do motorista tenha boa visibilidade. (veículo deve estar desligado)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" #: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" @@ -498,8 +498,8 @@ msgid "Reboot and Update" msgstr "Reiniciar e Atualizar" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" -msgstr "Gravar e Enviar Câmera do Motorista" +msgid "Record and Upload Cabin Camera" +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" @@ -638,8 +638,8 @@ msgid "Upgrade Now" msgstr "Atualizar Agora" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "Envie dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramento do motorista." +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" @@ -685,7 +685,7 @@ msgstr "Você deve aceitar os Termos e Condições para usar o openpilot." msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "Você deve aceitar os Termos e Condições para usar o openpilot. Leia os termos mais recentes em https://comma.ai/terms antes de continuar." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py msgid "camera starting" msgstr "câmera iniciando" diff --git a/openpilot/selfdrive/ui/translations/app_th.po b/openpilot/selfdrive/ui/translations/app_th.po index 59cc8df33d..c15d859954 100644 --- a/openpilot/selfdrive/ui/translations/app_th.po +++ b/openpilot/selfdrive/ui/translations/app_th.po @@ -126,6 +126,10 @@ msgstr "เชื่อมต่อ" msgid "CONNECTING..." msgstr "กำลังเชื่อมต่อ..." +#: openpilot/selfdrive/ui/layouts/settings/device.py +msgid "Cabin Camera" +msgstr "" + #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/keyboard.py #: openpilot/system/ui/widgets/network.py @@ -205,10 +209,6 @@ msgstr "รหัสดองเกิล" msgid "Download" msgstr "ดาวน์โหลด" -#: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" -msgstr "กล้องไดร์เวอร์" - #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "บุคลิกภาพในการขับขี่" @@ -466,8 +466,8 @@ msgid "Prevent large data uploads when on a metered cellular connection" msgstr "ป้องกันการอัพโหลดข้อมูลขนาดใหญ่เมื่อใช้การเชื่อมต่อมือถือแบบคิดค่าบริการตามปริมาณข้อมูล" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "ดูตัวอย่างกล้องที่หันหน้าไปทางคนขับเพื่อให้แน่ใจว่าการตรวจสอบผู้ขับขี่มีทัศนวิสัยที่ดี (รถจะต้องถูกปิด)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" #: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" @@ -498,8 +498,8 @@ msgid "Reboot and Update" msgstr "รีบูตและอัปเดต" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" -msgstr "บันทึกและอัพโหลดกล้องไดร์เวอร์" +msgid "Record and Upload Cabin Camera" +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" @@ -638,8 +638,8 @@ msgid "Upgrade Now" msgstr "อัพเกรดทันที" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "อัปโหลดข้อมูลจากกล้องที่หันเข้าหาคนขับและช่วยปรับปรุงอัลกอริธึมการตรวจสอบผู้ขับขี่" +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" @@ -685,7 +685,7 @@ msgstr "คุณต้องยอมรับข้อกำหนดและ msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "คุณต้องยอมรับข้อกำหนดและเงื่อนไขเพื่อใช้ openpilot อ่านข้อกำหนดล่าสุดได้ที่ https://comma.ai/terms ก่อนดำเนินการต่อ" -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py msgid "camera starting" msgstr "กำลังเริ่มกล้อง" diff --git a/openpilot/selfdrive/ui/translations/app_tr.po b/openpilot/selfdrive/ui/translations/app_tr.po index cf15e2c806..aefbb535de 100644 --- a/openpilot/selfdrive/ui/translations/app_tr.po +++ b/openpilot/selfdrive/ui/translations/app_tr.po @@ -126,6 +126,10 @@ msgstr "BAĞLAN" msgid "CONNECTING..." msgstr "BAĞLAN" +#: openpilot/selfdrive/ui/layouts/settings/device.py +msgid "Cabin Camera" +msgstr "" + #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/keyboard.py #: openpilot/system/ui/widgets/network.py @@ -205,10 +209,6 @@ msgstr "Dongle kimliği" msgid "Download" msgstr "İndir" -#: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" -msgstr "Sürücü Kamerası" - #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Sürüş Kişiliği" @@ -466,8 +466,8 @@ msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Ölçülü bir hücresel bağlantıdayken büyük veri yüklemelerini engelle" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "Sürücü izleme görünürlüğünün iyi olduğundan emin olmak için sürücüye bakan kamerayı önizleyin. (araç kapalı olmalıdır)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" #: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" @@ -498,8 +498,8 @@ msgid "Reboot and Update" msgstr "Yeniden Başlat ve Güncelle" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" -msgstr "Sürücü Kamerasını Kaydet ve Yükle" +msgid "Record and Upload Cabin Camera" +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" @@ -638,8 +638,8 @@ msgid "Upgrade Now" msgstr "Şimdi Yükselt" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "Sürücüye bakan kameradan veri yükleyin ve sürücü izleme algoritmasını geliştirmeye yardımcı olun." +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" @@ -685,7 +685,7 @@ msgstr "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz." msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz. Devam etmeden önce en güncel şartları https://comma.ai/terms adresinde okuyun." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py msgid "camera starting" msgstr "kamera başlatılıyor" diff --git a/openpilot/selfdrive/ui/translations/app_uk.po b/openpilot/selfdrive/ui/translations/app_uk.po index 24fab4f2e8..e4717ae462 100644 --- a/openpilot/selfdrive/ui/translations/app_uk.po +++ b/openpilot/selfdrive/ui/translations/app_uk.po @@ -126,6 +126,10 @@ msgstr "ПІДКЛЮЧИТИ" msgid "CONNECTING..." msgstr "ПІДКЛЮЧА..." +#: openpilot/selfdrive/ui/layouts/settings/device.py +msgid "Cabin Camera" +msgstr "" + #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/keyboard.py #: openpilot/system/ui/widgets/network.py @@ -205,10 +209,6 @@ msgstr "ID ключа" msgid "Download" msgstr "Завантажити" -#: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" -msgstr "Камера водія" - #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Стиль водіння" @@ -466,8 +466,8 @@ msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Запобігати великим завантаженням даних під час лімітного стільникового з'єднання" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "Попередньо перегляньте камеру, спрямовану на водія, щоб переконатися, що система моніторингу водія має добру видимість. (автомобіль повинен бути вимкнений)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" #: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" @@ -498,8 +498,8 @@ msgid "Reboot and Update" msgstr "Перезавантажити та оновити" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" -msgstr "Писати та вантажити відео з камери водія" +msgid "Record and Upload Cabin Camera" +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" @@ -638,8 +638,8 @@ msgid "Upgrade Now" msgstr "Оновити зараз" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "Завантажуйте дані з камери, спрямованої на водія, та допоможіть покращити алгоритм моніторингу водія." +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" @@ -685,7 +685,7 @@ msgstr "Ви повинні прийняти Умови та положення, msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "Ви повинні прийняти Умови використання, щоб користуватися openpilot. Перед тим, як продовжити, ознайомтеся з останніми умовами на сайті https://comma.ai/terms." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py msgid "camera starting" msgstr "запуск камери" diff --git a/openpilot/selfdrive/ui/translations/app_zh-CHS.po b/openpilot/selfdrive/ui/translations/app_zh-CHS.po index 2028b6ac1f..f1ec22857f 100644 --- a/openpilot/selfdrive/ui/translations/app_zh-CHS.po +++ b/openpilot/selfdrive/ui/translations/app_zh-CHS.po @@ -126,6 +126,10 @@ msgstr "连接" msgid "CONNECTING..." msgstr "连接中..." +#: openpilot/selfdrive/ui/layouts/settings/device.py +msgid "Cabin Camera" +msgstr "" + #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/keyboard.py #: openpilot/system/ui/widgets/network.py @@ -205,10 +209,6 @@ msgstr "设备 ID" msgid "Download" msgstr "下载" -#: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" -msgstr "车内摄像头" - #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "驾驶风格" @@ -466,8 +466,8 @@ msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在计量制蜂窝网络时避免大量上传" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "预览车内摄像头以确保驾驶员监控视野良好。(车辆必须熄火)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" #: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" @@ -498,8 +498,8 @@ msgid "Reboot and Update" msgstr "重启并更新" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" -msgstr "录制并上传车内摄像头" +msgid "Record and Upload Cabin Camera" +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" @@ -638,8 +638,8 @@ msgid "Upgrade Now" msgstr "立即升级" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "上传车内摄像头数据,帮助改进驾驶员监控算法。" +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" @@ -685,7 +685,7 @@ msgstr "您必须接受条款与条件才能使用 openpilot。" msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "您必须接受条款与条件才能使用 openpilot。继续前请阅读 https://comma.ai/terms 上的最新条款。" -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py msgid "camera starting" msgstr "相机启动中" diff --git a/openpilot/selfdrive/ui/translations/app_zh-CHT.po b/openpilot/selfdrive/ui/translations/app_zh-CHT.po index 39a67b432d..c5e5289984 100644 --- a/openpilot/selfdrive/ui/translations/app_zh-CHT.po +++ b/openpilot/selfdrive/ui/translations/app_zh-CHT.po @@ -126,6 +126,10 @@ msgstr "連線" msgid "CONNECTING..." msgstr "連線中..." +#: openpilot/selfdrive/ui/layouts/settings/device.py +msgid "Cabin Camera" +msgstr "" + #: openpilot/system/ui/widgets/confirm_dialog.py #: openpilot/system/ui/widgets/keyboard.py #: openpilot/system/ui/widgets/network.py @@ -205,10 +209,6 @@ msgstr "裝置 ID" msgid "Download" msgstr "下載" -#: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Driver Camera" -msgstr "車內鏡頭" - #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "駕駛風格" @@ -466,8 +466,8 @@ msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在計量制行動網路時避免大量上傳" #: openpilot/selfdrive/ui/layouts/settings/device.py -msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "預覽車內鏡頭以確保駕駛監控視野良好。(車輛須熄火)" +msgid "Preview the cabin camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" #: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" @@ -498,8 +498,8 @@ msgid "Reboot and Update" msgstr "重新啟動並更新" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Record and Upload Driver Camera" -msgstr "錄製並上傳車內鏡頭" +msgid "Record and Upload Cabin Camera" +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" @@ -638,8 +638,8 @@ msgid "Upgrade Now" msgstr "立即升級" #: openpilot/selfdrive/ui/layouts/settings/toggles.py -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "上傳車內鏡頭資料,協助改善駕駛監控演算法。" +msgid "Upload data from the cabin camera and help improve the driver monitoring algorithm." +msgstr "" #: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" @@ -685,7 +685,7 @@ msgstr "您必須接受條款與細則才能使用 openpilot。" msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "您必須接受條款與細則才能使用 openpilot。繼續前請閱讀 https://comma.ai/terms 上的最新條款。" -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py +#: openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py msgid "camera starting" msgstr "相機啟動中" diff --git a/openpilot/selfdrive/ui/ui_state.py b/openpilot/selfdrive/ui/ui_state.py index dca583d107..943ba46014 100644 --- a/openpilot/selfdrive/ui/ui_state.py +++ b/openpilot/selfdrive/ui/ui_state.py @@ -48,7 +48,7 @@ class UIState: "driverMonitoringState", "carState", "driverStateV2", - "roadCameraState", + "narrowRoadCameraState", "wideRoadCameraState", "managerState", "selfdriveState", diff --git a/openpilot/selfdrive/ui/watch3.py b/openpilot/selfdrive/ui/watch3.py index 162df82658..c601ccabe9 100755 --- a/openpilot/selfdrive/ui/watch3.py +++ b/openpilot/selfdrive/ui/watch3.py @@ -8,8 +8,8 @@ from openpilot.selfdrive.ui.onroad.cameraview import CameraView if __name__ == "__main__": gui_app.init_window("watch3") - road = CameraView("camerad", VisionStreamType.VISION_STREAM_ROAD) - driver = CameraView("camerad", VisionStreamType.VISION_STREAM_DRIVER) + road = CameraView("camerad", VisionStreamType.VISION_STREAM_NARROW_ROAD) + driver = CameraView("camerad", VisionStreamType.VISION_STREAM_CABIN) wide = CameraView("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD) for _ in gui_app.render(): road.render(rl.Rectangle(gui_app.width // 4, 0, gui_app.width // 2, gui_app.height // 2)) diff --git a/openpilot/system/camerad/cameras/camera_qcom2.cc b/openpilot/system/camerad/cameras/camera_qcom2.cc index 63640920db..4d30cd1e2c 100644 --- a/openpilot/system/camerad/cameras/camera_qcom2.cc +++ b/openpilot/system/camerad/cameras/camera_qcom2.cc @@ -234,11 +234,11 @@ void CameraState::sendState() { framed.setSensor(camera.sensor->image_sensor); // Log raw frames for road camera - if (env_log_raw_frames && camera.cc.stream_type == VISION_STREAM_ROAD && meta.frame_id % 100 == 5) { // no overlap with qlog decimation + if (env_log_raw_frames && camera.cc.stream_type == VISION_STREAM_NARROW_ROAD && meta.frame_id % 100 == 5) { // no overlap with qlog decimation framed.setImage(get_raw_frame_image(&camera.buf)); } - set_camera_exposure(calculate_exposure_value(&camera.buf, ae_xywh, 2, camera.cc.stream_type != VISION_STREAM_DRIVER ? 2 : 4)); + set_camera_exposure(calculate_exposure_value(&camera.buf, ae_xywh, 2, camera.cc.stream_type != VISION_STREAM_CABIN ? 2 : 4)); // Send the message pm->send(camera.cc.publish_name, msg); diff --git a/openpilot/system/camerad/cameras/hw.h b/openpilot/system/camerad/cameras/hw.h index 45546258a9..8f9de5bede 100644 --- a/openpilot/system/camerad/cameras/hw.h +++ b/openpilot/system/camerad/cameras/hw.h @@ -44,12 +44,12 @@ const CameraConfig WIDE_ROAD_CAMERA_CONFIG = { .staggered_sof = false, }; -const CameraConfig ROAD_CAMERA_CONFIG = { +const CameraConfig NARROW_ROAD_CAMERA_CONFIG = { .camera_num = 1, - .stream_type = VISION_STREAM_ROAD, + .stream_type = VISION_STREAM_NARROW_ROAD, .focal_len = 8.0, - .publish_name = "roadCameraState", - .init_camera_state = &cereal::Event::Builder::initRoadCameraState, + .publish_name = "narrowRoadCameraState", + .init_camera_state = &cereal::Event::Builder::initNarrowRoadCameraState, .enabled = !getenv("DISABLE_ROAD"), .phy = CAM_ISP_IFE_IN_RES_PHY_1, .vignetting_correction = true, @@ -57,12 +57,12 @@ const CameraConfig ROAD_CAMERA_CONFIG = { .staggered_sof = false, }; -const CameraConfig DRIVER_CAMERA_CONFIG = { +const CameraConfig CABIN_CAMERA_CONFIG = { .camera_num = 2, - .stream_type = VISION_STREAM_DRIVER, + .stream_type = VISION_STREAM_CABIN, .focal_len = 1.71, - .publish_name = "driverCameraState", - .init_camera_state = &cereal::Event::Builder::initDriverCameraState, + .publish_name = "cabinCameraState", + .init_camera_state = &cereal::Event::Builder::initCabinCameraState, .enabled = !getenv("DISABLE_DRIVER"), .phy = CAM_ISP_IFE_IN_RES_PHY_2, .vignetting_correction = false, @@ -70,4 +70,4 @@ const CameraConfig DRIVER_CAMERA_CONFIG = { .staggered_sof = true, }; -const CameraConfig ALL_CAMERA_CONFIGS[] = {WIDE_ROAD_CAMERA_CONFIG, ROAD_CAMERA_CONFIG, DRIVER_CAMERA_CONFIG}; +const CameraConfig ALL_CAMERA_CONFIGS[] = {WIDE_ROAD_CAMERA_CONFIG, NARROW_ROAD_CAMERA_CONFIG, CABIN_CAMERA_CONFIG}; diff --git a/openpilot/system/camerad/snapshot.py b/openpilot/system/camerad/snapshot.py index f865a452ff..622da4faef 100755 --- a/openpilot/system/camerad/snapshot.py +++ b/openpilot/system/camerad/snapshot.py @@ -9,8 +9,8 @@ from openpilot.common.realtime import DT_MDL VISION_STREAMS = { - "roadCameraState": VisionStreamType.VISION_STREAM_ROAD, - "driverCameraState": VisionStreamType.VISION_STREAM_DRIVER, + "narrowRoadCameraState": VisionStreamType.VISION_STREAM_NARROW_ROAD, + "cabinCameraState": VisionStreamType.VISION_STREAM_CABIN, "wideRoadCameraState": VisionStreamType.VISION_STREAM_WIDE_ROAD, } @@ -45,7 +45,7 @@ def extract_image(buf): return yuv_to_rgb(y, u, v) -def get_snapshots(frame="roadCameraState", front_frame="driverCameraState"): +def get_snapshots(frame="narrowRoadCameraState", front_frame="cabinCameraState"): sockets = [s for s in (frame, front_frame) if s is not None] sm = messaging.SubMaster(sockets) vipc_clients = {s: VisionIpcClient("camerad", VISION_STREAMS[s], True) for s in sockets} diff --git a/openpilot/system/camerad/test/test_camerad.py b/openpilot/system/camerad/test/test_camerad.py index 580a30683b..410ea9fdb3 100755 --- a/openpilot/system/camerad/test/test_camerad.py +++ b/openpilot/system/camerad/test/test_camerad.py @@ -13,7 +13,7 @@ from openpilot.system.camerad.snapshot import get_snapshots from openpilot.selfdrive.test.helpers import collect_logs, log_collector, processes_context TEST_TIMESPAN = 10 -CAMERAS = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState') +CAMERAS = ('narrowRoadCameraState', 'cabinCameraState', 'wideRoadCameraState') EXPOSURE_STABLE_COUNT = 3 EXPOSURE_RANGE = (0.15, 0.35) MAX_TEST_TIME = 25 @@ -49,7 +49,7 @@ def _camera_session(): exposure = {cam: [] for cam in CAMERAS} start = time.monotonic() while time.monotonic() - start < MAX_TEST_TIME: - rpic, dpic = get_snapshots(frame="roadCameraState", front_frame="driverCameraState") + rpic, dpic = get_snapshots(frame="narrowRoadCameraState", front_frame="cabinCameraState") wpic, _ = get_snapshots(frame="wideRoadCameraState") for cam, img in zip(CAMERAS, [rpic, dpic, wpic], strict=True): exposure[cam].append(_exposure_stats(img)) @@ -106,8 +106,8 @@ class TestCamerad(OpenpilotTestCase): assert set(np.diff(self.logs[c]['frameId'])) == {1, }, f"{c} has frame skips" def test_frame_sync(self): - SYNCED_CAMS = ('roadCameraState', 'wideRoadCameraState') - n = range(len(self.logs['roadCameraState']['t'][:-10])) + SYNCED_CAMS = ('narrowRoadCameraState', 'wideRoadCameraState') + n = range(len(self.logs['narrowRoadCameraState']['t'][:-10])) frame_ids = {i: [self.logs[cam]['frameId'][i] for cam in CAMERAS] for i in n} assert all(len(set(v)) == 1 for v in frame_ids.values()), "frame IDs not aligned" @@ -118,10 +118,10 @@ class TestCamerad(OpenpilotTestCase): laggy_frames = {k: v for k, v in diffs.items() if v > 1.1} assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" - # driver camera should be staggered ~25ms from road camera + # cabin camera should be staggered ~25ms from road camera for i in n: - offset_ms = abs(self.logs['driverCameraState']['timestampSof'][i] - self.logs['roadCameraState']['timestampSof'][i]) / 1e6 - assert 20 < offset_ms < 30, f"driver camera stagger out of range at frame {i}: {offset_ms:.1f}ms (expected ~25ms)" + offset_ms = abs(self.logs['cabinCameraState']['timestampSof'][i] - self.logs['narrowRoadCameraState']['timestampSof'][i]) / 1e6 + assert 20 < offset_ms < 30, f"cabin camera stagger out of range at frame {i}: {offset_ms:.1f}ms (expected ~25ms)" def test_sanity_checks(self): self._sanity_checks(self.logs) diff --git a/openpilot/system/camerad/webcam/camerad.py b/openpilot/system/camerad/webcam/camerad.py index bba982f897..4dfa5f3371 100755 --- a/openpilot/system/camerad/webcam/camerad.py +++ b/openpilot/system/camerad/webcam/camerad.py @@ -11,19 +11,19 @@ from openpilot.cereal import messaging from openpilot.system.camerad.webcam.camera import Camera from openpilot.common.realtime import Ratekeeper -ROAD_CAM = os.getenv("ROAD_CAM", "0") +NARROW_ROAD_CAM = os.getenv("NARROW_ROAD_CAM", os.getenv("ROAD_CAM", "0")) WIDE_CAM = os.getenv("WIDE_CAM") DRIVER_CAM = os.getenv("DRIVER_CAM") CameraType = namedtuple("CameraType", ["msg_name", "stream_type", "cam_id"]) CAMERAS = [ - CameraType("roadCameraState", VisionStreamType.VISION_STREAM_ROAD, ROAD_CAM) + CameraType("narrowRoadCameraState", VisionStreamType.VISION_STREAM_NARROW_ROAD, NARROW_ROAD_CAM) ] if WIDE_CAM: CAMERAS.append(CameraType("wideRoadCameraState", VisionStreamType.VISION_STREAM_WIDE_ROAD, WIDE_CAM)) if DRIVER_CAM: - CAMERAS.append(CameraType("driverCameraState", VisionStreamType.VISION_STREAM_DRIVER, DRIVER_CAM)) + CAMERAS.append(CameraType("cabinCameraState", VisionStreamType.VISION_STREAM_CABIN, DRIVER_CAM)) class Camerad: def __init__(self): diff --git a/openpilot/system/loggerd/clip_encoder.cc b/openpilot/system/loggerd/clip_encoder.cc index 884ef3f091..7e8f3a9026 100644 --- a/openpilot/system/loggerd/clip_encoder.cc +++ b/openpilot/system/loggerd/clip_encoder.cc @@ -29,11 +29,11 @@ constexpr int CLIP_FPS = 20; constexpr double PARALLEL_CLIP_MIN_DURATION = 2 * SEGMENT_DURATION; const EncoderInfo clip_encoder_info = { - .publish_name = "livestreamRoadEncodeData", + .publish_name = "livestreamNarrowRoadEncodeData", .record = false, .fps = CLIP_FPS, .get_settings = [](int) { return EncoderSettings::StreamEncoderSettings(); }, - INIT_ENCODE_FUNCTIONS(LivestreamRoadEncode), + INIT_ENCODE_FUNCTIONS(LivestreamNarrowRoadEncode), }; bool open_input(const std::string &path, AVFormatContext **ctx, int *stream_index) { diff --git a/openpilot/system/loggerd/loggerd.h b/openpilot/system/loggerd/loggerd.h index c6c7b41af1..beabd7d791 100644 --- a/openpilot/system/loggerd/loggerd.h +++ b/openpilot/system/loggerd/loggerd.h @@ -96,11 +96,11 @@ public: }; const EncoderInfo main_road_encoder_info = { - .publish_name = "roadEncodeData", + .publish_name = "narrowRoadEncodeData", .thumbnail_name = "thumbnail", .filename = "fcamera.hevc", .get_settings = [](int in_width){return EncoderSettings::MainEncoderSettings(in_width);}, - INIT_ENCODE_FUNCTIONS(RoadEncode), + INIT_ENCODE_FUNCTIONS(NarrowRoadEncode), }; const EncoderInfo main_wide_road_encoder_info = { @@ -110,23 +110,23 @@ const EncoderInfo main_wide_road_encoder_info = { INIT_ENCODE_FUNCTIONS(WideRoadEncode), }; -const EncoderInfo main_driver_encoder_info = { - .publish_name = "driverEncodeData", +const EncoderInfo main_cabin_encoder_info = { + .publish_name = "cabinEncodeData", .filename = "dcamera.hevc", .record = Params().getBool("RecordFront"), .get_settings = [](int in_width){return EncoderSettings::MainEncoderSettings(in_width);}, - INIT_ENCODE_FUNCTIONS(DriverEncode), + INIT_ENCODE_FUNCTIONS(CabinEncode), }; const EncoderInfo stream_road_encoder_info = { - .publish_name = "livestreamRoadEncodeData", + .publish_name = "livestreamNarrowRoadEncodeData", //.thumbnail_name = "thumbnail", .record = false, .is_live = true, .frame_width = livestream_width(), .frame_height = livestream_height(), .get_settings = [](int){return EncoderSettings::StreamEncoderSettings();}, - INIT_ENCODE_FUNCTIONS(LivestreamRoadEncode), + INIT_ENCODE_FUNCTIONS(LivestreamNarrowRoadEncode), }; const EncoderInfo stream_wide_road_encoder_info = { @@ -139,29 +139,29 @@ const EncoderInfo stream_wide_road_encoder_info = { INIT_ENCODE_FUNCTIONS(LivestreamWideRoadEncode), }; -const EncoderInfo stream_driver_encoder_info = { - .publish_name = "livestreamDriverEncodeData", +const EncoderInfo stream_cabin_encoder_info = { + .publish_name = "livestreamCabinEncodeData", .record = false, .is_live = true, .frame_width = livestream_width(), .frame_height = livestream_height(), .get_settings = [](int){return EncoderSettings::StreamEncoderSettings();}, - INIT_ENCODE_FUNCTIONS(LivestreamDriverEncode), + INIT_ENCODE_FUNCTIONS(LivestreamCabinEncode), }; const EncoderInfo qcam_encoder_info = { - .publish_name = "qRoadEncodeData", + .publish_name = "qNarrowRoadEncodeData", .filename = "qcamera.ts", .include_audio = Params().getBool("RecordAudio"), .frame_width = 526, .frame_height = 330, .get_settings = [](int){return EncoderSettings::QcamEncoderSettings();}, - INIT_ENCODE_FUNCTIONS(QRoadEncode), + INIT_ENCODE_FUNCTIONS(QNarrowRoadEncode), }; -const LogCameraInfo road_camera_info{ - .thread_name = "road_cam_encoder", - .stream_type = VISION_STREAM_ROAD, +const LogCameraInfo narrow_road_camera_info{ + .thread_name = "narrow_road_cam_encoder", + .stream_type = VISION_STREAM_NARROW_ROAD, .encoder_infos = {main_road_encoder_info, qcam_encoder_info} }; @@ -171,15 +171,15 @@ const LogCameraInfo wide_road_camera_info{ .encoder_infos = {main_wide_road_encoder_info} }; -const LogCameraInfo driver_camera_info{ - .thread_name = "driver_cam_encoder", - .stream_type = VISION_STREAM_DRIVER, - .encoder_infos = {main_driver_encoder_info} +const LogCameraInfo cabin_camera_info{ + .thread_name = "cabin_cam_encoder", + .stream_type = VISION_STREAM_CABIN, + .encoder_infos = {main_cabin_encoder_info} }; const LogCameraInfo stream_road_camera_info{ - .thread_name = "road_cam_encoder", - .stream_type = VISION_STREAM_ROAD, + .thread_name = "narrow_road_cam_encoder", + .stream_type = VISION_STREAM_NARROW_ROAD, .encoder_infos = {stream_road_encoder_info}, }; @@ -189,11 +189,11 @@ const LogCameraInfo stream_wide_road_camera_info{ .encoder_infos = {stream_wide_road_encoder_info}, }; -const LogCameraInfo stream_driver_camera_info{ - .thread_name = "driver_cam_encoder", - .stream_type = VISION_STREAM_DRIVER, - .encoder_infos = {stream_driver_encoder_info}, +const LogCameraInfo stream_cabin_camera_info{ + .thread_name = "cabin_cam_encoder", + .stream_type = VISION_STREAM_CABIN, + .encoder_infos = {stream_cabin_encoder_info}, }; -const LogCameraInfo cameras_logged[] = {road_camera_info, wide_road_camera_info, driver_camera_info}; -const LogCameraInfo stream_cameras_logged[] = {stream_road_camera_info, stream_wide_road_camera_info, stream_driver_camera_info}; +const LogCameraInfo cameras_logged[] = {narrow_road_camera_info, wide_road_camera_info, cabin_camera_info}; +const LogCameraInfo stream_cameras_logged[] = {stream_road_camera_info, stream_wide_road_camera_info, stream_cabin_camera_info}; diff --git a/openpilot/system/loggerd/tests/test_encoder.py b/openpilot/system/loggerd/tests/test_encoder.py index 307a89ba95..10cd586f35 100755 --- a/openpilot/system/loggerd/tests/test_encoder.py +++ b/openpilot/system/loggerd/tests/test_encoder.py @@ -22,8 +22,8 @@ SEGMENT_LENGTH = 2 FULL_SIZE = 2507572 def hevc_size(w): return FULL_SIZE // 2 if w <= 1344 else FULL_SIZE CAMERAS = [ - ("fcamera.hevc", 20, hevc_size, "roadEncodeIdx"), - ("dcamera.hevc", 20, hevc_size, "driverEncodeIdx"), + ("fcamera.hevc", 20, hevc_size, "narrowRoadEncodeIdx"), + ("dcamera.hevc", 20, hevc_size, "cabinEncodeIdx"), ("ecamera.hevc", 20, hevc_size, "wideRoadEncodeIdx"), ("qcamera.ts", 20, lambda x: 130000, None), ] diff --git a/openpilot/system/loggerd/tests/test_loggerd.py b/openpilot/system/loggerd/tests/test_loggerd.py index 333891ad91..de91cae1db 100644 --- a/openpilot/system/loggerd/tests/test_loggerd.py +++ b/openpilot/system/loggerd/tests/test_loggerd.py @@ -112,12 +112,12 @@ class TestLoggerd(OpenpilotTestCase): w, h = 320, 240 frame_spec = (w, h, w * h * 3 // 2, w, w * h) streams = [ - (VisionStreamType.VISION_STREAM_ROAD, frame_spec, "roadCameraState"), - (VisionStreamType.VISION_STREAM_DRIVER, frame_spec, "driverCameraState"), + (VisionStreamType.VISION_STREAM_NARROW_ROAD, frame_spec, "narrowRoadCameraState"), + (VisionStreamType.VISION_STREAM_CABIN, frame_spec, "cabinCameraState"), (VisionStreamType.VISION_STREAM_WIDE_ROAD, frame_spec, "wideRoadCameraState"), ] - sm = messaging.SubMaster(["roadEncodeData"]) + sm = messaging.SubMaster(["narrowRoadEncodeData"]) pm = messaging.PubMaster([s for _, _, s in streams] + ["rawAudioData"]) vipc_server = VisionIpcServer("camerad") for stream_type, frame_spec, _ in streams: @@ -128,7 +128,7 @@ class TestLoggerd(OpenpilotTestCase): os.environ["LOGGERD_SEGMENT_LENGTH"] = str(segment_length) managed_processes["loggerd"].start() managed_processes["encoderd"].start() - assert pm.wait_for_readers_to_update("roadCameraState", timeout=5) + assert pm.wait_for_readers_to_update("narrowRoadCameraState", timeout=5) fps = 20 for n in range(1, int(num_segs * segment_length * fps) + 1): @@ -322,8 +322,8 @@ class TestLoggerd(OpenpilotTestCase): self._publish_camera_and_audio_messages() - dcamera_hevc_exists = os.path.exists(os.path.join(self._get_latest_log_dir(), 'dcamera.hevc')) - assert dcamera_hevc_exists == record_front + cabin_hevc_exists = os.path.exists(os.path.join(self._get_latest_log_dir(), 'dcamera.hevc')) + assert cabin_hevc_exists == record_front @parameterized.expand([True, False]) def test_record_audio(self, record_audio): diff --git a/openpilot/system/webrtc/device/video.py b/openpilot/system/webrtc/device/video.py index c300f76485..cb85dff73e 100644 --- a/openpilot/system/webrtc/device/video.py +++ b/openpilot/system/webrtc/device/video.py @@ -32,9 +32,9 @@ class EncodedVideoFrame: class LiveStreamVideoStreamTrack(TiciVideoStreamTrack): camera_to_sock_mapping = { - "driver": "livestreamDriverEncodeData", + "driver": "livestreamCabinEncodeData", "wideRoad": "livestreamWideRoadEncodeData", - "road": "livestreamRoadEncodeData", + "road": "livestreamNarrowRoadEncodeData", } def __init__(self, camera_type: str, video_enabled: bool = True): diff --git a/openpilot/system/webrtc/tests/test_stream_session.py b/openpilot/system/webrtc/tests/test_stream_session.py index 5a9dc8772c..d64cf6aed6 100644 --- a/openpilot/system/webrtc/tests/test_stream_session.py +++ b/openpilot/system/webrtc/tests/test_stream_session.py @@ -65,7 +65,7 @@ class TestStreamSession(OpenpilotTestCase): mocked_pubmaster.reset_mock() def test_livestream_track(self, mocker): - fake_msg = messaging.new_message("livestreamDriverEncodeData") + fake_msg = messaging.new_message("livestreamCabinEncodeData") config = {"receive.return_value": fake_msg.to_bytes()} mocker.patch("msgq.SubSocket", spec=True, **config) diff --git a/openpilot/tools/cabana/README.md b/openpilot/tools/cabana/README.md index b30c29640e..fbdfbb40e5 100644 --- a/openpilot/tools/cabana/README.md +++ b/openpilot/tools/cabana/README.md @@ -15,7 +15,7 @@ Options: --auto Auto load the route from the best available source (no video): internal, openpilotci, comma_api, car_segments, testing_closet --qcam load qcamera - --ecam load wide road camera + --wide-road load wide road camera --msgq read can messages from msgq --panda read can messages from panda --panda-serial read can messages from panda with given serial @@ -55,7 +55,7 @@ Replace "5beb9b58bd12b691/0000010a--a51155e496" with your desired route identifi To run Cabana with multiple cameras, use the following command: ```shell -cabana "5beb9b58bd12b691/0000010a--a51155e496" --dcam --ecam +cabana "5beb9b58bd12b691/0000010a--a51155e496" --cabin --wide-road ``` ### Streaming CAN Messages from a comma Device diff --git a/openpilot/tools/cabana/cabana.cc b/openpilot/tools/cabana/cabana.cc index d8e21815dc..8b21143faf 100644 --- a/openpilot/tools/cabana/cabana.cc +++ b/openpilot/tools/cabana/cabana.cc @@ -19,8 +19,8 @@ struct CabanaArgs { bool demo = false; bool auto_source = false; bool qcam = false; - bool ecam = false; - bool dcam = false; + bool wide_road = false; + bool cabin = false; bool msgq = false; bool panda = false; bool no_vipc = false; @@ -44,8 +44,8 @@ void printUsage(const char *argv0) { " --auto Auto load the route from the best available source (no video):\n" " internal, openpilotci, comma_api, car_segments, testing_closet\n" " --qcam load qcamera\n" - " --ecam load wide road camera\n" - " --dcam load driver camera\n" + " --wide-road load wide road camera (alias: --ecam)\n" + " --cabin load cabin camera (alias: --dcam)\n" " --msgq read can messages from the msgq\n" " --panda read can messages from panda\n" " --panda-serial read can messages from panda with given serial\n" @@ -83,10 +83,10 @@ int parseArgs(int argc, char *argv[], CabanaArgs &args, bool &ok) { args.auto_source = true; } else if (std::strcmp(a, "--qcam") == 0) { args.qcam = true; - } else if (std::strcmp(a, "--ecam") == 0) { - args.ecam = true; - } else if (std::strcmp(a, "--dcam") == 0) { - args.dcam = true; + } else if (std::strcmp(a, "--wide-road") == 0 || std::strcmp(a, "--ecam") == 0) { + args.wide_road = true; + } else if (std::strcmp(a, "--cabin") == 0 || std::strcmp(a, "--dcam") == 0) { + args.cabin = true; } else if (std::strcmp(a, "--msgq") == 0) { args.msgq = true; } else if (std::strcmp(a, "--panda") == 0) { @@ -162,9 +162,9 @@ int main(int argc, char *argv[]) { #endif } else { uint32_t replay_flags = REPLAY_FLAG_NONE; - if (args.ecam) replay_flags |= REPLAY_FLAG_ECAM; + if (args.wide_road) replay_flags |= REPLAY_FLAG_WIDE_ROAD; if (args.qcam) replay_flags |= REPLAY_FLAG_QCAMERA; - if (args.dcam) replay_flags |= REPLAY_FLAG_DCAM; + if (args.cabin) replay_flags |= REPLAY_FLAG_CABIN_CAMERA; if (args.no_vipc) replay_flags |= REPLAY_FLAG_NO_VIPC; QString route; diff --git a/openpilot/tools/cabana/cameraview.cc b/openpilot/tools/cabana/cameraview.cc index a8df373dc4..9bd6b9be19 100644 --- a/openpilot/tools/cabana/cameraview.cc +++ b/openpilot/tools/cabana/cameraview.cc @@ -57,8 +57,8 @@ void CameraWidget::paintEvent(QPaintEvent *event) { QRect video_rect((width() - w) / 2, (height() - h) / 2, w, h); p.setRenderHint(QPainter::SmoothPixmapTransform); - if (active_stream_type == VISION_STREAM_DRIVER) { - // mirror driver camera horizontally + if (active_stream_type == VISION_STREAM_CABIN) { + // mirror cabin camera horizontally const qreal cx = video_rect.x() + video_rect.width() / 2.0; p.translate(cx, 0); p.scale(-1, 1); diff --git a/openpilot/tools/cabana/streams/replaystream.cc b/openpilot/tools/cabana/streams/replaystream.cc index dbd44b54e3..6d54369aef 100644 --- a/openpilot/tools/cabana/streams/replaystream.cc +++ b/openpilot/tools/cabana/streams/replaystream.cc @@ -46,7 +46,7 @@ void ReplayStream::mergeSegments() { } bool ReplayStream::loadRoute(const std::string &route, const std::string &data_dir, uint32_t replay_flags, bool auto_source) { - replay.reset(new Replay(route, {"can", "roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx", "carParams"}, + replay.reset(new Replay(route, {"can", "narrowRoadEncodeIdx", "cabinEncodeIdx", "wideRoadEncodeIdx", "carParams"}, {}, nullptr, replay_flags, data_dir, auto_source)); replay->setSegmentCacheLimit(settings.max_cached_minutes); replay->installEventFilter([this](const Event *event) { return eventFilter(event); }); @@ -163,8 +163,8 @@ AbstractStream *OpenReplayWidget::open() { } else { auto replay_stream = std::make_unique(qApp); uint32_t flags = REPLAY_FLAG_NONE; - if (cameras[1]->isChecked()) flags |= REPLAY_FLAG_DCAM; - if (cameras[2]->isChecked()) flags |= REPLAY_FLAG_ECAM; + if (cameras[1]->isChecked()) flags |= REPLAY_FLAG_CABIN_CAMERA; + if (cameras[2]->isChecked()) flags |= REPLAY_FLAG_WIDE_ROAD; if (flags == REPLAY_FLAG_NONE && !cameras[0]->isChecked()) flags = REPLAY_FLAG_NO_VIPC; if (replay_stream->loadRoute(route.toStdString(), data_dir.toStdString(), flags)) { diff --git a/openpilot/tools/cabana/tools/routeinfo.cc b/openpilot/tools/cabana/tools/routeinfo.cc index 77a0e065cd..dc272e3d12 100644 --- a/openpilot/tools/cabana/tools/routeinfo.cc +++ b/openpilot/tools/cabana/tools/routeinfo.cc @@ -14,7 +14,7 @@ RouteInfoDlg::RouteInfoDlg(QWidget *parent) : QDialog(parent) { table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setSelectionBehavior(QAbstractItemView::SelectRows); table->setSelectionMode(QAbstractItemView::SingleSelection); - table->setHorizontalHeaderLabels({"", "rlog", "fcam", "ecam", "dcam", "qlog", "qcam"}); + table->setHorizontalHeaderLabels({"", "rlog", "narrow road", "wide road", "driver", "qlog", "qcam"}); table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); table->verticalHeader()->setVisible(false); table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -23,9 +23,9 @@ RouteInfoDlg::RouteInfoDlg(QWidget *parent) : QDialog(parent) { for (const auto &[seg_num, seg] : replay->route().segments()) { table->setItem(row, 0, new QTableWidgetItem(QString::number(seg_num))); table->setItem(row, 1, new QTableWidgetItem(seg.rlog.empty() ? "--" : "Yes")); - table->setItem(row, 2, new QTableWidgetItem(seg.road_cam.empty() ? "--" : "Yes")); + table->setItem(row, 2, new QTableWidgetItem(seg.narrow_road_cam.empty() ? "--" : "Yes")); table->setItem(row, 3, new QTableWidgetItem(seg.wide_road_cam.empty() ? "--" : "Yes")); - table->setItem(row, 4, new QTableWidgetItem(seg.driver_cam.empty() ? "--" : "Yes")); + table->setItem(row, 4, new QTableWidgetItem(seg.cabin_cam.empty() ? "--" : "Yes")); table->setItem(row, 5, new QTableWidgetItem(seg.qlog.empty() ? "--" : "Yes")); table->setItem(row, 6, new QTableWidgetItem(seg.qcamera.empty() ? "--" : "Yes")); ++row; diff --git a/openpilot/tools/cabana/videowidget.cc b/openpilot/tools/cabana/videowidget.cc index 67b89cb080..bd61573658 100644 --- a/openpilot/tools/cabana/videowidget.cc +++ b/openpilot/tools/cabana/videowidget.cc @@ -148,7 +148,7 @@ QWidget *VideoWidget::createCameraWidget() { camera_tab->setAutoHide(true); camera_tab->setExpanding(false); - l->addWidget(cam_widget = new StreamCameraView("camerad", VISION_STREAM_ROAD)); + l->addWidget(cam_widget = new StreamCameraView("camerad", VISION_STREAM_NARROW_ROAD)); cam_widget->setMinimumHeight(MIN_VIDEO_HEIGHT); cam_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); diff --git a/openpilot/tools/camerastream/compressed_vipc.py b/openpilot/tools/camerastream/compressed_vipc.py index 2ec538981b..e85c88d40c 100755 --- a/openpilot/tools/camerastream/compressed_vipc.py +++ b/openpilot/tools/camerastream/compressed_vipc.py @@ -19,8 +19,8 @@ V4L2_BUF_FLAG_KEYFRAME = 8 # then run this "./compressed_vipc.py " ENCODE_SOCKETS = { - VisionStreamType.VISION_STREAM_ROAD: "roadEncodeData", - VisionStreamType.VISION_STREAM_DRIVER: "driverEncodeData", + VisionStreamType.VISION_STREAM_NARROW_ROAD: "narrowRoadEncodeData", + VisionStreamType.VISION_STREAM_CABIN: "cabinEncodeData", VisionStreamType.VISION_STREAM_WIDE_ROAD: "wideRoadEncodeData", } @@ -146,8 +146,8 @@ if __name__ == "__main__": args = parser.parse_args() vision_streams = [ - VisionStreamType.VISION_STREAM_ROAD, - VisionStreamType.VISION_STREAM_DRIVER, + VisionStreamType.VISION_STREAM_NARROW_ROAD, + VisionStreamType.VISION_STREAM_CABIN, VisionStreamType.VISION_STREAM_WIDE_ROAD, ] diff --git a/openpilot/tools/clip/run.py b/openpilot/tools/clip/run.py index 6c847ca342..81145e2259 100755 --- a/openpilot/tools/clip/run.py +++ b/openpilot/tools/clip/run.py @@ -320,7 +320,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, wide_frame_queue = FrameQueue(ecamera_paths, start, end, fps=FRAMERATE) vipc = VisionIpcServer("camerad") - vipc.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 4, frame_queue.frame_w, frame_queue.frame_h) + vipc.create_buffers(VisionStreamType.VISION_STREAM_NARROW_ROAD, 4, frame_queue.frame_w, frame_queue.frame_h) if wide_frame_queue: vipc.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 4, wide_frame_queue.frame_w, wide_frame_queue.frame_h) vipc.start_listener() @@ -339,7 +339,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, if frame_idx >= len(message_chunks): break _, frame_bytes = frame_queue.get() - vipc.send(VisionStreamType.VISION_STREAM_ROAD, frame_bytes, frame_idx, int(frame_idx * 5e7), int(frame_idx * 5e7)) + vipc.send(VisionStreamType.VISION_STREAM_NARROW_ROAD, frame_bytes, frame_idx, int(frame_idx * 5e7), int(frame_idx * 5e7)) if wide_frame_queue: _, wide_bytes = wide_frame_queue.get() vipc.send(VisionStreamType.VISION_STREAM_WIDE_ROAD, wide_bytes, frame_idx, int(frame_idx * 5e7), int(frame_idx * 5e7)) diff --git a/openpilot/tools/jotpluggler/app.h b/openpilot/tools/jotpluggler/app.h index b7754f51b2..9a6777ee90 100644 --- a/openpilot/tools/jotpluggler/app.h +++ b/openpilot/tools/jotpluggler/app.h @@ -87,7 +87,7 @@ enum class PaneKind : uint8_t { enum class CameraViewKind : uint8_t { Road, - Driver, + Cabin, WideRoad, QRoad, }; @@ -322,7 +322,7 @@ struct RouteData { std::vector roots; std::vector can_messages; CameraFeedIndex road_camera; - CameraFeedIndex driver_camera; + CameraFeedIndex cabin_camera; CameraFeedIndex wide_road_camera; CameraFeedIndex qroad_camera; std::vector thumbnails; diff --git a/openpilot/tools/jotpluggler/common.h b/openpilot/tools/jotpluggler/common.h index 5c68b0ed22..7149bbe54d 100644 --- a/openpilot/tools/jotpluggler/common.h +++ b/openpilot/tools/jotpluggler/common.h @@ -23,7 +23,7 @@ struct SpecialItemSpec { inline constexpr std::array kCameraViewSpecs = {{ {CameraViewKind::Road, "Road Camera", "road", "road", "camera_road", &RouteData::road_camera}, - {CameraViewKind::Driver, "Driver Camera", "driver", "driver", "camera_driver", &RouteData::driver_camera}, + {CameraViewKind::Cabin, "Cabin Camera", "driver", "driver", "camera_driver", &RouteData::cabin_camera}, {CameraViewKind::WideRoad, "Wide Road Camera", "wide", "wide_road", "camera_wide_road", &RouteData::wide_road_camera}, {CameraViewKind::QRoad, "qRoad Camera", "qroad", "qroad", "camera_qroad", &RouteData::qroad_camera}, }}; diff --git a/openpilot/tools/jotpluggler/layouts/camera-timings.json b/openpilot/tools/jotpluggler/layouts/camera-timings.json index 64decf15d3..643a7bc43a 100644 --- a/openpilot/tools/jotpluggler/layouts/camera-timings.json +++ b/openpilot/tools/jotpluggler/layouts/camera-timings.json @@ -1 +1 @@ -{"current_tab_index":0,"tabs":[{"name":"SOF / EOF (encodeIdx)","root":{"split":"vertical","sizes":[0.500885,0.499115],"children":[{"title":"...","range":{"left":0.0,"right":630.006367,"top":65000000.0,"bottom":35000000.0},"curves":[{"name":"/driverEncodeIdx/timestampSof","color":"#1f77b4","transform":"derivative","derivative_dt":1.0},{"name":"/roadEncodeIdx/timestampSof","color":"#d62728","transform":"derivative","derivative_dt":1.0},{"name":"/wideRoadEncodeIdx/timestampSof","color":"#1ac938","transform":"derivative","derivative_dt":1.0}],"y_limits":{"min":35000000.0,"max":65000000.0}},{"title":"...","range":{"left":0.0,"right":630.006367,"top":65000000.0,"bottom":35000000.0},"curves":[{"name":"/driverEncodeIdx/timestampEof","color":"#f14cc1","transform":"derivative","derivative_dt":1.0},{"name":"/roadEncodeIdx/timestampEof","color":"#9467bd","transform":"derivative","derivative_dt":1.0},{"name":"/wideRoadEncodeIdx/timestampEof","color":"#17becf","transform":"derivative","derivative_dt":1.0}],"y_limits":{"min":35000000.0,"max":65000000.0}}]}},{"name":"model timings","root":{"split":"vertical","sizes":[0.5,0.5],"children":[{"title":"...","range":{"left":0.0,"right":630.006367,"top":0.016865,"bottom":0.015143},"curves":[{"name":"/modelV2/modelExecutionTime","color":"#ff7f0e"}]},{"title":"...","range":{"left":0.0,"right":630.006367,"top":0.1,"bottom":-0.1},"curves":[{"name":"/modelV2/frameDropPerc","color":"#f14cc1"}]}]}},{"name":"sensor info","root":{"split":"vertical","sizes":[1.0],"children":[{"title":"...","range":{"left":0.0,"right":630.006367,"top":0.1,"bottom":-0.1},"curves":[{"name":"/driverCameraState/sensor","color":"#bcbd22"},{"name":"/roadCameraState/sensor","color":"#1f77b4"},{"name":"/wideRoadCameraState/sensor","color":"#d62728"}]}]}},{"name":"SOF / EOF (cameraState)","root":{"split":"vertical","sizes":[0.500885,0.499115],"children":[{"title":"...","range":{"left":0.0,"right":630.006367,"top":65000000.0,"bottom":35000000.0},"curves":[{"name":"/driverCameraState/timestampSof","color":"#1f77b4","transform":"derivative","derivative_dt":1.0},{"name":"/roadCameraState/timestampSof","color":"#d62728","transform":"derivative","derivative_dt":1.0},{"name":"/wideRoadCameraState/timestampSof","color":"#1ac938","transform":"derivative","derivative_dt":1.0}],"y_limits":{"min":35000000.0,"max":65000000.0}},{"title":"...","range":{"left":0.0,"right":630.006367,"top":65000000.0,"bottom":35000000.0},"curves":[{"name":"/driverCameraState/timestampEof","color":"#ff7f0e","transform":"derivative","derivative_dt":1.0},{"name":"/roadCameraState/timestampEof","color":"#f14cc1","transform":"derivative","derivative_dt":1.0},{"name":"/wideRoadCameraState/timestampEof","color":"#9467bd","transform":"derivative","derivative_dt":1.0}],"y_limits":{"min":35000000.0,"max":65000000.0}}]}}]} +{"current_tab_index":0,"tabs":[{"name":"SOF / EOF (encodeIdx)","root":{"split":"vertical","sizes":[0.500885,0.499115],"children":[{"title":"...","range":{"left":0.0,"right":630.006367,"top":65000000.0,"bottom":35000000.0},"curves":[{"name":"/cabinEncodeIdx/timestampSof","color":"#1f77b4","transform":"derivative","derivative_dt":1.0},{"name":"/narrowRoadEncodeIdx/timestampSof","color":"#d62728","transform":"derivative","derivative_dt":1.0},{"name":"/wideRoadEncodeIdx/timestampSof","color":"#1ac938","transform":"derivative","derivative_dt":1.0}],"y_limits":{"min":35000000.0,"max":65000000.0}},{"title":"...","range":{"left":0.0,"right":630.006367,"top":65000000.0,"bottom":35000000.0},"curves":[{"name":"/cabinEncodeIdx/timestampEof","color":"#f14cc1","transform":"derivative","derivative_dt":1.0},{"name":"/narrowRoadEncodeIdx/timestampEof","color":"#9467bd","transform":"derivative","derivative_dt":1.0},{"name":"/wideRoadEncodeIdx/timestampEof","color":"#17becf","transform":"derivative","derivative_dt":1.0}],"y_limits":{"min":35000000.0,"max":65000000.0}}]}},{"name":"model timings","root":{"split":"vertical","sizes":[0.5,0.5],"children":[{"title":"...","range":{"left":0.0,"right":630.006367,"top":0.016865,"bottom":0.015143},"curves":[{"name":"/modelV2/modelExecutionTime","color":"#ff7f0e"}]},{"title":"...","range":{"left":0.0,"right":630.006367,"top":0.1,"bottom":-0.1},"curves":[{"name":"/modelV2/frameDropPerc","color":"#f14cc1"}]}]}},{"name":"sensor info","root":{"split":"vertical","sizes":[1.0],"children":[{"title":"...","range":{"left":0.0,"right":630.006367,"top":0.1,"bottom":-0.1},"curves":[{"name":"/cabinCameraState/sensor","color":"#bcbd22"},{"name":"/narrowRoadCameraState/sensor","color":"#1f77b4"},{"name":"/wideRoadCameraState/sensor","color":"#d62728"}]}]}},{"name":"SOF / EOF (cameraState)","root":{"split":"vertical","sizes":[0.500885,0.499115],"children":[{"title":"...","range":{"left":0.0,"right":630.006367,"top":65000000.0,"bottom":35000000.0},"curves":[{"name":"/cabinCameraState/timestampSof","color":"#1f77b4","transform":"derivative","derivative_dt":1.0},{"name":"/narrowRoadCameraState/timestampSof","color":"#d62728","transform":"derivative","derivative_dt":1.0},{"name":"/wideRoadCameraState/timestampSof","color":"#1ac938","transform":"derivative","derivative_dt":1.0}],"y_limits":{"min":35000000.0,"max":65000000.0}},{"title":"...","range":{"left":0.0,"right":630.006367,"top":65000000.0,"bottom":35000000.0},"curves":[{"name":"/cabinCameraState/timestampEof","color":"#ff7f0e","transform":"derivative","derivative_dt":1.0},{"name":"/narrowRoadCameraState/timestampEof","color":"#f14cc1","transform":"derivative","derivative_dt":1.0},{"name":"/wideRoadCameraState/timestampEof","color":"#9467bd","transform":"derivative","derivative_dt":1.0}],"y_limits":{"min":35000000.0,"max":65000000.0}}]}}]} diff --git a/openpilot/tools/jotpluggler/layouts/cameras-and-map.json b/openpilot/tools/jotpluggler/layouts/cameras-and-map.json index 68c590f7bc..7e120fcd5c 100644 --- a/openpilot/tools/jotpluggler/layouts/cameras-and-map.json +++ b/openpilot/tools/jotpluggler/layouts/cameras-and-map.json @@ -1 +1 @@ -{"current_tab_index": 0, "tabs": [{"name": "tab1", "root": {"children": [{"children": [{"curves": [], "kind": "map", "title": "Map"}, {"camera_view": "road", "curves": [], "kind": "camera", "title": "Road Camera"}], "sizes": [0.5, 0.5], "split": "horizontal"}, {"children": [{"camera_view": "wide_road", "curves": [], "kind": "camera", "title": "Wide Road Camera"}, {"camera_view": "driver", "curves": [], "kind": "camera", "title": "Driver Camera"}], "sizes": [0.5, 0.5], "split": "horizontal"}], "sizes": [0.5, 0.5], "split": "vertical"}}]} +{"current_tab_index": 0, "tabs": [{"name": "tab1", "root": {"children": [{"children": [{"curves": [], "kind": "map", "title": "Map"}, {"camera_view": "road", "curves": [], "kind": "camera", "title": "Road Camera"}], "sizes": [0.5, 0.5], "split": "horizontal"}, {"children": [{"camera_view": "wide_road", "curves": [], "kind": "camera", "title": "Wide Road Camera"}, {"camera_view": "driver", "curves": [], "kind": "camera", "title": "Cabin Camera"}], "sizes": [0.5, 0.5], "split": "horizontal"}], "sizes": [0.5, 0.5], "split": "vertical"}}]} diff --git a/openpilot/tools/jotpluggler/layouts/driver-monitoring-debug.json b/openpilot/tools/jotpluggler/layouts/driver-monitoring-debug.json index 07a5c2fd6e..3197f40ad8 100644 --- a/openpilot/tools/jotpluggler/layouts/driver-monitoring-debug.json +++ b/openpilot/tools/jotpluggler/layouts/driver-monitoring-debug.json @@ -1 +1 @@ -{"current_tab_index": 0, "tabs": [{"name": "tab1", "root": {"children": [{"children": [{"camera_view": "driver", "curves": [], "kind": "camera", "title": "Driver Camera"}, {"curves": [{"color": "#236bb4", "name": "/driverMonitoringState/alertLevel"}], "title": "..."}], "sizes": [0.5, 0.5], "split": "vertical"}, {"children": [{"curves": [{"color": "#236bb4", "name": "/driverMonitoringState/activePolicy"}], "title": "..."}, {"curves": [{"color": "#236bb4", "name": "/driverMonitoringState/visionPolicyState/faceDetected"}], "title": "..."}, {"curves": [{"color": "#236bb4", "name": "/driverMonitoringState/visionPolicyState/distractedTypes/eye"}, {"color": "#dc5234", "name": "/driverMonitoringState/visionPolicyState/distractedTypes/phone"}, {"color": "#43a047", "name": "/driverMonitoringState/visionPolicyState/distractedTypes/pose"}], "title": "..."}, {"curves": [{"color": "#236bb4", "name": "/driverMonitoringState/visionPolicyState/awarenessPercent"}], "title": "..."}], "sizes": [0.25, 0.25, 0.25, 0.25], "split": "vertical"}], "sizes": [0.5, 0.5], "split": "horizontal"}}]} +{"current_tab_index": 0, "tabs": [{"name": "tab1", "root": {"children": [{"children": [{"camera_view": "driver", "curves": [], "kind": "camera", "title": "Cabin Camera"}, {"curves": [{"color": "#236bb4", "name": "/driverMonitoringState/alertLevel"}], "title": "..."}], "sizes": [0.5, 0.5], "split": "vertical"}, {"children": [{"curves": [{"color": "#236bb4", "name": "/driverMonitoringState/activePolicy"}], "title": "..."}, {"curves": [{"color": "#236bb4", "name": "/driverMonitoringState/visionPolicyState/faceDetected"}], "title": "..."}, {"curves": [{"color": "#236bb4", "name": "/driverMonitoringState/visionPolicyState/distractedTypes/eye"}, {"color": "#dc5234", "name": "/driverMonitoringState/visionPolicyState/distractedTypes/phone"}, {"color": "#43a047", "name": "/driverMonitoringState/visionPolicyState/distractedTypes/pose"}], "title": "..."}, {"curves": [{"color": "#236bb4", "name": "/driverMonitoringState/visionPolicyState/awarenessPercent"}], "title": "..."}], "sizes": [0.25, 0.25, 0.25, 0.25], "split": "vertical"}], "sizes": [0.5, 0.5], "split": "horizontal"}}]} diff --git a/openpilot/tools/jotpluggler/runtime.cc b/openpilot/tools/jotpluggler/runtime.cc index a1e47c7e8e..d7d0fc79c4 100644 --- a/openpilot/tools/jotpluggler/runtime.cc +++ b/openpilot/tools/jotpluggler/runtime.cc @@ -39,11 +39,11 @@ const bool kLogCameraTimings = env_flag_enabled("JOTP_CAMERA_TIMINGS"); CameraType decoder_camera_type(CameraViewKind view) { switch (view) { - case CameraViewKind::Driver: return DriverCam; + case CameraViewKind::Cabin: return CabinCam; case CameraViewKind::WideRoad: return WideRoadCam; - case CameraViewKind::QRoad: return RoadCam; + case CameraViewKind::QRoad: return NarrowRoadCam; case CameraViewKind::Road: - default: return RoadCam; + default: return NarrowRoadCam; } } @@ -59,17 +59,17 @@ bool stream_batch_has_data(const StreamExtractBatch &batch) { bool should_subscribe_stream_service(const std::string &name) { static const std::array kSkippedServices = {{ - "roadEncodeIdx", - "driverEncodeIdx", + "narrowRoadEncodeIdx", + "cabinEncodeIdx", "wideRoadEncodeIdx", - "qRoadEncodeIdx", - "roadEncodeData", - "driverEncodeData", + "qNarrowRoadEncodeIdx", + "narrowRoadEncodeData", + "cabinEncodeData", "wideRoadEncodeData", - "qRoadEncodeData", + "qNarrowRoadEncodeData", "livestreamWideRoadEncodeIdx", - "livestreamRoadEncodeIdx", - "livestreamDriverEncodeIdx", + "livestreamNarrowRoadEncodeIdx", + "livestreamCabinEncodeIdx", "thumbnail", }}; if (name == "rawAudioData") return false; diff --git a/openpilot/tools/jotpluggler/sketch_layout.cc b/openpilot/tools/jotpluggler/sketch_layout.cc index ddb38fd473..5440d9dd8d 100644 --- a/openpilot/tools/jotpluggler/sketch_layout.cc +++ b/openpilot/tools/jotpluggler/sketch_layout.cc @@ -45,9 +45,9 @@ struct RouteSelection { struct SegmentLogs { std::string rlog; std::string qlog; - std::string fcamera; - std::string dcamera; - std::string ecamera; + std::string narrow_road; + std::string cabin; + std::string wide_road; std::string qcamera; }; @@ -295,11 +295,11 @@ void add_log_file_to_segments(std::map *segments, int segment_ } else if (name == "qlog.bz2" || name == "qlog.zst" || name == "qlog") { segment.qlog = file; } else if (name == "fcamera.hevc") { - segment.fcamera = file; + segment.narrow_road = file; } else if (name == "dcamera.hevc") { - segment.dcamera = file; + segment.cabin = file; } else if (name == "ecamera.hevc") { - segment.ecamera = file; + segment.wide_road = file; } else if (name == "qcamera.ts") { segment.qcamera = file; } @@ -1886,10 +1886,10 @@ RouteData load_route_data(const std::string &route_name, metadata.car_fingerprint, resolved_dbc); route_data.route_id = make_route_identifier(route, segments); - build_camera_index(segments, route_data, &SegmentLogs::fcamera, "roadEncodeIdx", &route_data.road_camera); - build_camera_index(segments, route_data, &SegmentLogs::dcamera, "driverEncodeIdx", &route_data.driver_camera); - build_camera_index(segments, route_data, &SegmentLogs::ecamera, "wideRoadEncodeIdx", &route_data.wide_road_camera); - build_camera_index(segments, route_data, &SegmentLogs::qcamera, "qRoadEncodeIdx", &route_data.qroad_camera); + build_camera_index(segments, route_data, &SegmentLogs::narrow_road, "narrowRoadEncodeIdx", &route_data.road_camera); + build_camera_index(segments, route_data, &SegmentLogs::cabin, "cabinEncodeIdx", &route_data.cabin_camera); + build_camera_index(segments, route_data, &SegmentLogs::wide_road, "wideRoadEncodeIdx", &route_data.wide_road_camera); + build_camera_index(segments, route_data, &SegmentLogs::qcamera, "qNarrowRoadEncodeIdx", &route_data.qroad_camera); stats.load_end = LoadStats::Clock::now(); stats.publish(RouteLoadStage::Finished, segments.size(), {}); stats.print_summary(route_data.series.size()); diff --git a/openpilot/tools/lib/log_time_series.py b/openpilot/tools/lib/log_time_series.py index 5e8aa4f73b..eba821bc84 100644 --- a/openpilot/tools/lib/log_time_series.py +++ b/openpilot/tools/lib/log_time_series.py @@ -80,5 +80,5 @@ if __name__ == "__main__": import sys from openpilot.tools.lib.logreader import LogReader m = msgs_to_time_series(LogReader(sys.argv[1])) - print(m['driverCameraState']['t']) - print(np.diff(m['driverCameraState']['timestampSof'])) + print(m['cabinCameraState']['t']) + print(np.diff(m['cabinCameraState']['timestampSof'])) diff --git a/openpilot/tools/plotjuggler/layouts/camera-timings.xml b/openpilot/tools/plotjuggler/layouts/camera-timings.xml index f91cbd3f53..2abff4d508 100644 --- a/openpilot/tools/plotjuggler/layouts/camera-timings.xml +++ b/openpilot/tools/plotjuggler/layouts/camera-timings.xml @@ -8,13 +8,13 @@ - - + + - - + + @@ -29,13 +29,13 @@ - - + + - - + + @@ -76,8 +76,8 @@ - - + + @@ -91,13 +91,13 @@ - - + + - - + + @@ -112,13 +112,13 @@ - - + + - - + + @@ -145,4 +145,3 @@ - diff --git a/openpilot/tools/replay/README.md b/openpilot/tools/replay/README.md index 108df08935..1d500eac96 100644 --- a/openpilot/tools/replay/README.md +++ b/openpilot/tools/replay/README.md @@ -68,8 +68,8 @@ Options: internal, openpilotci, comma_api, car_segments, testing_closet --data_dir local directory with routes --prefix set OPENPILOT_PREFIX - --dcam load driver camera - --ecam load wide road camera + --cabin load cabin camera + --wide-road load wide road camera --no-loop stop at the end of the route --no-cache turn off local cache --qcam load qcamera @@ -103,11 +103,11 @@ openpilot/tools/plotjuggler/juggle.py --stream watch all three cameras simultaneously from your comma three routes with watch3 -simply replay a route using the `--dcam` and `--ecam` flags: +simply replay a route using the `--cabin` and `--wide-road` flags: ```bash # start a replay -cd openpilot/tools/replay && ./replay --demo --dcam --ecam +cd openpilot/tools/replay && ./replay --demo --cabin --wide-road # then start watch3 cd openpilot/selfdrive/ui && ./watch3.py diff --git a/openpilot/tools/replay/camera.h b/openpilot/tools/replay/camera.h index 35fab5f7d3..81b3bb1a81 100644 --- a/openpilot/tools/replay/camera.h +++ b/openpilot/tools/replay/camera.h @@ -33,8 +33,8 @@ protected: VisionBuf *getFrame(Camera &cam, FrameReader *fr, int32_t segment_id, uint32_t frame_id); Camera cameras_[MAX_CAMERAS] = { - {.type = RoadCam, .stream_type = VISION_STREAM_ROAD}, - {.type = DriverCam, .stream_type = VISION_STREAM_DRIVER}, + {.type = NarrowRoadCam, .stream_type = VISION_STREAM_NARROW_ROAD}, + {.type = CabinCam, .stream_type = VISION_STREAM_CABIN}, {.type = WideRoadCam, .stream_type = VISION_STREAM_WIDE_ROAD}, }; std::atomic publishing_ = 0; diff --git a/openpilot/tools/replay/logreader.cc b/openpilot/tools/replay/logreader.cc index 0416413b4c..315aed9741 100644 --- a/openpilot/tools/replay/logreader.cc +++ b/openpilot/tools/replay/logreader.cc @@ -74,8 +74,8 @@ bool LogReader::load(const char *data, size_t size, std::atomic *abort, uint64_t mono_time = event.getLogMonoTime(); const Event &evt = events.emplace_back(which, mono_time, event_data); // Add encodeIdx packet again as a frame packet for the video stream - if (evt.which == cereal::Event::ROAD_ENCODE_IDX || - evt.which == cereal::Event::DRIVER_ENCODE_IDX || + if (evt.which == cereal::Event::NARROW_ROAD_ENCODE_IDX || + evt.which == cereal::Event::CABIN_ENCODE_IDX || evt.which == cereal::Event::WIDE_ROAD_ENCODE_IDX) { auto idx = capnp::AnyStruct::Reader(event).getPointerSection()[0].getAs(); if (idx.getType() == cereal::EncodeIndex::Type::FULL_H_E_V_C) { diff --git a/openpilot/tools/replay/logreader.h b/openpilot/tools/replay/logreader.h index 63f7468401..52e82c46ad 100644 --- a/openpilot/tools/replay/logreader.h +++ b/openpilot/tools/replay/logreader.h @@ -8,7 +8,7 @@ #include "openpilot/cereal/gen/cpp/log.capnp.h" #include "tools/replay/util.h" -const CameraType ALL_CAMERAS[] = {RoadCam, DriverCam, WideRoadCam}; +const CameraType ALL_CAMERAS[] = {NarrowRoadCam, CabinCam, WideRoadCam}; const int MAX_CAMERAS = std::size(ALL_CAMERAS); class Event { diff --git a/openpilot/tools/replay/main.cc b/openpilot/tools/replay/main.cc index bdb9cf4f35..50233189a1 100644 --- a/openpilot/tools/replay/main.cc +++ b/openpilot/tools/replay/main.cc @@ -26,8 +26,8 @@ Options: internal, openpilotci, comma_api, car_segments, testing_closet -d, --data_dir Local directory with routes -p, --prefix Set OPENPILOT_PREFIX - --dcam Load driver camera - --ecam Load wide road camera + --cabin Load cabin camera (alias: --dcam) + --wide-road Load wide road camera (alias: --ecam) --no-loop Stop at the end of the route --no-cache Turn off local cache --qcam Load qcamera @@ -62,8 +62,10 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) { {"auto", no_argument, nullptr, 0}, {"data_dir", required_argument, nullptr, 'd'}, {"prefix", required_argument, nullptr, 'p'}, - {"dcam", no_argument, nullptr, 0}, - {"ecam", no_argument, nullptr, 0}, + {"cabin", no_argument, nullptr, 0}, + {"dcam", no_argument, nullptr, 0}, // deprecated alias + {"wide-road", no_argument, nullptr, 0}, + {"ecam", no_argument, nullptr, 0}, // deprecated alias {"no-loop", no_argument, nullptr, 0}, {"no-cache", no_argument, nullptr, 0}, {"qcam", no_argument, nullptr, 0}, @@ -76,8 +78,10 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) { }; const std::map flag_map = { - {"dcam", REPLAY_FLAG_DCAM}, - {"ecam", REPLAY_FLAG_ECAM}, + {"cabin", REPLAY_FLAG_CABIN_CAMERA}, + {"dcam", REPLAY_FLAG_CABIN_CAMERA}, // deprecated alias + {"wide-road", REPLAY_FLAG_WIDE_ROAD}, + {"ecam", REPLAY_FLAG_WIDE_ROAD}, // deprecated alias {"no-loop", REPLAY_FLAG_NO_LOOP}, {"no-cache", REPLAY_FLAG_NO_FILE_CACHE}, {"qcam", REPLAY_FLAG_QCAMERA}, diff --git a/openpilot/tools/replay/replay.cc b/openpilot/tools/replay/replay.cc index 75aecfd0c2..6449fc30ea 100644 --- a/openpilot/tools/replay/replay.cc +++ b/openpilot/tools/replay/replay.cc @@ -251,13 +251,13 @@ void Replay::publishMessage(const Event *e) { void Replay::publishFrame(const Event *e) { CameraType cam; switch (e->which) { - case cereal::Event::ROAD_ENCODE_IDX: cam = RoadCam; break; - case cereal::Event::DRIVER_ENCODE_IDX: cam = DriverCam; break; + case cereal::Event::NARROW_ROAD_ENCODE_IDX: cam = NarrowRoadCam; break; + case cereal::Event::CABIN_ENCODE_IDX: cam = CabinCam; break; case cereal::Event::WIDE_ROAD_ENCODE_IDX: cam = WideRoadCam; break; default: return; // Invalid event type } - if ((cam == DriverCam && !hasFlag(REPLAY_FLAG_DCAM)) || (cam == WideRoadCam && !hasFlag(REPLAY_FLAG_ECAM))) + if ((cam == CabinCam && !hasFlag(REPLAY_FLAG_CABIN_CAMERA)) || (cam == WideRoadCam && !hasFlag(REPLAY_FLAG_WIDE_ROAD))) return; // Camera isdisabled auto seg_it = event_data_->segments.find(e->eidx_segnum); diff --git a/openpilot/tools/replay/replay.h b/openpilot/tools/replay/replay.h index ce6d75bd6d..7cd5fbecd0 100644 --- a/openpilot/tools/replay/replay.h +++ b/openpilot/tools/replay/replay.h @@ -16,8 +16,8 @@ enum REPLAY_FLAGS { REPLAY_FLAG_NONE = 0x0000, - REPLAY_FLAG_DCAM = 0x0002, - REPLAY_FLAG_ECAM = 0x0004, + REPLAY_FLAG_CABIN_CAMERA = 0x0002, + REPLAY_FLAG_WIDE_ROAD = 0x0004, REPLAY_FLAG_NO_LOOP = 0x0010, REPLAY_FLAG_NO_FILE_CACHE = 0x0020, REPLAY_FLAG_QCAMERA = 0x0040, diff --git a/openpilot/tools/replay/route.cc b/openpilot/tools/replay/route.cc index 326d28d726..f38c2b1971 100644 --- a/openpilot/tools/replay/route.cc +++ b/openpilot/tools/replay/route.cc @@ -180,9 +180,9 @@ void Route::addFileToSegment(int n, const std::string &file) { } else if (name == "qlog.bz2" || name == "qlog.zst" || name == "qlog") { segments_[n].qlog = file; } else if (name == "fcamera.hevc") { - segments_[n].road_cam = file; + segments_[n].narrow_road_cam = file; } else if (name == "dcamera.hevc") { - segments_[n].driver_cam = file; + segments_[n].cabin_cam = file; } else if (name == "ecamera.hevc") { segments_[n].wide_road_cam = file; } else if (name == "qcamera.ts") { @@ -195,11 +195,11 @@ void Route::addFileToSegment(int n, const std::string &file) { Segment::Segment(int n, const SegmentFile &files, uint32_t flags, const std::vector &filters, std::function callback) : seg_num(n), flags(flags), filters_(filters), on_load_finished_(callback) { - // [RoadCam, DriverCam, WideRoadCam, log]. fallback to qcamera/qlog + // [NarrowRoadCam, CabinCam, WideRoadCam, log]. fallback to qcamera/qlog const std::array file_list = { - (flags & REPLAY_FLAG_QCAMERA) || files.road_cam.empty() ? files.qcamera : files.road_cam, - flags & REPLAY_FLAG_DCAM ? files.driver_cam : "", - flags & REPLAY_FLAG_ECAM ? files.wide_road_cam : "", + (flags & REPLAY_FLAG_QCAMERA) || files.narrow_road_cam.empty() ? files.qcamera : files.narrow_road_cam, + flags & REPLAY_FLAG_CABIN_CAMERA ? files.cabin_cam : "", + flags & REPLAY_FLAG_WIDE_ROAD ? files.wide_road_cam : "", files.rlog.empty() ? files.qlog : files.rlog, }; for (int i = 0; i < file_list.size(); ++i) { diff --git a/openpilot/tools/replay/route.h b/openpilot/tools/replay/route.h index 50f3eba854..7a55997969 100644 --- a/openpilot/tools/replay/route.h +++ b/openpilot/tools/replay/route.h @@ -33,8 +33,8 @@ struct RouteIdentifier { struct SegmentFile { std::string rlog; std::string qlog; - std::string road_cam; - std::string driver_cam; + std::string narrow_road_cam; + std::string cabin_cam; std::string wide_road_cam; std::string qcamera; }; diff --git a/openpilot/tools/replay/ui.py b/openpilot/tools/replay/ui.py index 2c253a1364..16c050f8a6 100755 --- a/openpilot/tools/replay/ui.py +++ b/openpilot/tools/replay/ui.py @@ -57,7 +57,7 @@ def ui_thread(addr): font_path = os.path.join(BASEDIR, "openpilot/selfdrive/assets/fonts/JetBrainsMono-Medium.ttf") font = rl.load_font_ex(font_path, 32, None, 0) - camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_ROAD) + camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_NARROW_ROAD) # Overlay texture for model/lane line drawing overlay_img = np.zeros((480, 640, 4), dtype='uint8') @@ -82,7 +82,7 @@ def ui_thread(addr): 'liveTracks', 'modelV2', 'liveParameters', - 'roadCameraState', + 'narrowRoadCameraState', ], addr=addr, ) @@ -152,13 +152,13 @@ def ui_thread(addr): sm.update(0) - camera = DEVICE_CAMERAS[("tici", str(sm['roadCameraState'].sensor))] - calib_scale = camera.fcam.width / 640.0 + camera = DEVICE_CAMERAS[("tici", str(sm['narrowRoadCameraState'].sensor))] + calib_scale = camera.narrow_road.width / 640.0 if camera_view.frame: num_px = camera_view.frame.width * camera_view.frame.height - intrinsic_matrix = camera.fcam.intrinsics + intrinsic_matrix = camera.narrow_road.intrinsics w = sm['controlsState'].lateralControlState.which() if w == 'lqrStateDEPRECATED': diff --git a/openpilot/tools/replay/util.h b/openpilot/tools/replay/util.h index 45d6f7cd0b..5cffc11706 100644 --- a/openpilot/tools/replay/util.h +++ b/openpilot/tools/replay/util.h @@ -10,8 +10,8 @@ #include "openpilot/cereal/messaging/messaging.h" enum CameraType { - RoadCam = 0, - DriverCam, + NarrowRoadCam = 0, + CabinCam, WideRoadCam }; diff --git a/openpilot/tools/sim/lib/camerad.py b/openpilot/tools/sim/lib/camerad.py index d4825eada7..206e4fdf90 100644 --- a/openpilot/tools/sim/lib/camerad.py +++ b/openpilot/tools/sim/lib/camerad.py @@ -38,20 +38,20 @@ def rgb_to_nv12(rgb): class Camerad: """Simulates the camerad daemon""" def __init__(self, dual_camera): - self.pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState']) + self.pm = messaging.PubMaster(['narrowRoadCameraState', 'wideRoadCameraState']) self.frame_road_id = 0 self.frame_wide_id = 0 self.vipc_server = VisionIpcServer("camerad") - self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 5, W, H) + self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_NARROW_ROAD, 5, W, H) if dual_camera: self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 5, W, H) self.vipc_server.start_listener() def cam_send_yuv_road(self, yuv): - self._send_yuv(yuv, self.frame_road_id, 'roadCameraState', VisionStreamType.VISION_STREAM_ROAD) + self._send_yuv(yuv, self.frame_road_id, 'narrowRoadCameraState', VisionStreamType.VISION_STREAM_NARROW_ROAD) self.frame_road_id += 1 def cam_send_yuv_wide_road(self, yuv): diff --git a/tools/scripts/cycle_alerts.py b/tools/scripts/cycle_alerts.py index 0bd42bcd3f..cf4a1d8999 100755 --- a/tools/scripts/cycle_alerts.py +++ b/tools/scripts/cycle_alerts.py @@ -50,11 +50,11 @@ def cycle_alerts(duration=200, is_metric=False): (EventName.cameraFrameRate, ET.PERMANENT), ] - cameras = ['roadCameraState', 'wideRoadCameraState', 'driverCameraState'] + cameras = ['narrowRoadCameraState', 'wideRoadCameraState', 'cabinCameraState'] CS = car.CarState.new_message() CP = CarInterface.get_non_essential_params("HONDA_CIVIC") - sm = messaging.SubMaster(['deviceState', 'pandaStates', 'roadCameraState', 'modelV2', 'liveCalibration', + sm = messaging.SubMaster(['deviceState', 'pandaStates', 'narrowRoadCameraState', 'modelV2', 'liveCalibration', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'managerState'] + cameras) From f8d50e062d00e1cb9bcc6ecf5a06f63ac5599eaf Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 8 Aug 2026 22:55:32 -0400 Subject: [PATCH 147/170] updated: add fallback mechanism for `agnos.json` path resolution (#38593) * updated: add fallback mechanism for agnos.json path resolution * nvm fallback seems overkill, red diff * add symlink for tici/agnos.json --------- Co-authored-by: stefpi <19478336+stefpi@users.noreply.github.com> --- openpilot/system/hardware/tici/agnos.json | 1 + system/hardware/tici/agnos.json | 1 + 2 files changed, 2 insertions(+) create mode 120000 openpilot/system/hardware/tici/agnos.json create mode 120000 system/hardware/tici/agnos.json diff --git a/openpilot/system/hardware/tici/agnos.json b/openpilot/system/hardware/tici/agnos.json new file mode 120000 index 0000000000..87cb197052 --- /dev/null +++ b/openpilot/system/hardware/tici/agnos.json @@ -0,0 +1 @@ +../comma/agnos.json \ No newline at end of file diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json new file mode 120000 index 0000000000..7bc8df1b8c --- /dev/null +++ b/system/hardware/tici/agnos.json @@ -0,0 +1 @@ +../../../openpilot/system/hardware/comma/agnos.json \ No newline at end of file From 855c99bf923aaf10b72e9938bc52dfe61f17cc65 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Sun, 9 Aug 2026 16:26:55 -0700 Subject: [PATCH 148/170] speedup chestnut probe (#38596) use link-up in scons --- openpilot/selfdrive/modeld/SConscript | 18 ++++++------------ openpilot/system/hardware/chestnut/flash.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/openpilot/selfdrive/modeld/SConscript b/openpilot/selfdrive/modeld/SConscript index 2085d65835..30b008078e 100644 --- a/openpilot/selfdrive/modeld/SConscript +++ b/openpilot/selfdrive/modeld/SConscript @@ -1,7 +1,6 @@ import glob import json import os -import sys, subprocess import time from SCons.Script import Action, Value from openpilot.common.file_chunker import chunk_file, get_chunk_targets, get_existing_chunks @@ -88,18 +87,13 @@ for usbgpu in [False, True] if USBGPU else [False]: onnx_sizes_sum = sum(os.path.getsize(f) for f in driving_onnx_deps) chunk_targets = get_chunk_targets(target_pkl_path, estimate_pickle_max_size(onnx_sizes_sum)) def do_compile(target, source, env, command=cmd, pkl=target_pkl_path, chunks=chunk_targets): + from openpilot.system.hardware.chestnut.flash import link_up # chestnut can enumerate before its PCIe link is up due to varying 12V power behavior across cars - probe_cmd = [sys.executable, '-c', 'from openpilot.selfdrive.modeld.compile_modeld import Device; Device[Device.DEFAULT]'] - probe_env = {**env['ENV'], 'DEV': 'USB+AMD'} - ready = False - for attempt in range(3): - if attempt: time.sleep(1) - try: - ready = subprocess.run(probe_cmd, env=probe_env, capture_output=True, timeout=10).returncode == 0 - except subprocess.TimeoutExpired: - pass - if ready: break - if not ready: + for _ in range(10): + if link_up(): + break + time.sleep(1) + else: print("Chestnut not ready, skipping big model build") return if ret := env.Execute(command): diff --git a/openpilot/system/hardware/chestnut/flash.py b/openpilot/system/hardware/chestnut/flash.py index cd9901b476..32aa5a443a 100755 --- a/openpilot/system/hardware/chestnut/flash.py +++ b/openpilot/system/hardware/chestnut/flash.py @@ -104,6 +104,26 @@ def open_device(path): return os.open(f"/dev/bus/usb/{bus:03d}/{dev:03d}", os.O_RDWR) +def link_up() -> bool: + # asm enumerates on USB-C alone, gpu is only usable once pcie link is up + try: + path, _, _ = find_chestnut() + if path is None: + return False + fd = open_device(path) + except (OSError, RuntimeError): + return False + try: + fcntl.ioctl(fd, USBDEVFS_CONTROL, Ctrl(0x40, 0xF3, 1, 0, 0, 2000, None)) + buf = (ctypes.c_ubyte * 1)() + fcntl.ioctl(fd, USBDEVFS_CONTROL, Ctrl(0xC0, 0xE4, 0xB450, 0, 1, 1000, ctypes.cast(buf, ctypes.c_void_p))) + return buf[0] == 0x78 # LTSSM L0 + except OSError: + return False + finally: + os.close(fd) + + def claim_interface(path, setup=False): # unbind usb-storage, which binds to the ROM bootloader disable_runtime_pm(path) From ea4dbc89364155c78d8e2d35d7b7fdcde88854f8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 9 Aug 2026 21:09:50 -0700 Subject: [PATCH 149/170] Remove unused code (#38598) --- openpilot/common/git.py | 9 ----- openpilot/common/stat_live.py | 3 -- .../selfdrive/controls/lib/drive_helpers.py | 1 - openpilot/selfdrive/controls/radard.py | 3 -- openpilot/selfdrive/selfdrived/events.py | 5 --- openpilot/selfdrive/ui/body/animations.py | 19 --------- .../ui/mici/layouts/settings/settings.py | 1 - openpilot/selfdrive/ui/mici/widgets/button.py | 1 - openpilot/selfdrive/ui/mici/widgets/dialog.py | 14 +------ openpilot/system/updated/common.py | 16 -------- tools/scripts/profiling/clpeak/no_print.patch | 39 ------------------- tools/scripts/test_fw_query_on_routes.py | 2 - 12 files changed, 1 insertion(+), 112 deletions(-) delete mode 100644 openpilot/system/updated/common.py delete mode 100644 tools/scripts/profiling/clpeak/no_print.patch diff --git a/openpilot/common/git.py b/openpilot/common/git.py index 6b662e5719..d285116957 100644 --- a/openpilot/common/git.py +++ b/openpilot/common/git.py @@ -31,12 +31,3 @@ def get_origin(cwd: str | None = None) -> str: return run_cmd(["git", "config", "remote." + tracking_remote + ".url"], cwd=cwd) except subprocess.CalledProcessError: # Not on a branch, fallback return run_cmd_default(["git", "config", "--get", "remote.origin.url"], cwd=cwd) - - -@cache -def get_normalized_origin(cwd: str | None = None) -> str: - return get_origin(cwd) \ - .replace("git@", "", 1) \ - .replace(".git", "", 1) \ - .replace("https://", "", 1) \ - .replace(":", "/", 1) diff --git a/openpilot/common/stat_live.py b/openpilot/common/stat_live.py index 3901c448d8..db0919b993 100644 --- a/openpilot/common/stat_live.py +++ b/openpilot/common/stat_live.py @@ -48,9 +48,6 @@ class RunningStat: def std(self): return np.sqrt(self.variance()) - def params_to_save(self): - return [self.M, self.S, self.n] - class RunningStatFilter: def __init__(self, raw_priors=None, filtered_priors=None, max_trackable=-1): self.raw_stat = RunningStat(raw_priors, -1) diff --git a/openpilot/selfdrive/controls/lib/drive_helpers.py b/openpilot/selfdrive/controls/lib/drive_helpers.py index 7ac2f50de0..35ca45e56e 100644 --- a/openpilot/selfdrive/controls/lib/drive_helpers.py +++ b/openpilot/selfdrive/controls/lib/drive_helpers.py @@ -7,7 +7,6 @@ CONTROL_N = 17 CAR_ROTATION_RADIUS = 0.0 # This is a turn radius smaller than most cars can achieve MAX_CURVATURE = 0.2 -MAX_VEL_ERR = 5.0 # m/s MIN_STABLE_DELAY = 0.3 # EU guidelines diff --git a/openpilot/selfdrive/controls/radard.py b/openpilot/selfdrive/controls/radard.py index 30824c07b8..6fecfffb1d 100755 --- a/openpilot/selfdrive/controls/radard.py +++ b/openpilot/selfdrive/controls/radard.py @@ -178,8 +178,6 @@ def get_lead(v_ego: float, ready: bool, tracks: dict[int, Track], lead_msg: capn class RadarD: def __init__(self, delay: float = 0.0): - self.current_time = 0.0 - self.tracks: dict[int, Track] = {} self.kalman_params = KalmanParams(DT_MDL) self.lead_prob_filters = [FirstOrderFilter(0.0, 0.2, DT_MDL) for _ in range(2)] @@ -195,7 +193,6 @@ class RadarD: def update(self, sm: messaging.SubMaster, rr: car.RadarData): self.ready = sm.seen['modelV2'] - self.current_time = 1e-9*max(sm.logMonoTime.values()) if sm.recv_frame['carState'] != self.last_v_ego_frame: self.v_ego = sm['carState'].vEgo diff --git a/openpilot/selfdrive/selfdrived/events.py b/openpilot/selfdrive/selfdrived/events.py index 0464647c3b..2127cdeceb 100755 --- a/openpilot/selfdrive/selfdrived/events.py +++ b/openpilot/selfdrive/selfdrived/events.py @@ -339,11 +339,6 @@ def low_memory_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaste return NormalPermanentAlert("Low Memory", f"{sm['deviceState'].memoryUsagePercent}% used") -def high_cpu_usage_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: - x = max(sm['deviceState'].cpuUsagePercent, default=0.) - return NormalPermanentAlert("High CPU Usage", f"{x}% used") - - def modeld_lagging_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: return NormalPermanentAlert("Driving Model Lagging", f"{sm['modelV2'].frameDropPerc:.1f}% frames dropped") diff --git a/openpilot/selfdrive/ui/body/animations.py b/openpilot/selfdrive/ui/body/animations.py index 302f8989bf..3aecd52e97 100644 --- a/openpilot/selfdrive/ui/body/animations.py +++ b/openpilot/selfdrive/ui/body/animations.py @@ -87,21 +87,12 @@ BROW_LOWERED = [ (2, 0) ] BROW_STRAIGHT = [(1, 0), (1, 1), (1, 2)] -BROW_DOWN = [ -(0, 1), (0, 2), - (1, 3) -] - # Mouths (centered, not mirrored) MOUTH_SMILE = [ (6, 6), (6, 9), (7, 7), (7, 8), ] MOUTH_NORMAL = [(7, 7), (7, 8)] -MOUTH_SAD = [ - (6, 7), (6, 8), -(7, 6), (7, 9) -] # --- Animations --- @@ -168,16 +159,6 @@ INQUISITIVE = Animation( repeat_interval=10 ) -WINK = Animation( - frames=[ - _make_frame(EYE_OPEN, _mirror(EYE_OPEN), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), - _make_frame(EYE_OPEN, _mirror(EYE_CLOSED), BROW_HIGH, _mirror(_shift(BROW_DOWN, (0, 2))), MOUTH_SMILE), - ], - mode=AnimationMode.ONCE_FORWARD_BACKWARD, - frame_duration=0.75, -) - - # --- Face Animator Class --- class FaceAnimator: diff --git a/openpilot/selfdrive/ui/mici/layouts/settings/settings.py b/openpilot/selfdrive/ui/mici/layouts/settings/settings.py index 56a953a65d..eb7789cba9 100644 --- a/openpilot/selfdrive/ui/mici/layouts/settings/settings.py +++ b/openpilot/selfdrive/ui/mici/layouts/settings/settings.py @@ -50,7 +50,6 @@ class SettingsLayout(NavScroller): device_btn, software_btn, PairBigButton(), - #BigDialogButton("manual", "", "icons_mici/settings/manual_icon.png", "Check out the mici user\nmanual at comma.ai/setup"), firehose_btn, developer_btn, ]) diff --git a/openpilot/selfdrive/ui/mici/widgets/button.py b/openpilot/selfdrive/ui/mici/widgets/button.py index dcb1dc2fd0..0ecda6a0c5 100644 --- a/openpilot/selfdrive/ui/mici/widgets/button.py +++ b/openpilot/selfdrive/ui/mici/widgets/button.py @@ -17,7 +17,6 @@ else: except (ImportError, OSError): Params = None -SCROLLING_SPEED_PX_S = 50 COMPLICATION_SIZE = 36 LABEL_COLOR = rl.Color(255, 255, 255, int(255 * 0.9)) COMPLICATION_GREY = rl.Color(0xAA, 0xAA, 0xAA, 255) diff --git a/openpilot/selfdrive/ui/mici/widgets/dialog.py b/openpilot/selfdrive/ui/mici/widgets/dialog.py index ed1466449b..77dfa3cb97 100644 --- a/openpilot/selfdrive/ui/mici/widgets/dialog.py +++ b/openpilot/selfdrive/ui/mici/widgets/dialog.py @@ -10,7 +10,7 @@ from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton, GreyBigButton +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, GreyBigButton DEBUG = False @@ -216,18 +216,6 @@ class BigInputDialog(BigDialogBase): self._confirm_callback() -class BigDialogButton(BigButton): - def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", description: str = ""): - super().__init__(text, value, icon) - self._description = description - - def _handle_mouse_release(self, mouse_pos: MousePos): - super()._handle_mouse_release(mouse_pos) - - dlg = BigDialog(self.text, self._description) - gui_app.push_widget(dlg) - - class BigConfirmationCircleButton(BigCircleButton): def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable[[], None], exit_on_confirm: bool = True, red: bool = False, icon_offset: tuple[int, int] = (0, 0)): diff --git a/openpilot/system/updated/common.py b/openpilot/system/updated/common.py deleted file mode 100644 index 6bb745f6b0..0000000000 --- a/openpilot/system/updated/common.py +++ /dev/null @@ -1,16 +0,0 @@ -import os -import pathlib - - -def get_consistent_flag(path: str) -> bool: - consistent_file = pathlib.Path(os.path.join(path, ".overlay_consistent")) - return consistent_file.is_file() - -def set_consistent_flag(path: str, consistent: bool) -> None: - os.sync() - consistent_file = pathlib.Path(os.path.join(path, ".overlay_consistent")) - if consistent: - consistent_file.touch() - elif not consistent: - consistent_file.unlink(missing_ok=True) - os.sync() diff --git a/tools/scripts/profiling/clpeak/no_print.patch b/tools/scripts/profiling/clpeak/no_print.patch deleted file mode 100644 index 44a5efd10f..0000000000 --- a/tools/scripts/profiling/clpeak/no_print.patch +++ /dev/null @@ -1,39 +0,0 @@ -diff --git a/src/logger.cpp b/src/logger.cpp -index a63c6dd..a1d9860 100644 ---- a/src/logger.cpp -+++ b/src/logger.cpp -@@ -24,34 +24,22 @@ logger::~logger() - - void logger::print(string str) - { -- cout << str; -- cout.flush(); - } - - void logger::print(double val) - { -- cout << setprecision(2) << fixed; -- cout << val; -- cout.flush(); - } - - void logger::print(float val) - { -- cout << setprecision(2) << fixed; -- cout << val; -- cout.flush(); - } - - void logger::print(int val) - { -- cout << val; -- cout.flush(); - } - - void logger::print(unsigned int val) - { -- cout << val; -- cout.flush(); - } - - void logger::xmlOpenTag(string tag) diff --git a/tools/scripts/test_fw_query_on_routes.py b/tools/scripts/test_fw_query_on_routes.py index ab539e4feb..33e1aa39ba 100755 --- a/tools/scripts/test_fw_query_on_routes.py +++ b/tools/scripts/test_fw_query_on_routes.py @@ -12,7 +12,6 @@ from openpilot.tools.lib.logreader import LogReader, ReadMode from openpilot.tools.lib.route import SegmentRange -NO_API = "NO_API" in os.environ SUPPORTED_BRANDS = VERSIONS.keys() SUPPORTED_CARS = [brand for brand in SUPPORTED_BRANDS for brand in interface_names[brand]] UNKNOWN_BRAND = "unknown" @@ -178,4 +177,3 @@ if __name__ == "__main__": print(f"Correct fuzzy matches: {good_fuzzy}") print(f"Wrong fuzzy matches: {wrong_fuzzy}") print() - From 65c007a9a2f59e9f290d566a09acc59d65090435 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 10 Aug 2026 08:16:57 -0700 Subject: [PATCH 150/170] tools: setup.sh isn't for AGNOS (#38588) * tools: setup.sh isn't for AGNOS * Update setup.sh --- tools/setup.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/setup.sh b/tools/setup.sh index d4b68d15cb..ced451ab11 100755 --- a/tools/setup.sh +++ b/tools/setup.sh @@ -33,6 +33,13 @@ cat << 'EOF' EOF } +function check_platform() { + if [[ -f /AGNOS ]]; then + echo -e "[${RED}✗${NC}] This installer is for PCs only. The environment is pre-configured in AGNOS." + return 1 + fi +} + function check_stdin() { if [ -t 0 ]; then INTERACTIVE=1 @@ -135,6 +142,7 @@ function install_with_op() { } show_motd +check_platform check_stdin ask_dir check_dir From 434fbad5fcfc68697f447e8bfcf812bf6a1d56cc Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 10 Aug 2026 08:36:12 -0700 Subject: [PATCH 151/170] [bot] Update Python packages (#38599) * Update Python packages * Fix package update CI --------- Co-authored-by: Vehicle Researcher Co-authored-by: Adeeb Shihadeh --- .github/workflows/diff_report.yaml | 3 +- opendbc_repo | 2 +- .../test/process_replay/migration.py | 2 +- tinygrad_repo | 2 +- uv.lock | 76 +++++++++---------- 5 files changed, 43 insertions(+), 42 deletions(-) diff --git a/.github/workflows/diff_report.yaml b/.github/workflows/diff_report.yaml index 0f706ea2ed..202bc0ec79 100644 --- a/.github/workflows/diff_report.yaml +++ b/.github/workflows/diff_report.yaml @@ -40,6 +40,7 @@ jobs: echo "run-id=$run_id" >> "$GITHUB_OUTPUT" - name: Download diff if: steps.wait.outcome == 'success' + continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -48,7 +49,7 @@ jobs: name: diff_report_${{ github.event.number }} path: . - name: Comment on PR - if: steps.wait.outcome == 'success' + if: steps.wait.outcome == 'success' && hashFiles('diff_report.txt') != '' uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b with: file-path: diff_report.txt diff --git a/opendbc_repo b/opendbc_repo index b0685818f3..44f2987cb6 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit b0685818f31df1e9d225ebc762087a8ebd563edb +Subproject commit 44f2987cb6ed28f7dcd99d5930abf6c2917d8f60 diff --git a/openpilot/selfdrive/test/process_replay/migration.py b/openpilot/selfdrive/test/process_replay/migration.py index ff98389e2c..65d54221ee 100644 --- a/openpilot/selfdrive/test/process_replay/migration.py +++ b/openpilot/selfdrive/test/process_replay/migration.py @@ -313,7 +313,7 @@ def migrate_pandaStates(msgs): "CHEVROLET_BOLT_EUV": GMSafetyFlags.EV | GMSafetyFlags.HW_CAM, } # TODO: get new Ford route - safety_param_migration |= dict.fromkeys((set(FORD) - FORD.with_flags(FordFlags.CANFD)), FordSafetyFlags.LONG_CONTROL) + safety_param_migration |= dict.fromkeys({p for p in FORD if not (p.config.flags & FordFlags.CANFD)}, FordSafetyFlags.LONG_CONTROL) # Migrate safety param base on carParams CP = next((m.carParams for _, m in msgs if m.which() == 'carParams'), None) diff --git a/tinygrad_repo b/tinygrad_repo index 1858f1fd9a..8611fe22a7 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 1858f1fd9aa94ca4e302b60a88f075d0d1dd88bc +Subproject commit 8611fe22a7fcc7d1928bbde19ded66277cb12f3e diff --git a/uv.lock b/uv.lock index efd0aad651..5717d0a38d 100644 --- a/uv.lock +++ b/uv.lock @@ -513,21 +513,21 @@ provides-extras = ["dev"] [[package]] name = "numpy" -version = "2.5.1" +version = "2.5.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/80/db0b4559e57ec36362bedbb05530a87fafbcb6067708c946967a41d449e7/numpy-2.5.2.tar.gz", hash = "sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860", size = 20773161, upload-time = "2026-08-09T13:48:27.962Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, - { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, - { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, - { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, - { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, - { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, - { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, - { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, - { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, + { url = "https://files.pythonhosted.org/packages/69/72/dccb0aaf40972777283303919f613964227266d0c13adebb79ac124f1c3e/numpy-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14e373cfc6387177e8409dac3c7159be8eb05cd77096cd7c950268b86f62831c", size = 16891693, upload-time = "2026-08-09T13:44:51.702Z" }, + { url = "https://files.pythonhosted.org/packages/60/2e/b5aee50a1f74ac815cf8331812cb8251e29024025de462e0c047641c614c/numpy-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbd96c833ecc8cc069ce518078fc8c60cb9cbfb0fea5b7a803ad65035596d03", size = 11903109, upload-time = "2026-08-09T13:44:55.501Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f4/29e78102a80601cf034d4e9767022cffeca2c3b4c926e1754572ca95593d/numpy-2.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:6e8172ddfcf5cf74b811d372b570b83c60bd2de87a6fbfbebdadb4a9bd9c6cbb", size = 5350202, upload-time = "2026-08-09T13:44:58.401Z" }, + { url = "https://files.pythonhosted.org/packages/11/4b/dcd3b7eadaf4035d2c7a4289d232523a6964f602598ef7674e4bd7291f93/numpy-2.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f188481f1669e26f62b701e8205d19e460fa4a9b52a1414ba382330e4a3414", size = 6687736, upload-time = "2026-08-09T13:45:00.813Z" }, + { url = "https://files.pythonhosted.org/packages/e5/21/4947e0e9d6c9fc2e2ff15b8949049ee44f63adb9cacc729ab8793f97e712/numpy-2.5.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee9c4eeb8454b3660a8b53493563c3e121c2fc94fbd72b848ef814ed7b676a9", size = 15612696, upload-time = "2026-08-09T13:45:04.151Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5f/62d28cf019460c7f1394105b4d49d9911a9c444cb77ab0bd95a204c5a6de/numpy-2.5.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdec01fa790a186d430433fdd4d4ffb70eed6f0eeb4bf05c8dbe2dce0a9bcb8", size = 16722264, upload-time = "2026-08-09T13:45:07.714Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/3f0be4c1b9fdf5dd5e708a6806978564d7c46a055c000496309ff2a2f8af/numpy-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7999d4ddb0c4025018373fd787510d46e04c769467af22869707b3c1cfd459ab", size = 16974396, upload-time = "2026-08-09T13:45:11.316Z" }, + { url = "https://files.pythonhosted.org/packages/22/72/6262cbdeeb45da9d971e40715f579d791603ba8ec0b5e2db1ac55454421d/numpy-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1f017dc0875c9209d219f97feceb7d54c2661bb243deb4114478e1295808af7", size = 18476044, upload-time = "2026-08-09T13:45:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/36/33/29208b8b075bde62d26a81d14b358c42b0f69b6cabd98d4ff97f37f22b05/numpy-2.5.2-cp312-cp312-win32.whl", hash = "sha256:d6a48072864e3324e194a8fbb3c657bcc5b5c869dbc64c9537b1d5c862572c0a", size = 6072817, upload-time = "2026-08-09T13:45:17.867Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/87fea2769fe1c47c1b5b01d8310772c9d1a85d485de7cf386ef7a3332b02/numpy-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:28ac63476ec7651484215ee7fa15a1f78b57c14621f01e392afe17b9a1390ce4", size = 12464674, upload-time = "2026-08-09T13:45:20.734Z" }, + { url = "https://files.pythonhosted.org/packages/14/52/032b97e00461ab0809bbe4c588b035620e5a14b8cdee47ecddefc7b17d33/numpy-2.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:27650bb0e7140fa3d37b9923b4803645e0b125d190f326eecfd3f4dad8e8ade1", size = 10397131, upload-time = "2026-08-09T13:45:23.73Z" }, ] [[package]] @@ -879,27 +879,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.1" +version = "0.16.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/25/7113f6d5498888c5fb7db34081cba7d5971c4cb1bfb26819966eee68f003/ruff-0.16.1.tar.gz", hash = "sha256:fedad7c801dabd3fb9741d76aca39246e6ddd9ca446a015875207bf19f1e6bc7", size = 4877500, upload-time = "2026-07-30T19:37:01.379Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/e1/4508a569211b35599016e84ba65c1a992b7a4004b4b6c4bea02a851cba1b/ruff-0.16.2.tar.gz", hash = "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c", size = 4885811, upload-time = "2026-08-07T13:31:01.432Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/bd/694da69368e0973de65df2ddc73ab18d43c469d5963d9b150911de6bc513/ruff-0.16.1-py3-none-linux_armv6l.whl", hash = "sha256:58edb313b88f0c5460a26adf5f39a37a3be789494a15e3e411e35fa78b89f9a0", size = 10839126, upload-time = "2026-07-30T19:36:13.697Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f0/b626e5d5bd0dd9576263658ef12885e2288afd1029a48e26ffed65ec1ac1/ruff-0.16.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fde5a99e2f97479af66edd6622c6d5a2a7592c77cf4153d9e4428f5eeb55b60c", size = 11070253, upload-time = "2026-07-30T19:36:17.14Z" }, - { url = "https://files.pythonhosted.org/packages/83/63/f40acfb6b35b88623e71684942b552c3edd96035f5d98f313815f7b277de/ruff-0.16.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e0d4c20532fca4f7fa609369161d968dd28f65d83dabbd61d8e9c7edbf7001f6", size = 10561425, upload-time = "2026-07-30T19:36:20.04Z" }, - { url = "https://files.pythonhosted.org/packages/aa/dd/14ec0e9c2b4d315547dd38765004b4863e354e1b52cb308272215d9f6f6d/ruff-0.16.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30affbcedf59ad5703d9c91f82266e02b47739f797e1a7b6e158e5526a6dae38", size = 10948879, upload-time = "2026-07-30T19:36:22.476Z" }, - { url = "https://files.pythonhosted.org/packages/33/e9/9d870cbae575030fdef595f04b4b97573c525b5497cce4f4498cf2f85446/ruff-0.16.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24e9c631573cbca9d20f1283f8f479b2afa4a8503504822bd71a293889f16743", size = 10643691, upload-time = "2026-07-30T19:36:24.914Z" }, - { url = "https://files.pythonhosted.org/packages/c4/09/12743d544e2173f53ecd27217c65f90d2bc0f8424a66a60339e56bbc0457/ruff-0.16.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b41bdd48fb420987a9b5212e4957c26ad4abce401fa9ea9d4d85843727945f4f", size = 11435354, upload-time = "2026-07-30T19:36:28.447Z" }, - { url = "https://files.pythonhosted.org/packages/7f/89/a1652b2daee52083c9554a6333b678a8b01d0400f976827bb87857f9449a/ruff-0.16.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0d1e1393b7648079e13669de1c1f4fde06d4583e84d8fd5c1551e0a77a2aa75", size = 12259033, upload-time = "2026-07-30T19:36:31.326Z" }, - { url = "https://files.pythonhosted.org/packages/16/96/ecdcb8c54ee7b123b487f807eb014e6e019155a0b81dfb669acd52f28ce3/ruff-0.16.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07bf434b1c95f4e093be4532068ef4fcf00924eb2ade8796075980902d6fd54a", size = 11667981, upload-time = "2026-07-30T19:36:34.394Z" }, - { url = "https://files.pythonhosted.org/packages/cd/90/c52e12e0d862e9572f2a33aa227409143520abe53111e9a6babbac7b4af8/ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39897739f112253ee4fdd2e8aa9a4f9ded99fb2be367d5f31dfa4ded6025584c", size = 11468183, upload-time = "2026-07-30T19:36:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6b/4ffb7ad1d83eb16cf8cbb3c8815d3f11c88460fd162d4b372a2059be1c2a/ruff-0.16.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:82ae3c0c0d74daf17b968a10b7b3bb3ef297ab7de0c1f749646b25e690ccb150", size = 11470071, upload-time = "2026-07-30T19:36:39.91Z" }, - { url = "https://files.pythonhosted.org/packages/9c/72/32ae7db4c0b5e32ab611787caa19d1546800676d79f7483b7100a3561bf4/ruff-0.16.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4d5f2ed10f8242d83fc08d521301089364e3375375705356f20c0e31606ef3ef", size = 10919503, upload-time = "2026-07-30T19:36:42.65Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ca/3d901ba6ad6fc38da39c3448fc6c59ac945679293a17c3ceb6d6c1cba13e/ruff-0.16.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a4665b309891f83f3e3c25447935f1213e9abbd4b5640af7a1f2def9f8d413c1", size = 10649861, upload-time = "2026-07-30T19:36:45.18Z" }, - { url = "https://files.pythonhosted.org/packages/92/79/894ef1ced26552d5f8c9cf6d85b0687840e1128c55aeab7b9c2d54a0d880/ruff-0.16.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26e9ca5c9bc3971f20d3cf18a957f52ffd6a5f6564ff15c4912a144dcac22494", size = 11148137, upload-time = "2026-07-30T19:36:47.936Z" }, - { url = "https://files.pythonhosted.org/packages/2d/69/3609a09fa1cb46cc28b762363e440a354204e5dff01bd0c8d7437874d6b9/ruff-0.16.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:67e1e1e3fa4f0c82f0e36d4cd61e661f6e7a6196cb1aa92fe0828fa7b8f257cd", size = 11559211, upload-time = "2026-07-30T19:36:50.448Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8a/fb22af2fd78a736e241fabf67e30ce1799a64244026377a49e133af90762/ruff-0.16.1-py3-none-win32.whl", hash = "sha256:d31765e131295b8445caf301e3e8a85b34d1b9b211b4109b7ba457888b051806", size = 10838258, upload-time = "2026-07-30T19:36:53.298Z" }, - { url = "https://files.pythonhosted.org/packages/d4/35/e57fd9fb5d423961df087a00b12d42c0a830288dc2f3b45ecca299158b4f/ruff-0.16.1-py3-none-win_amd64.whl", hash = "sha256:09b05e8b90c2cb06ad63464350e7a45e8e44a2dfe52072ebfba6666ca8d3f596", size = 11961111, upload-time = "2026-07-30T19:36:56.107Z" }, - { url = "https://files.pythonhosted.org/packages/cb/46/240ea004bf6dc4feb40e9832f2205a476a47dd5b8a3f8211a5fc5f95e20e/ruff-0.16.1-py3-none-win_arm64.whl", hash = "sha256:dbaadaac38c70239f056d306b7476f246b0bf000fa6b3876402acbf5b227eaf8", size = 11309414, upload-time = "2026-07-30T19:36:58.79Z" }, + { url = "https://files.pythonhosted.org/packages/14/57/db19951540f98859c956b50bdb4d31089b4d91e9f15e2968e7d5193806d5/ruff-0.16.2-py3-none-linux_armv6l.whl", hash = "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318", size = 10847925, upload-time = "2026-08-07T13:30:14.468Z" }, + { url = "https://files.pythonhosted.org/packages/13/5a/995fe85a8470d3e391ac0f7fa8054bb454eaf33ee138196d6172ed1079c0/ruff-0.16.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344", size = 11072662, upload-time = "2026-08-07T13:30:18.143Z" }, + { url = "https://files.pythonhosted.org/packages/32/53/370d767c61c71a971a4ace36703a7ecd8c393956349a7325d7fab2b56827/ruff-0.16.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700", size = 10566771, upload-time = "2026-08-07T13:30:20.899Z" }, + { url = "https://files.pythonhosted.org/packages/85/d6/9d96948caf5a632be62d62202d5ec914d6856f204fd79eb036e5915e79ea/ruff-0.16.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe", size = 10975825, upload-time = "2026-08-07T13:30:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/92/ea87129b3414acb0b5770563779c51804d37ac67675c7ba35447ddb14773/ruff-0.16.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f", size = 10649437, upload-time = "2026-08-07T13:30:26.097Z" }, + { url = "https://files.pythonhosted.org/packages/ac/43/f8f291dcd4af5bb7872b74fdfa41a7cd7c856ca1d4069670971cf1b9f5cb/ruff-0.16.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f", size = 11446761, upload-time = "2026-08-07T13:30:28.752Z" }, + { url = "https://files.pythonhosted.org/packages/71/4a/ef991fb2fcf516ab71f0808adcdd8da5e18c8cde447f4ceaf5f47a5132a5/ruff-0.16.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12", size = 12336364, upload-time = "2026-08-07T13:30:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/f3/24/f615e74f307e6ca0e56a482872477b856c70d530aa356abfb6dfe5ca8a80/ruff-0.16.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140", size = 11630720, upload-time = "2026-08-07T13:30:34.426Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d3/8ef50149e8412a77f7ab409efdef0e2b23803707a3863da4fc64cb23d459/ruff-0.16.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f", size = 11466130, upload-time = "2026-08-07T13:30:36.958Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a7/a19334985c4dea8c381981fa252cd854c7ee52dc4b1686dc16f4a911c702/ruff-0.16.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0", size = 11523634, upload-time = "2026-08-07T13:30:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6c/96d192b0e742412ceda08c0a50f9669b253dde9fd6a60ea1a10c9fa79a63/ruff-0.16.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690", size = 10949807, upload-time = "2026-08-07T13:30:42.745Z" }, + { url = "https://files.pythonhosted.org/packages/fa/51/e26599ceca11e79ee255c7df515995561edf87e9ca1893284e44d98f5a86/ruff-0.16.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1", size = 10646891, upload-time = "2026-08-07T13:30:45.522Z" }, + { url = "https://files.pythonhosted.org/packages/68/01/800c4b1f97bc8d7c6029e06b1f20473a3cf1e13c4933d8f3342add83fc55/ruff-0.16.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8", size = 11162063, upload-time = "2026-08-07T13:30:48.131Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d0/1477ea50fc5a0d4b0b71d1d63d50770bdd794d90b43e37a7618e63ec9894/ruff-0.16.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd", size = 11556038, upload-time = "2026-08-07T13:30:50.686Z" }, + { url = "https://files.pythonhosted.org/packages/b8/76/a7776f32048d991e16d4fa8ff91790b877342d3596cc3ed04acdbf1aaedc/ruff-0.16.2-py3-none-win32.whl", hash = "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b", size = 10872850, upload-time = "2026-08-07T13:30:53.471Z" }, + { url = "https://files.pythonhosted.org/packages/00/0d/929c800d920e61397d82a01b60bffc68da3052c17d31de59efaad2e4ed75/ruff-0.16.2-py3-none-win_amd64.whl", hash = "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa", size = 12023338, upload-time = "2026-08-07T13:30:56.193Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6c/93e26c22c5f78ff87363e07da49c84955affbeb1098bd1936bf3b3f293bf/ruff-0.16.2-py3-none-win_arm64.whl", hash = "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f", size = 11374065, upload-time = "2026-08-07T13:30:58.775Z" }, ] [[package]] @@ -913,15 +913,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.66.1" +version = "2.67.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7f/6f/d59cad0889d15fde85254cf58e701484de3f3f0406003b3197746910b19b/sentry_sdk-2.66.1.tar.gz", hash = "sha256:f882fb08710c5f8bfc603aafa3e901b384009a19cc3f76a572b863392ee81cdc", size = 940543, upload-time = "2026-07-22T12:26:54.553Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/8a/b2eec40df8a67bf073e244d29001d04ee365d163bc4f15efdfce35f53090/sentry_sdk-2.67.1.tar.gz", hash = "sha256:f263d8c9aa4137750640de8fb0ed5404df6bb564e20e4b59cb16a6eeba18d4ed", size = 990599, upload-time = "2026-08-10T13:05:55.892Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/d3/726bd88f0eece09ddf431bea4c9191c18e7a8d070b854eb0014d447712ee/sentry_sdk-2.66.1-py3-none-any.whl", hash = "sha256:86002793161d9a95ef04bdd8d442e9bfece5d989b755f05d6360215094a7aff6", size = 505555, upload-time = "2026-07-22T12:26:52.71Z" }, + { url = "https://files.pythonhosted.org/packages/34/e2/70692eba662037cddf93391cbbf98297159f3038612e9b9a8129e16feb7a/sentry_sdk-2.67.1-py3-none-any.whl", hash = "sha256:a66bfbce1cd8a93c51c369d642ad85b46253ea7a6f7938141315b83e2823cda5", size = 515591, upload-time = "2026-08-10T13:05:54.213Z" }, ] [[package]] @@ -944,11 +944,11 @@ wheels = [ [[package]] name = "setuptools" -version = "83.0.0" +version = "84.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449, upload-time = "2026-08-08T18:27:58.365Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" }, ] [[package]] From 6d5d1f691708ae95896516e45089676e487c876e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 10 Aug 2026 09:30:22 -0700 Subject: [PATCH 152/170] Rename "live" cereal services (#38601) --- openpilot/cereal/log.capnp | 24 +++--- .../messaging/tests/test_pub_sub_master.py | 4 +- openpilot/cereal/services.py | 12 +-- openpilot/common/mock/__init__.py | 4 +- openpilot/common/mock/generators.py | 18 ++--- openpilot/common/params_keys.h | 1 + openpilot/selfdrive/car/card.py | 8 +- openpilot/selfdrive/controls/controlsd.py | 24 +++--- .../controls/lib/latcontrol_torque.py | 2 +- .../lib/longitudinal_mpc_lib/long_mpc.py | 2 +- .../controls/lib/longitudinal_planner.py | 2 +- openpilot/selfdrive/controls/plannerd.py | 2 +- openpilot/selfdrive/controls/radard.py | 4 +- .../controls/tests/test_latcontrol.py | 2 +- .../tests/test_latcontrol_torque_buffer.py | 2 +- .../selfdrive/controls/tests/test_leads.py | 2 +- .../tests/test_torqued_lat_accel_offset.py | 16 ++-- openpilot/selfdrive/locationd/calibrationd.py | 56 +++++++------- openpilot/selfdrive/locationd/helpers.py | 18 ++--- openpilot/selfdrive/locationd/lagd.py | 58 +++++++-------- openpilot/selfdrive/locationd/locationd.py | 38 +++++----- openpilot/selfdrive/locationd/paramsd.py | 74 +++++++++---------- .../locationd/test/test_calibrationd.py | 20 ++--- .../selfdrive/locationd/test/test_lagd.py | 50 ++++++------- .../test/test_locationd_scenarios.py | 2 +- .../selfdrive/locationd/test/test_paramsd.py | 22 +++--- .../selfdrive/locationd/test/test_torqued.py | 6 +- openpilot/selfdrive/locationd/torqued.py | 64 ++++++++-------- .../selfdrive/modeld/dmonitoringmodeld.py | 6 +- openpilot/selfdrive/modeld/fill_model_msg.py | 4 +- openpilot/selfdrive/modeld/modeld.py | 16 ++-- .../selfdrive/monitoring/dmonitoringd.py | 2 +- openpilot/selfdrive/monitoring/policy.py | 2 +- openpilot/selfdrive/selfdrived/events.py | 18 ++--- openpilot/selfdrive/selfdrived/helpers.py | 8 +- openpilot/selfdrive/selfdrived/selfdrived.py | 33 +++++---- openpilot/selfdrive/test/helpers.py | 6 +- .../test/longitudinal_maneuvers/plant.py | 4 +- .../selfdrive/test/process_replay/README.md | 2 +- .../test/process_replay/migration.py | 28 +++---- .../test/process_replay/model_replay.py | 6 +- .../test/process_replay/process_replay.py | 69 ++++++++--------- openpilot/selfdrive/test/test_onroad.py | 4 +- openpilot/selfdrive/test/test_power_draw.py | 2 +- .../selfdrive/ui/layouts/settings/device.py | 8 +- .../ui/mici/onroad/augmented_road_view.py | 10 +-- .../ui/mici/onroad/model_renderer.py | 6 +- .../selfdrive/ui/mici/onroad/torque_bar.py | 4 +- .../ui/onroad/augmented_road_view.py | 10 +-- .../selfdrive/ui/onroad/model_renderer.py | 6 +- .../selfdrive/ui/tests/diff/replay_script.py | 22 +++--- openpilot/selfdrive/ui/ui_state.py | 4 +- .../jotpluggler/layouts/locationd_debug.json | 2 +- .../jotpluggler/layouts/max-torque-debug.json | 2 +- .../layouts/torque-controller.json | 2 +- openpilot/tools/joystick/joystickd.py | 6 +- .../longitudinal_maneuvers/generate_report.py | 8 +- .../plotjuggler/layouts/locationd_debug.xml | 5 +- .../plotjuggler/layouts/max-torque-debug.xml | 7 +- .../plotjuggler/layouts/torque-controller.xml | 37 +++++----- openpilot/tools/replay/consoleui.cc | 4 +- openpilot/tools/replay/ui.py | 20 ++--- tools/scripts/car/max_lat_accel.py | 4 +- tools/scripts/cycle_alerts.py | 6 +- 64 files changed, 460 insertions(+), 460 deletions(-) diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp index fa185aa483..7b93e4aa73 100644 --- a/openpilot/cereal/log.capnp +++ b/openpilot/cereal/log.capnp @@ -763,7 +763,7 @@ struct RadarState @0x9a185389d6fdd05f { } } -struct LiveCalibrationData { +struct ExtrinsicsCalibration @0x96df70754d8390bc { calStatus @11 :Status; calCycle @2 :Int32; calPerc @3 :Int8; @@ -1384,7 +1384,7 @@ struct LiveLocationKalman { } -struct LivePose { +struct DeviceMotion @0xc24ca2b57206b44d { # More info on reference frames: # https://github.com/commaai/openpilot/tree/master/openpilot/common/transformations orientationNED @0 :XYZMeasurement; @@ -2266,7 +2266,7 @@ struct Boot { } } -struct LiveParametersData { +struct VehicleParameters @0xd9058dcb967c2753 { valid @0 :Bool; gyroBias @1 :Float32; angleOffsetDeg @2 :Float32; @@ -2300,8 +2300,8 @@ struct LiveParametersData { } } -struct LiveTorqueParametersData { - liveValid @0 :Bool; +struct LateralTorqueParameters @0xe61690eb0b091692 { + valid @0 :Bool; latAccelFactorRaw @1 :Float32; latAccelOffsetRaw @2 :Float32; frictionCoefficientRaw @3 :Float32; @@ -2317,7 +2317,7 @@ struct LiveTorqueParametersData { calPerc @13 :Int8; } -struct LiveDelayData { +struct LateralDelay @0x98dfdb22c44df8d4 { lateralDelay @0 :Float32; validBlocks @1 :Int32; status @2 :Status; @@ -2535,9 +2535,9 @@ struct Event { pandaStates @81 :List(PandaState); peripheralState @80 :PeripheralState; radarState @13 :RadarState; - liveTracks @131 :Car.RadarData; + radarTracks @131 :Car.RadarData; sendcan @17 :List(CanData); - liveCalibration @19 :LiveCalibrationData; + extrinsicsCalibration @19 :ExtrinsicsCalibration; carState @22 :Car.CarState; carControl @23 :Car.CarControl; carOutput @127 :Car.CarOutput; @@ -2548,15 +2548,15 @@ struct Event { qcomGnss @31 :QcomGnss; gpsLocationExternal @48 :GpsLocationData; gpsLocation @21 :GpsLocationData; - liveParameters @61 :LiveParametersData; - liveTorqueParameters @94 :LiveTorqueParametersData; - liveDelay @146 : LiveDelayData; + vehicleParameters @61 :VehicleParameters; + lateralTorqueParameters @94 :LateralTorqueParameters; + lateralDelay @146 : LateralDelay; cameraOdometry @63 :CameraOdometry; thumbnail @66: Thumbnail; onroadEvents @134: List(OnroadEvent); carParams @69: Car.CarParams; driverMonitoringState @151 :DriverMonitoringState; - livePose @129 :LivePose; + deviceMotion @129 :DeviceMotion; modelV2 @75 :ModelDataV2; drivingModelData @128 :DrivingModelData; driverStateV2 @92 :DriverStateV2; diff --git a/openpilot/cereal/messaging/tests/test_pub_sub_master.py b/openpilot/cereal/messaging/tests/test_pub_sub_master.py index c9304a3e65..24ee68d4fd 100644 --- a/openpilot/cereal/messaging/tests/test_pub_sub_master.py +++ b/openpilot/cereal/messaging/tests/test_pub_sub_master.py @@ -70,7 +70,7 @@ class TestSubMaster(OpenpilotTestCase): def test_avg_frequency_checks(self): for poll in (True, False): - sm = messaging.SubMaster(["modelV2", "carParams", "carState", "cameraOdometry", "liveCalibration"], + sm = messaging.SubMaster(["modelV2", "carParams", "carState", "cameraOdometry", "extrinsicsCalibration"], poll=("modelV2" if poll else None), frequency=(20. if not poll else None)) @@ -78,7 +78,7 @@ class TestSubMaster(OpenpilotTestCase): "carState": (20, 20), "modelV2": (20, 20 if poll else 10), "cameraOdometry": (20, 10), - "liveCalibration": (4, 4), + "extrinsicsCalibration": (4, 4), "carParams": (None, None), "userBookmark": (None, None), } diff --git a/openpilot/cereal/services.py b/openpilot/cereal/services.py index 61fb8fcbd2..ebe8be1d60 100755 --- a/openpilot/cereal/services.py +++ b/openpilot/cereal/services.py @@ -34,13 +34,13 @@ _services: dict[str, tuple] = { "peripheralState": (True, 2., 1), "radarState": (True, 20., 5), "narrowRoadEncodeIdx": (False, 20., 1), - "liveTracks": (True, 20.), + "radarTracks": (True, 20.), "sendcan": (True, 100., 139, QueueSize.MEDIUM), "logMessage": (True, 0., None, QueueSize.BIG), "errorLogMessage": (True, 0., 1, QueueSize.BIG), - "liveCalibration": (True, 4., 4), - "liveTorqueParameters": (True, 4., 1), - "liveDelay": (True, 4., 1), + "extrinsicsCalibration": (True, 4., 4), + "lateralTorqueParameters": (True, 4., 1), + "lateralDelay": (True, 4., 1), "operatingSystemLog": (True, 0.), "carState": (True, 100., 10), "carControl": (True, 100., 10), @@ -55,8 +55,8 @@ _services: dict[str, tuple] = { "qcomGnss": (True, 2.), "clocks": (True, 0.1, 1), "ubloxRaw": (True, 20.), - "livePose": (True, 20., 4), - "liveParameters": (True, 20., 5), + "deviceMotion": (True, 20., 4), + "vehicleParameters": (True, 20., 5), "cameraOdometry": (True, 20., 10), "thumbnail": (True, 1 / 60., 1), "onroadEvents": (True, 1., 1), diff --git a/openpilot/common/mock/__init__.py b/openpilot/common/mock/__init__.py index ff4dd32b92..9fa1e4b4d7 100644 --- a/openpilot/common/mock/__init__.py +++ b/openpilot/common/mock/__init__.py @@ -8,12 +8,12 @@ import functools import threading from openpilot.cereal.messaging import PubMaster from openpilot.cereal.services import SERVICE_LIST -from openpilot.common.mock.generators import generate_livePose +from openpilot.common.mock.generators import generate_deviceMotion from openpilot.common.realtime import Ratekeeper MOCK_GENERATOR = { - "livePose": generate_livePose + "deviceMotion": generate_deviceMotion } diff --git a/openpilot/common/mock/generators.py b/openpilot/common/mock/generators.py index 28a3b98e58..176471f1ab 100644 --- a/openpilot/common/mock/generators.py +++ b/openpilot/common/mock/generators.py @@ -1,14 +1,14 @@ from openpilot.cereal import messaging -def generate_livePose(): - msg = messaging.new_message('livePose') +def generate_deviceMotion(): + msg = messaging.new_message('deviceMotion') meas = {'x': 0.0, 'y': 0.0, 'z': 0.0, 'xStd': 0.0, 'yStd': 0.0, 'zStd': 0.0, 'valid': True} - msg.livePose.orientationNED = meas - msg.livePose.velocityDevice = meas - msg.livePose.angularVelocityDevice = meas - msg.livePose.accelerationDevice = meas - msg.livePose.inputsOK = True - msg.livePose.posenetOK = True - msg.livePose.sensorsOK = True + msg.deviceMotion.orientationNED = meas + msg.deviceMotion.velocityDevice = meas + msg.deviceMotion.angularVelocityDevice = meas + msg.deviceMotion.accelerationDevice = meas + msg.deviceMotion.inputsOK = True + msg.deviceMotion.posenetOK = True + msg.deviceMotion.sensorsOK = True return msg diff --git a/openpilot/common/params_keys.h b/openpilot/common/params_keys.h index 5bdfdf8636..0019f6c9ac 100644 --- a/openpilot/common/params_keys.h +++ b/openpilot/common/params_keys.h @@ -77,6 +77,7 @@ inline static std::unordered_map keys = { {"LastUpdateRouteCount", {PERSISTENT, INT, "0"}}, {"LastUpdateTime", {PERSISTENT, TIME}}, {"LastUpdateUptimeOnroad", {PERSISTENT, FLOAT, "0.0"}}, + // TODO: rename the Live* learner cache keys to match their Cereal services, with migration for persisted values. {"LiveDelay", {PERSISTENT, BYTES}}, {"LiveParametersV2", {PERSISTENT, BYTES}}, {"LivestreamEncoderBitrate", {CLEAR_ON_MANAGER_START | DONT_LOG, INT}}, diff --git a/openpilot/selfdrive/car/card.py b/openpilot/selfdrive/car/card.py index 96c169a1da..86f8fbbde5 100755 --- a/openpilot/selfdrive/car/card.py +++ b/openpilot/selfdrive/car/card.py @@ -66,7 +66,7 @@ class Car: def __init__(self, CI=None, RI=None) -> None: self.can_sock = messaging.sub_sock('can', timeout=20) self.sm = messaging.SubMaster(['pandaStates', 'carControl', 'onroadEvents']) - self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput', 'liveTracks']) + self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams', 'carOutput', 'radarTracks']) self.can_rcv_cum_timeout_counter = 0 @@ -216,10 +216,10 @@ class Car: self.pm.send('carState', cs_send) if RD is not None: - tracks_msg = messaging.new_message('liveTracks') + tracks_msg = messaging.new_message('radarTracks') tracks_msg.valid = not any(RD.errors.to_dict().values()) - tracks_msg.liveTracks = RD - self.pm.send('liveTracks', tracks_msg) + tracks_msg.radarTracks = RD + self.pm.send('radarTracks', tracks_msg) def controls_update(self, CS: car.CarState, CC: car.CarControl): """control update loop, driven by carControl""" diff --git a/openpilot/selfdrive/controls/controlsd.py b/openpilot/selfdrive/controls/controlsd.py index 9afcdf1d27..aef92fb7ab 100755 --- a/openpilot/selfdrive/controls/controlsd.py +++ b/openpilot/selfdrive/controls/controlsd.py @@ -38,8 +38,8 @@ class Controls: self.CI = interfaces[self.CP.carFingerprint](self.CP) - self.sm = messaging.SubMaster(['liveDelay', 'liveParameters', 'liveTorqueParameters', 'modelV2', 'selfdriveState', - 'liveCalibration', 'livePose', 'longitudinalPlan', 'lateralManeuverPlan', 'carState', 'carOutput', + self.sm = messaging.SubMaster(['lateralDelay', 'vehicleParameters', 'lateralTorqueParameters', 'modelV2', 'selfdriveState', + 'extrinsicsCalibration', 'deviceMotion', 'longitudinalPlan', 'lateralManeuverPlan', 'carState', 'carOutput', 'driverMonitoringState', 'onroadEvents', 'driverAssistance'], poll='selfdriveState') self.pm = messaging.PubMaster(['carControl', 'controlsState']) @@ -64,17 +64,17 @@ class Controls: def update(self): self.sm.update(15) - if self.sm.updated["liveCalibration"]: - self.pose_calibrator.feed_live_calib(self.sm['liveCalibration']) - if self.sm.updated["livePose"]: - device_pose = Pose.from_live_pose(self.sm['livePose']) - self.calibrated_pose = self.pose_calibrator.build_calibrated_pose(device_pose) + if self.sm.updated["extrinsicsCalibration"]: + self.pose_calibrator.feed_extrinsics_calibration(self.sm['extrinsicsCalibration']) + if self.sm.updated["deviceMotion"]: + device_motion = Pose.from_device_motion(self.sm['deviceMotion']) + self.calibrated_pose = self.pose_calibrator.build_calibrated_pose(device_motion) def state_control(self): CS = self.sm['carState'] # Update VehicleModel - lp = self.sm['liveParameters'] + lp = self.sm['vehicleParameters'] x = max(lp.stiffnessFactor, 0.1) sr = max(lp.steerRatio, 0.1) self.VM.update_params(x, sr) @@ -84,9 +84,9 @@ class Controls: # Update Torque Params if self.CP.lateralTuning.which() == 'torque': - torque_params = self.sm['liveTorqueParameters'] - if self.sm.all_checks(['liveTorqueParameters']) and torque_params.useParams: - self.LaC.update_live_torque_params(torque_params.latAccelFactorFiltered, torque_params.latAccelOffsetFiltered, + torque_params = self.sm['lateralTorqueParameters'] + if self.sm.all_checks(['lateralTorqueParameters']) and torque_params.useParams: + self.LaC.update_torque_parameters(torque_params.latAccelFactorFiltered, torque_params.latAccelOffsetFiltered, torque_params.frictionCoefficientFiltered) long_plan = self.sm['longitudinalPlan'] @@ -125,7 +125,7 @@ class Controls: else: new_desired_curvature = model_v2.action.desiredCurvature if CC.latActive else self.curvature self.desired_curvature, curvature_limited = clip_curvature(CS.vEgo, self.desired_curvature, new_desired_curvature, lp.roll) - lat_delay = self.sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS + lat_delay = self.sm["lateralDelay"].lateralDelay + LAT_SMOOTH_SECONDS actuators.curvature = self.desired_curvature steer, lateral_output, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp, diff --git a/openpilot/selfdrive/controls/lib/latcontrol_torque.py b/openpilot/selfdrive/controls/lib/latcontrol_torque.py index 846f1daf00..267c5b5967 100644 --- a/openpilot/selfdrive/controls/lib/latcontrol_torque.py +++ b/openpilot/selfdrive/controls/lib/latcontrol_torque.py @@ -46,7 +46,7 @@ class LatControlTorque(LatControl): self.lookahead_frames = int(JERK_LOOKAHEAD_SECONDS / self.dt) self.jerk_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt) - def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction): + def update_torque_parameters(self, latAccelFactor, latAccelOffset, friction): self.torque_params.latAccelFactor = latAccelFactor self.torque_params.latAccelOffset = latAccelOffset self.torque_params.friction = friction diff --git a/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 725491eed2..0a0722fbc6 100755 --- a/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -102,7 +102,7 @@ def gen_long_model(): a_ego_dot = SX.sym('a_ego_dot') model.xdot = vertcat(x_ego_dot, v_ego_dot, a_ego_dot) - # live parameters + # runtime parameters a_min = SX.sym('a_min') a_max = SX.sym('a_max') x_obstacle = SX.sym('x_obstacle') diff --git a/openpilot/selfdrive/controls/lib/longitudinal_planner.py b/openpilot/selfdrive/controls/lib/longitudinal_planner.py index 8855d85af4..c2b8b94abb 100755 --- a/openpilot/selfdrive/controls/lib/longitudinal_planner.py +++ b/openpilot/selfdrive/controls/lib/longitudinal_planner.py @@ -96,7 +96,7 @@ class LongitudinalPlanner: throttle_prob = throttle_probs[1] if len(throttle_probs) > 1 else 1.0 self.allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD or v_ego <= MIN_ALLOW_THROTTLE_SPEED - steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg + steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['vehicleParameters'].angleOffsetDeg if reset_state: self.v_desired_filter.x = v_ego diff --git a/openpilot/selfdrive/controls/plannerd.py b/openpilot/selfdrive/controls/plannerd.py index 60a6525853..110c124166 100755 --- a/openpilot/selfdrive/controls/plannerd.py +++ b/openpilot/selfdrive/controls/plannerd.py @@ -19,7 +19,7 @@ def main(): ldw = LaneDepartureWarning() longitudinal_planner = LongitudinalPlanner(CP) pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance']) - sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState'], + sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'vehicleParameters', 'radarState', 'modelV2', 'selfdriveState'], poll='modelV2') while True: diff --git a/openpilot/selfdrive/controls/radard.py b/openpilot/selfdrive/controls/radard.py index 6fecfffb1d..e6b5a0aea8 100755 --- a/openpilot/selfdrive/controls/radard.py +++ b/openpilot/selfdrive/controls/radard.py @@ -260,7 +260,7 @@ def main() -> None: cloudlog.info("radard got CarParams") # *** setup messaging - sm = messaging.SubMaster(['modelV2', 'carState', 'liveTracks'], poll='modelV2') + sm = messaging.SubMaster(['modelV2', 'carState', 'radarTracks'], poll='modelV2') pm = messaging.PubMaster(['radarState']) RD = RadarD(CP.radarDelay) @@ -268,7 +268,7 @@ def main() -> None: while 1: sm.update() - RD.update(sm, sm['liveTracks']) + RD.update(sm, sm['radarTracks']) RD.publish(pm) diff --git a/openpilot/selfdrive/controls/tests/test_latcontrol.py b/openpilot/selfdrive/controls/tests/test_latcontrol.py index 7f2ab8d6b7..2b7a0cf695 100644 --- a/openpilot/selfdrive/controls/tests/test_latcontrol.py +++ b/openpilot/selfdrive/controls/tests/test_latcontrol.py @@ -31,7 +31,7 @@ class TestLatControl(OpenpilotTestCase): CS.vEgo = 30 CS.steeringPressed = False - params = log.LiveParametersData.new_message() + params = log.VehicleParameters.new_message() # Saturate for curvature limited and controller limited for _ in range(1000): diff --git a/openpilot/selfdrive/controls/tests/test_latcontrol_torque_buffer.py b/openpilot/selfdrive/controls/tests/test_latcontrol_torque_buffer.py index 65befdec0f..6be242821c 100644 --- a/openpilot/selfdrive/controls/tests/test_latcontrol_torque_buffer.py +++ b/openpilot/selfdrive/controls/tests/test_latcontrol_torque_buffer.py @@ -27,7 +27,7 @@ class TestLatControlTorqueBuffer(OpenpilotTestCase): CS = car.CarState.new_message() CS.vEgo = 30 CS.steeringPressed = False - params = log.LiveParametersData.new_message() + params = log.VehicleParameters.new_message() for _ in range(buffer_steps): controller.update(True, CS, VM, params, False, 0.001, False, 0.2) diff --git a/openpilot/selfdrive/controls/tests/test_leads.py b/openpilot/selfdrive/controls/tests/test_leads.py index a3e226cb4b..45257c545b 100644 --- a/openpilot/selfdrive/controls/tests/test_leads.py +++ b/openpilot/selfdrive/controls/tests/test_leads.py @@ -26,7 +26,7 @@ class TestLeads(OpenpilotTestCase): msgs = [m for _ in range(3) for m in single_iter_pkg()] out = replay_process_with_name("card", msgs, fingerprint=TOYOTA.TOYOTA_COROLLA_TSS2) - states = [m for m in out if m.which() == "liveTracks"] + states = [m for m in out if m.which() == "radarTracks"] failures = [not state.valid for state in states] assert len(states) == 0 or all(failures) diff --git a/openpilot/selfdrive/controls/tests/test_torqued_lat_accel_offset.py b/openpilot/selfdrive/controls/tests/test_torqued_lat_accel_offset.py index 4fe855544e..011713dfb3 100644 --- a/openpilot/selfdrive/controls/tests/test_torqued_lat_accel_offset.py +++ b/openpilot/selfdrive/controls/tests/test_torqued_lat_accel_offset.py @@ -41,7 +41,7 @@ def simulate_straight_road_msgs(est): carControl = messaging.new_message('carControl').carControl carOutput = messaging.new_message('carOutput').carOutput carState = messaging.new_message('carState').carState - livePose = messaging.new_message('livePose').livePose + deviceMotion = messaging.new_message('deviceMotion').deviceMotion carControl.latActive = True carState.vEgo = V_EGO carState.steeringPressed = False @@ -50,11 +50,11 @@ def simulate_straight_road_msgs(est): lat_accels = TORQUE_TUNE.latAccelFactor * steer_torques for t, steer_torque, lat_accel in zip(ts, steer_torques, lat_accels, strict=True): carOutput.actuatorsOutput.torque = float(-steer_torque) - livePose.orientationNED = {'x': float(np.deg2rad(ROLL_BIAS_DEG)), 'valid': True} - livePose.angularVelocityDevice = {'z': float(lat_accel / V_EGO), 'valid': True} - livePose.inputsOK, livePose.sensorsOK, livePose.posenetOK = True, True, True - livePose.timestamp = int(t * 1e9) - for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('livePose', livePose)): + deviceMotion.orientationNED = {'x': float(np.deg2rad(ROLL_BIAS_DEG)), 'valid': True} + deviceMotion.angularVelocityDevice = {'z': float(lat_accel / V_EGO), 'valid': True} + deviceMotion.inputsOK, deviceMotion.sensorsOK, deviceMotion.posenetOK = True, True, True + deviceMotion.timestamp = int(t * 1e9) + for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('deviceMotion', deviceMotion)): est.handle_log(t, which, msg) class TestTorquedLatAccelOffset(OpenpilotTestCase): @@ -63,11 +63,11 @@ class TestTorquedLatAccelOffset(OpenpilotTestCase): est = get_warmed_up_estimator(steer_torques, lat_accels) msg = est.get_msg() # TODO add lataccelfactor and friction check when we have more accurate estimates - assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1 + assert abs(msg.lateralTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1 def test_straight_road_roll_bias(self): steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD) est = get_warmed_up_estimator(steer_torques, lat_accels) simulate_straight_road_msgs(est) msg = est.get_msg() - assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw) + assert (msg.lateralTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.lateralTorqueParameters.latAccelOffsetRaw) diff --git a/openpilot/selfdrive/locationd/calibrationd.py b/openpilot/selfdrive/locationd/calibrationd.py index b9616bf632..ec9a225634 100755 --- a/openpilot/selfdrive/locationd/calibrationd.py +++ b/openpilot/selfdrive/locationd/calibrationd.py @@ -74,15 +74,15 @@ class Calibrator: wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT height = HEIGHT_INIT valid_blocks = 0 - self.cal_status = log.LiveCalibrationData.Status.uncalibrated + self.cal_status = log.ExtrinsicsCalibration.Status.uncalibrated if param_put and calibration_params: try: with log.Event.from_bytes(calibration_params) as msg: - rpy_init = np.array(msg.liveCalibration.rpyCalib) - valid_blocks = msg.liveCalibration.validBlocks - wide_from_device_euler = np.array(msg.liveCalibration.wideFromDeviceEuler) - height = np.array(msg.liveCalibration.height) + rpy_init = np.array(msg.extrinsicsCalibration.rpyCalib) + valid_blocks = msg.extrinsicsCalibration.validBlocks + wide_from_device_euler = np.array(msg.extrinsicsCalibration.wideFromDeviceEuler) + height = np.array(msg.extrinsicsCalibration.height) except Exception: cloudlog.exception("Error reading cached CalibrationParams") @@ -149,22 +149,22 @@ class Calibrator: self.calib_spread = np.zeros(3) if self.valid_blocks < INPUTS_NEEDED: - if self.cal_status == log.LiveCalibrationData.Status.recalibrating: - self.cal_status = log.LiveCalibrationData.Status.recalibrating + if self.cal_status == log.ExtrinsicsCalibration.Status.recalibrating: + self.cal_status = log.ExtrinsicsCalibration.Status.recalibrating else: - self.cal_status = log.LiveCalibrationData.Status.uncalibrated + self.cal_status = log.ExtrinsicsCalibration.Status.uncalibrated elif is_calibration_valid(self.rpy): - self.cal_status = log.LiveCalibrationData.Status.calibrated + self.cal_status = log.ExtrinsicsCalibration.Status.calibrated else: - self.cal_status = log.LiveCalibrationData.Status.invalid + self.cal_status = log.ExtrinsicsCalibration.Status.invalid # If spread is too high, assume mounting was changed and reset to last block. # Make the transition smooth. Abrupt transitions are not good for feedback loop through supercombo model. # TODO: add height spread check with smooth transition too spread_too_high = self.calib_spread[1] > MAX_ALLOWED_PITCH_SPREAD or self.calib_spread[2] > MAX_ALLOWED_YAW_SPREAD - if spread_too_high and self.cal_status == log.LiveCalibrationData.Status.calibrated: + if spread_too_high and self.cal_status == log.ExtrinsicsCalibration.Status.calibrated: self.reset(self.rpys[self.block_idx - 1], valid_blocks=1, smooth_from=self.rpy) - self.cal_status = log.LiveCalibrationData.Status.recalibrating + self.cal_status = log.ExtrinsicsCalibration.Status.recalibrating write_this_cycle = (self.idx == 0) and (self.block_idx % (INPUTS_WANTED//5) == 5) if self.param_put and write_this_cycle: @@ -234,35 +234,35 @@ class Calibrator: def get_msg(self, valid: bool) -> capnp.lib.capnp._DynamicStructBuilder: smooth_rpy = self.get_smooth_rpy() - msg = messaging.new_message('liveCalibration') + msg = messaging.new_message('extrinsicsCalibration') msg.valid = valid - liveCalibration = msg.liveCalibration - liveCalibration.validBlocks = self.valid_blocks - liveCalibration.calStatus = self.cal_status - liveCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100) - liveCalibration.rpyCalib = smooth_rpy.tolist() - liveCalibration.rpyCalibSpread = self.calib_spread.tolist() - liveCalibration.wideFromDeviceEuler = self.wide_from_device_euler.tolist() - liveCalibration.height = self.height.tolist() + extrinsicsCalibration = msg.extrinsicsCalibration + extrinsicsCalibration.validBlocks = self.valid_blocks + extrinsicsCalibration.calStatus = self.cal_status + extrinsicsCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100) + extrinsicsCalibration.rpyCalib = smooth_rpy.tolist() + extrinsicsCalibration.rpyCalibSpread = self.calib_spread.tolist() + extrinsicsCalibration.wideFromDeviceEuler = self.wide_from_device_euler.tolist() + extrinsicsCalibration.height = self.height.tolist() if self.not_car: - liveCalibration.validBlocks = INPUTS_NEEDED - liveCalibration.calStatus = log.LiveCalibrationData.Status.calibrated - liveCalibration.calPerc = 100. - liveCalibration.rpyCalib = [0, 0, 0] - liveCalibration.rpyCalibSpread = self.calib_spread.tolist() + extrinsicsCalibration.validBlocks = INPUTS_NEEDED + extrinsicsCalibration.calStatus = log.ExtrinsicsCalibration.Status.calibrated + extrinsicsCalibration.calPerc = 100. + extrinsicsCalibration.rpyCalib = [0, 0, 0] + extrinsicsCalibration.rpyCalibSpread = self.calib_spread.tolist() return msg def send_data(self, pm: messaging.PubMaster, valid: bool) -> None: - pm.send('liveCalibration', self.get_msg(valid)) + pm.send('extrinsicsCalibration', self.get_msg(valid)) def main() -> NoReturn: config_realtime_process([0, 1, 2, 3], 5) - pm = messaging.PubMaster(['liveCalibration']) + pm = messaging.PubMaster(['extrinsicsCalibration']) sm = messaging.SubMaster(['cameraOdometry', 'carState'], poll='cameraOdometry') params_reader = Params() diff --git a/openpilot/selfdrive/locationd/helpers.py b/openpilot/selfdrive/locationd/helpers.py index 9ea9b6bf4f..48e1ee25c2 100644 --- a/openpilot/selfdrive/locationd/helpers.py +++ b/openpilot/selfdrive/locationd/helpers.py @@ -130,7 +130,7 @@ class Measurement: self.xyz_std: np.ndarray = xyz_std @classmethod - def from_measurement_xyz(cls, measurement: log.LivePose.XYZMeasurement) -> 'Measurement': + def from_measurement_xyz(cls, measurement: log.DeviceMotion.XYZMeasurement) -> 'Measurement': return cls( xyz=np.array([measurement.x, measurement.y, measurement.z]), xyz_std=np.array([measurement.xStd, measurement.yStd, measurement.zStd]) @@ -145,12 +145,12 @@ class Pose: self.angular_velocity = angular_velocity @classmethod - def from_live_pose(cls, live_pose: log.LivePose) -> 'Pose': + def from_device_motion(cls, device_motion: log.DeviceMotion) -> 'Pose': return Pose( - orientation=Measurement.from_measurement_xyz(live_pose.orientationNED), - velocity=Measurement.from_measurement_xyz(live_pose.velocityDevice), - acceleration=Measurement.from_measurement_xyz(live_pose.accelerationDevice), - angular_velocity=Measurement.from_measurement_xyz(live_pose.angularVelocityDevice) + orientation=Measurement.from_measurement_xyz(device_motion.orientationNED), + velocity=Measurement.from_measurement_xyz(device_motion.velocityDevice), + acceleration=Measurement.from_measurement_xyz(device_motion.accelerationDevice), + angular_velocity=Measurement.from_measurement_xyz(device_motion.angularVelocityDevice) ) @@ -178,8 +178,8 @@ class PoseCalibrator: return Pose(ned_from_calib_euler, velocity_calib, acceleration_calib, angular_velocity_calib) - def feed_live_calib(self, live_calib: log.LiveCalibrationData): - calib_rpy = np.array(live_calib.rpyCalib) + def feed_extrinsics_calibration(self, extrinsics_calibration: log.ExtrinsicsCalibration): + calib_rpy = np.array(extrinsics_calibration.rpyCalib) device_from_calib = rot_from_euler(calib_rpy) self.calib_from_device = device_from_calib.T - self.calib_valid = live_calib.calStatus == log.LiveCalibrationData.Status.calibrated + self.calib_valid = extrinsics_calibration.calStatus == log.ExtrinsicsCalibration.Status.calibrated diff --git a/openpilot/selfdrive/locationd/lagd.py b/openpilot/selfdrive/locationd/lagd.py index d3c0c5195b..a198ce915f 100755 --- a/openpilot/selfdrive/locationd/lagd.py +++ b/openpilot/selfdrive/locationd/lagd.py @@ -171,7 +171,7 @@ class BlockAverage: class LateralLagEstimator: - inputs = {"carControl", "carState", "controlsState", "liveCalibration", "livePose"} + inputs = {"carControl", "carState", "controlsState", "extrinsicsCalibration", "deviceMotion"} def __init__(self, CP: car.CarParams, dt: float, block_count: int = BLOCK_NUM, min_valid_block_count: int = BLOCK_NUM_NEEDED, block_size: int = BLOCK_SIZE, @@ -219,39 +219,39 @@ class LateralLagEstimator: self.block_avg = BlockAverage(self.block_count, self.block_size, valid_blocks, initial_lag) def get_msg(self, valid: bool, debug: bool = False) -> capnp._DynamicStructBuilder: - msg = messaging.new_message('liveDelay') + msg = messaging.new_message('lateralDelay') msg.valid = valid - liveDelay = msg.liveDelay + lateralDelay = msg.lateralDelay valid_mean_lag, valid_std, current_mean_lag, current_std = self.block_avg.get() if self.block_avg.valid_blocks >= self.min_valid_block_count and not np.isnan(valid_mean_lag) and not np.isnan(valid_std): if valid_std > MAX_LAG_STD: - liveDelay.status = log.LiveDelayData.Status.invalid + lateralDelay.status = log.LateralDelay.Status.invalid else: - liveDelay.status = log.LiveDelayData.Status.estimated + lateralDelay.status = log.LateralDelay.Status.estimated else: - liveDelay.status = log.LiveDelayData.Status.unestimated + lateralDelay.status = log.LateralDelay.Status.unestimated - if liveDelay.status == log.LiveDelayData.Status.estimated: - liveDelay.lateralDelay = min(MAX_LAG, max(MIN_LAG, valid_mean_lag)) + if lateralDelay.status == log.LateralDelay.Status.estimated: + lateralDelay.lateralDelay = min(MAX_LAG, max(MIN_LAG, valid_mean_lag)) else: - liveDelay.lateralDelay = self.initial_lag + lateralDelay.lateralDelay = self.initial_lag if not np.isnan(current_mean_lag) and not np.isnan(current_std): - liveDelay.lateralDelayEstimate = current_mean_lag - liveDelay.lateralDelayEstimateStd = current_std + lateralDelay.lateralDelayEstimate = current_mean_lag + lateralDelay.lateralDelayEstimateStd = current_std else: - liveDelay.lateralDelayEstimate = self.initial_lag - liveDelay.lateralDelayEstimateStd = 0.0 + lateralDelay.lateralDelayEstimate = self.initial_lag + lateralDelay.lateralDelayEstimateStd = 0.0 - liveDelay.validBlocks = self.block_avg.valid_blocks - liveDelay.calPerc = min(100 * (self.block_avg.valid_blocks * self.block_size + self.block_avg.idx) // + lateralDelay.validBlocks = self.block_avg.valid_blocks + lateralDelay.calPerc = min(100 * (self.block_avg.valid_blocks * self.block_size + self.block_avg.idx) // (self.min_valid_block_count * self.block_size), 100) if debug: - liveDelay.points = self.block_avg.values.flatten().tolist() - liveDelay.version = VERSION + lateralDelay.points = self.block_avg.values.flatten().tolist() + lateralDelay.version = VERSION return msg @@ -264,11 +264,11 @@ class LateralLagEstimator: elif which == "controlsState": self.steering_saturated = getattr(msg.lateralControlState, msg.lateralControlState.which()).saturated self.desired_curvature = msg.desiredCurvature - elif which == "liveCalibration": - self.calibrator.feed_live_calib(msg) - elif which == "livePose": - device_pose = Pose.from_live_pose(msg) - calibrated_pose = self.calibrator.build_calibrated_pose(device_pose) + elif which == "extrinsicsCalibration": + self.calibrator.feed_extrinsics_calibration(msg) + elif which == "deviceMotion": + device_motion = Pose.from_device_motion(msg) + calibrated_pose = self.calibrator.build_calibrated_pose(device_motion) self.yaw_rate = calibrated_pose.angular_velocity.yaw self.yaw_rate_std = calibrated_pose.angular_velocity.yaw_std self.pose_valid = msg.angularVelocityDevice.valid and msg.posenetOK and msg.inputsOK @@ -368,13 +368,13 @@ def retrieve_initial_lag(params: Params, CP: car.CarParams): if last_lag_data is not None: try: with log.Event.from_bytes(last_lag_data) as last_lag_msg, car.CarParams.from_bytes(last_carparams_data) as last_CP: - ld = last_lag_msg.liveDelay + ld = last_lag_msg.lateralDelay if last_CP.carFingerprint != CP.carFingerprint: raise Exception("Car model mismatch") lag, valid_blocks, status, version = ld.lateralDelayEstimate, ld.validBlocks, ld.status, ld.version assert valid_blocks <= BLOCK_NUM, "Invalid number of valid blocks" - assert status != log.LiveDelayData.Status.invalid, "Lag estimate is invalid" + assert status != log.LateralDelay.Status.invalid, "Lag estimate is invalid" assert version == VERSION, f"Lag estimate is from a different version (got {version}, expected {VERSION})" return lag, valid_blocks except Exception as e: @@ -389,13 +389,13 @@ def main(): DEBUG = bool(int(os.getenv("DEBUG", "0"))) - pm = messaging.PubMaster(['liveDelay']) - sm = messaging.SubMaster(['livePose', 'liveCalibration', 'carState', 'controlsState', 'carControl'], poll='livePose') + pm = messaging.PubMaster(['lateralDelay']) + sm = messaging.SubMaster(['deviceMotion', 'extrinsicsCalibration', 'carState', 'controlsState', 'carControl'], poll='deviceMotion') params = Params() CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) - lag_learner = LateralLagEstimator(CP, 1. / SERVICE_LIST['livePose'].frequency) + lag_learner = LateralLagEstimator(CP, 1. / SERVICE_LIST['deviceMotion'].frequency) if (initial_lag_params := retrieve_initial_lag(params, CP)) is not None: lag, valid_blocks = initial_lag_params lag_learner.reset(lag, valid_blocks) @@ -409,12 +409,12 @@ def main(): lag_learner.handle_log(t, which, sm[which]) lag_learner.update_points() - # 4Hz driven by livePose + # 4Hz driven by deviceMotion if sm.frame % 5 == 0: lag_learner.update_estimate() lag_msg = lag_learner.get_msg(sm.all_checks(), DEBUG) lag_msg_dat = lag_msg.to_bytes() - pm.send('liveDelay', lag_msg_dat) + pm.send('lateralDelay', lag_msg_dat) if sm.frame % 1200 == 0: # cache every 60 seconds params.put("LiveDelay", lag_msg_dat) diff --git a/openpilot/selfdrive/locationd/locationd.py b/openpilot/selfdrive/locationd/locationd.py index eb3c42fce2..9fbec991d5 100755 --- a/openpilot/selfdrive/locationd/locationd.py +++ b/openpilot/selfdrive/locationd/locationd.py @@ -148,7 +148,7 @@ class LocationEstimator: elif which == "carState": self.car_speed = abs(msg.vEgo) - elif which == "liveCalibration": + elif which == "extrinsicsCalibration": # Note that we use this message during calibration if len(msg.rpyCalib) > 0: calib = np.array(msg.rpyCalib) @@ -217,19 +217,19 @@ class LocationEstimator: angular_velocity_device, angular_velocity_device_std = state[States.ANGULAR_VELOCITY], std[States.ANGULAR_VELOCITY] acceleration_device, acceleration_device_std = state[States.ACCELERATION], std[States.ACCELERATION] - msg = messaging.new_message("livePose") + msg = messaging.new_message("deviceMotion") msg.valid = filter_valid - livePose = msg.livePose - init_xyz_measurement(livePose.orientationNED, orientation_ned, orientation_ned_std, filter_valid) - init_xyz_measurement(livePose.velocityDevice, velocity_device, velocity_device_std, filter_valid) - init_xyz_measurement(livePose.angularVelocityDevice, angular_velocity_device, angular_velocity_device_std, filter_valid) - init_xyz_measurement(livePose.accelerationDevice, acceleration_device, acceleration_device_std, filter_valid) + deviceMotion = msg.deviceMotion + init_xyz_measurement(deviceMotion.orientationNED, orientation_ned, orientation_ned_std, filter_valid) + init_xyz_measurement(deviceMotion.velocityDevice, velocity_device, velocity_device_std, filter_valid) + init_xyz_measurement(deviceMotion.angularVelocityDevice, angular_velocity_device, angular_velocity_device_std, filter_valid) + init_xyz_measurement(deviceMotion.accelerationDevice, acceleration_device, acceleration_device_std, filter_valid) if self.debug: - livePose.debugFilterState.value = state.tolist() - livePose.debugFilterState.std = std.tolist() - livePose.debugFilterState.valid = filter_valid - livePose.debugFilterState.observations = [ + deviceMotion.debugFilterState.value = state.tolist() + deviceMotion.debugFilterState.std = std.tolist() + deviceMotion.debugFilterState.valid = filter_valid + deviceMotion.debugFilterState.observations = [ {'kind': k, 'value': self.observations[k].tolist(), 'error': self.observation_errors[k].tolist()} for k in self.observations.keys() ] @@ -238,10 +238,10 @@ class LocationEstimator: new_mean = np.mean(self.posenet_stds[POSENET_STD_HIST_HALF:]) std_spike = (new_mean / old_mean) > 4.0 and new_mean > 7.0 - livePose.inputsOK = inputs_valid - livePose.posenetOK = not std_spike or self.car_speed <= 5.0 - livePose.sensorsOK = sensors_valid - livePose.timestamp = int(np.nan_to_num(self.kf.t) * 1e9) + deviceMotion.inputsOK = inputs_valid + deviceMotion.posenetOK = not std_spike or self.car_speed <= 5.0 + deviceMotion.sensorsOK = sensors_valid + deviceMotion.timestamp = int(np.nan_to_num(self.kf.t) * 1e9) return msg @@ -267,8 +267,8 @@ def main(): DEBUG = bool(int(os.getenv("DEBUG", "0"))) SIMULATION = bool(int(os.getenv("SIMULATION", "0"))) - pm = messaging.PubMaster(['livePose']) - sm = messaging.SubMaster(['carState', 'liveCalibration', 'cameraOdometry'], poll='cameraOdometry') + pm = messaging.PubMaster(['deviceMotion']) + sm = messaging.SubMaster(['carState', 'extrinsicsCalibration', 'cameraOdometry'], poll='cameraOdometry') # separate sensor sockets for efficiency sensor_sockets = [messaging.sub_sock(which, timeout=20) for which in ['accelerometer', 'gyroscope']] sensor_alive, sensor_valid, sensor_recv_time = defaultdict(bool), defaultdict(bool), defaultdict(float) @@ -288,7 +288,7 @@ def main(): initial_pose_data = params.get("LocationFilterInitialState") if initial_pose_data is not None: with log.Event.from_bytes(initial_pose_data) as lp_msg: - filter_state = lp_msg.livePose.debugFilterState + filter_state = lp_msg.deviceMotion.debugFilterState x_initial = np.array(filter_state.value, dtype=np.float64) if len(filter_state.value) != 0 else PoseKalman.initial_x P_initial = np.diag(np.array(filter_state.std, dtype=np.float64)) if len(filter_state.std) != 0 else PoseKalman.initial_P estimator.reset(None, x_initial, P_initial) @@ -333,7 +333,7 @@ def main(): sensors_valid = sensor_all_checks(acc_msgs, gyro_msgs, sensor_valid, sensor_recv_time, sensor_alive, SIMULATION) msg = estimator.get_msg(sensors_valid, inputs_valid, filter_initialized) - pm.send("livePose", msg) + pm.send("deviceMotion", msg) if __name__ == "__main__": diff --git a/openpilot/selfdrive/locationd/paramsd.py b/openpilot/selfdrive/locationd/paramsd.py index 9b5ff7143e..760176bc5c 100755 --- a/openpilot/selfdrive/locationd/paramsd.py +++ b/openpilot/selfdrive/locationd/paramsd.py @@ -65,10 +65,10 @@ class VehicleParamsLearner: self.avg_angle_offset = self.angle_offset def handle_log(self, t: float, which: str, msg: capnp._DynamicStructReader): - if which == 'livePose': + if which == 'deviceMotion': t = msg.timestamp * 1e-9 - device_pose = Pose.from_live_pose(msg) - calibrated_pose = self.calibrator.build_calibrated_pose(device_pose) + device_motion = Pose.from_device_motion(msg) + calibrated_pose = self.calibrator.build_calibrated_pose(device_motion) yaw_rate, yaw_rate_std = calibrated_pose.angular_velocity.z, calibrated_pose.angular_velocity.z_std yaw_rate_valid = msg.angularVelocityDevice.valid @@ -79,7 +79,7 @@ class VehicleParamsLearner: yaw_rate, yaw_rate_std = 0.0, np.radians(10.0) self.observed_yaw_rate = yaw_rate - localizer_roll, localizer_roll_std = device_pose.orientation.x, device_pose.orientation.x_std + localizer_roll, localizer_roll_std = device_motion.orientation.x, device_motion.orientation.x_std localizer_roll_std = np.radians(1) if np.isnan(localizer_roll_std) else localizer_roll_std roll_valid = (localizer_roll_std < ROLL_STD_MAX) and (ROLL_MIN < localizer_roll < ROLL_MAX) and msg.sensorsOK if roll_valid: @@ -113,8 +113,8 @@ class VehicleParamsLearner: self.kf.predict_and_observe(t, ObservationKind.STIFFNESS, np.array([[stiffness]])) self.kf.predict_and_observe(t, ObservationKind.STEER_RATIO, np.array([[steer_ratio]])) - elif which == 'liveCalibration': - self.calibrator.feed_live_calib(msg) + elif which == 'extrinsicsCalibration': + self.calibrator.feed_extrinsics_calibration(msg) elif which == 'carState': steering_angle = msg.steeringAngleDeg @@ -136,7 +136,7 @@ class VehicleParamsLearner: x = self.kf.x P = np.sqrt(self.kf.P.diagonal()) if not np.all(np.isfinite(x)): - cloudlog.error("NaN in liveParameters estimate. Resetting to default values") + cloudlog.error("NaN in vehicleParameters estimate. Resetting to default values") self.reset(self.kf.t) x = self.kf.x @@ -156,38 +156,38 @@ class VehicleParamsLearner: self.total_offset_valid = check_valid_with_hysteresis(self.total_offset_valid, self.angle_offset, OFFSET_MAX, OFFSET_LOWERED_MAX) self.roll_valid = check_valid_with_hysteresis(self.roll_valid, self.roll, ROLL_MAX, ROLL_LOWERED_MAX) - msg = messaging.new_message('liveParameters') + msg = messaging.new_message('vehicleParameters') msg.valid = valid - liveParameters = msg.liveParameters - liveParameters.posenetValid = True - liveParameters.sensorValid = sensors_valid - liveParameters.steerRatio = float(x[States.STEER_RATIO].item()) - liveParameters.stiffnessFactor = float(x[States.STIFFNESS].item()) - liveParameters.roll = float(self.roll) - liveParameters.angleOffsetAverageDeg = float(self.avg_angle_offset) - liveParameters.angleOffsetDeg = float(self.angle_offset) - liveParameters.steerRatioValid = self.min_sr <= liveParameters.steerRatio <= self.max_sr - liveParameters.stiffnessFactorValid = 0.2 <= liveParameters.stiffnessFactor <= 5.0 - liveParameters.angleOffsetAverageValid = bool(self.avg_offset_valid) - liveParameters.angleOffsetValid = bool(self.total_offset_valid) - liveParameters.valid = all(( - liveParameters.angleOffsetAverageValid, - liveParameters.angleOffsetValid , + vehicleParameters = msg.vehicleParameters + vehicleParameters.posenetValid = True + vehicleParameters.sensorValid = sensors_valid + vehicleParameters.steerRatio = float(x[States.STEER_RATIO].item()) + vehicleParameters.stiffnessFactor = float(x[States.STIFFNESS].item()) + vehicleParameters.roll = float(self.roll) + vehicleParameters.angleOffsetAverageDeg = float(self.avg_angle_offset) + vehicleParameters.angleOffsetDeg = float(self.angle_offset) + vehicleParameters.steerRatioValid = self.min_sr <= vehicleParameters.steerRatio <= self.max_sr + vehicleParameters.stiffnessFactorValid = 0.2 <= vehicleParameters.stiffnessFactor <= 5.0 + vehicleParameters.angleOffsetAverageValid = bool(self.avg_offset_valid) + vehicleParameters.angleOffsetValid = bool(self.total_offset_valid) + vehicleParameters.valid = all(( + vehicleParameters.angleOffsetAverageValid, + vehicleParameters.angleOffsetValid , self.roll_valid, roll_std < ROLL_STD_MAX, - liveParameters.stiffnessFactorValid, - liveParameters.steerRatioValid, + vehicleParameters.stiffnessFactorValid, + vehicleParameters.steerRatioValid, )) - liveParameters.steerRatioStd = float(P[States.STEER_RATIO].item()) - liveParameters.stiffnessFactorStd = float(P[States.STIFFNESS].item()) - liveParameters.angleOffsetAverageStd = float(P[States.ANGLE_OFFSET].item()) - liveParameters.angleOffsetFastStd = float(P[States.ANGLE_OFFSET_FAST].item()) + vehicleParameters.steerRatioStd = float(P[States.STEER_RATIO].item()) + vehicleParameters.stiffnessFactorStd = float(P[States.STIFFNESS].item()) + vehicleParameters.angleOffsetAverageStd = float(P[States.ANGLE_OFFSET].item()) + vehicleParameters.angleOffsetFastStd = float(P[States.ANGLE_OFFSET_FAST].item()) if debug: - liveParameters.debugFilterState = log.LiveParametersData.FilterState.new_message() - liveParameters.debugFilterState.value = x.tolist() - liveParameters.debugFilterState.std = P.tolist() + vehicleParameters.debugFilterState = log.VehicleParameters.FilterState.new_message() + vehicleParameters.debugFilterState.value = x.tolist() + vehicleParameters.debugFilterState.std = P.tolist() return msg @@ -210,7 +210,7 @@ def retrieve_initial_vehicle_params(params: Params, CP: car.CarParams, replay: b if last_parameters_data is not None and last_carparams_data is not None: try: with log.Event.from_bytes(last_parameters_data) as last_lp_msg, car.CarParams.from_bytes(last_carparams_data) as last_CP: - lp = last_lp_msg.liveParameters + lp = last_lp_msg.vehicleParameters # Check if car model matches if last_CP.carFingerprint != CP.carFingerprint: raise Exception("Car model mismatch") @@ -248,8 +248,8 @@ def main(): DEBUG = bool(int(os.getenv("DEBUG", "0"))) REPLAY = bool(int(os.getenv("REPLAY", "0"))) - pm = messaging.PubMaster(['liveParameters']) - sm = messaging.SubMaster(['livePose', 'liveCalibration', 'carState'], poll='livePose') + pm = messaging.PubMaster(['vehicleParameters']) + sm = messaging.SubMaster(['deviceMotion', 'extrinsicsCalibration', 'carState'], poll='deviceMotion') params = Params() CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) @@ -265,14 +265,14 @@ def main(): t = sm.logMonoTime[which] * 1e-9 learner.handle_log(t, which, sm[which]) - if sm.updated['livePose']: + if sm.updated['deviceMotion']: msg = learner.get_msg(sm.all_checks(), debug=DEBUG) msg_dat = msg.to_bytes() if sm.frame % 1200 == 0: # once a minute params.put("LiveParametersV2", msg_dat) - pm.send('liveParameters', msg_dat) + pm.send('vehicleParameters', msg_dat) if __name__ == "__main__": diff --git a/openpilot/selfdrive/locationd/test/test_calibrationd.py b/openpilot/selfdrive/locationd/test/test_calibrationd.py index b3722e344c..7afd43b35b 100644 --- a/openpilot/selfdrive/locationd/test/test_calibrationd.py +++ b/openpilot/selfdrive/locationd/test/test_calibrationd.py @@ -33,16 +33,16 @@ def process_messages(c, cam_odo_calib, cycles, class TestCalibrationd(OpenpilotTestCase): def test_read_saved_params(self): - msg = messaging.new_message('liveCalibration') - msg.liveCalibration.validBlocks = random.randint(1, 10) - msg.liveCalibration.rpyCalib = [random.random() for _ in range(3)] - msg.liveCalibration.height = [random.random() for _ in range(1)] + msg = messaging.new_message('extrinsicsCalibration') + msg.extrinsicsCalibration.validBlocks = random.randint(1, 10) + msg.extrinsicsCalibration.rpyCalib = [random.random() for _ in range(3)] + msg.extrinsicsCalibration.height = [random.random() for _ in range(1)] Params().put("CalibrationParams", msg.to_bytes(), block=True) c = Calibrator(param_put=True) - np.testing.assert_allclose(msg.liveCalibration.rpyCalib, c.rpy) - np.testing.assert_allclose(msg.liveCalibration.height, c.height) - assert msg.liveCalibration.validBlocks == c.valid_blocks + np.testing.assert_allclose(msg.extrinsicsCalibration.rpyCalib, c.rpy) + np.testing.assert_allclose(msg.extrinsicsCalibration.height, c.height) + assert msg.extrinsicsCalibration.validBlocks == c.valid_blocks def test_calibration_basics(self): @@ -92,7 +92,7 @@ class TestCalibrationd(OpenpilotTestCase): np.testing.assert_allclose(c.rpy, [0.0, 0.0, 0.0], atol=1e-3) process_messages(c, [0.0, MAX_ALLOWED_PITCH_SPREAD*0.9, MAX_ALLOWED_YAW_SPREAD*0.9], BLOCK_SIZE + 10) assert c.valid_blocks == INPUTS_NEEDED + 1 - assert c.cal_status == log.LiveCalibrationData.Status.calibrated + assert c.cal_status == log.ExtrinsicsCalibration.Status.calibrated c = Calibrator(param_put=False) process_messages(c, [0.0, 0.0, 0.0], BLOCK_SIZE * INPUTS_NEEDED) @@ -100,7 +100,7 @@ class TestCalibrationd(OpenpilotTestCase): np.testing.assert_allclose(c.rpy, [0.0, 0.0, 0.0]) process_messages(c, [0.0, MAX_ALLOWED_PITCH_SPREAD*1.1, 0.0], BLOCK_SIZE + 10) assert c.valid_blocks == 1 - assert c.cal_status == log.LiveCalibrationData.Status.recalibrating + assert c.cal_status == log.ExtrinsicsCalibration.Status.recalibrating np.testing.assert_allclose(c.rpy, [0.0, MAX_ALLOWED_PITCH_SPREAD*1.1, 0.0], atol=1e-2) c = Calibrator(param_put=False) @@ -109,5 +109,5 @@ class TestCalibrationd(OpenpilotTestCase): np.testing.assert_allclose(c.rpy, [0.0, 0.0, 0.0]) process_messages(c, [0.0, 0.0, MAX_ALLOWED_YAW_SPREAD*1.1], BLOCK_SIZE + 10) assert c.valid_blocks == 1 - assert c.cal_status == log.LiveCalibrationData.Status.recalibrating + assert c.cal_status == log.ExtrinsicsCalibration.Status.recalibrating np.testing.assert_allclose(c.rpy, [0.0, 0.0, MAX_ALLOWED_YAW_SPREAD*1.1], atol=1e-2) diff --git a/openpilot/selfdrive/locationd/test/test_lagd.py b/openpilot/selfdrive/locationd/test/test_lagd.py index d6006c6cf9..0acc3e646f 100644 --- a/openpilot/selfdrive/locationd/test/test_lagd.py +++ b/openpilot/selfdrive/locationd/test/test_lagd.py @@ -43,9 +43,9 @@ def process_messages(estimator, lag_frames, n_frames, vego=25.0, rejection_thres (t, "carControl", car.CarControl(latActive=not rejected)), (t, "carState", car.CarState(vEgo=vego, steeringPressed=False)), (t, "controlsState", log.ControlsState(desiredCurvature=desired_cuvature)), - (t, "livePose", log.LivePose(angularVelocityDevice=log.LivePose.XYZMeasurement(z=actual_yr, valid=True), + (t, "deviceMotion", log.DeviceMotion(angularVelocityDevice=log.DeviceMotion.XYZMeasurement(z=actual_yr, valid=True), posenetOK=True, inputsOK=True)), - (t, "liveCalibration", log.LiveCalibrationData(rpyCalib=[0, 0, 0], calStatus=log.LiveCalibrationData.Status.calibrated)), + (t, "extrinsicsCalibration", log.ExtrinsicsCalibration(rpyCalib=[0, 0, 0], calStatus=log.ExtrinsicsCalibration.Status.calibrated)), ] for t, w, m in msgs: estimator.handle_log(t, w, m) @@ -59,10 +59,10 @@ class TestLagd(OpenpilotTestCase): CP = get_test_car_params() - msg = messaging.new_message('liveDelay') - msg.liveDelay.lateralDelayEstimate = random.random() - msg.liveDelay.validBlocks = random.randint(1, 10) - msg.liveDelay.version = VERSION + msg = messaging.new_message('lateralDelay') + msg.lateralDelay.lateralDelayEstimate = random.random() + msg.lateralDelay.validBlocks = random.randint(1, 10) + msg.lateralDelay.version = VERSION params.put("LiveDelay", msg.to_bytes(), block=True) params.put("CarParamsPrevRoute", CP.as_builder().to_bytes(), block=True) @@ -70,8 +70,8 @@ class TestLagd(OpenpilotTestCase): assert saved_lag_params is not None lag, valid_blocks = saved_lag_params - assert lag == msg.liveDelay.lateralDelayEstimate - assert valid_blocks == msg.liveDelay.validBlocks + assert lag == msg.lateralDelay.lateralDelayEstimate + assert valid_blocks == msg.lateralDelay.validBlocks def test_read_invalid_saved_params(self, subtests): params = Params() @@ -79,9 +79,9 @@ class TestLagd(OpenpilotTestCase): CP = get_test_car_params() for msg_dict in [{'version': 0}, {'status': 'invalid'}, {'validBlocks': 100}]: - with subtests.test(msg=f"liveDelay={msg_dict}"): - msg = messaging.new_message('liveDelay') - msg.liveDelay = msg_dict + with subtests.test(msg=f"lateralDelay={msg_dict}"): + msg = messaging.new_message('lateralDelay') + msg.lateralDelay = msg_dict params.put("LiveDelay", msg.to_bytes(), block=True) params.put("CarParamsPrevRoute", CP.as_builder().to_bytes(), block=True) assert retrieve_initial_lag(params, CP) is None @@ -114,11 +114,11 @@ class TestLagd(OpenpilotTestCase): mocked_CP = car.CarParams(steerActuatorDelay=0.5) estimator = LateralLagEstimator(mocked_CP, DT) msg = estimator.get_msg(True) - assert msg.liveDelay.status == 'unestimated' - assert np.allclose(msg.liveDelay.lateralDelay, estimator.initial_lag) - assert np.allclose(msg.liveDelay.lateralDelayEstimate, estimator.initial_lag) - assert msg.liveDelay.validBlocks == 0 - assert msg.liveDelay.calPerc == 0 + assert msg.lateralDelay.status == 'unestimated' + assert np.allclose(msg.lateralDelay.lateralDelay, estimator.initial_lag) + assert np.allclose(msg.lateralDelay.lateralDelayEstimate, estimator.initial_lag) + assert msg.lateralDelay.validBlocks == 0 + assert msg.lateralDelay.calPerc == 0 def test_estimator_basics(self, subtests): for lag_frames in range(LAGD_MIN_LAG_FRAMES, LAGD_MAX_LAG_FRAMES - 1): @@ -127,21 +127,21 @@ class TestLagd(OpenpilotTestCase): estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0) process_messages(estimator, lag_frames, int(MIN_OKAY_WINDOW_SEC / DT) + BLOCK_NUM_NEEDED * BLOCK_SIZE) msg = estimator.get_msg(True) - assert msg.liveDelay.status == 'estimated' - assert np.allclose(msg.liveDelay.lateralDelay, lag_frames * DT, atol=0.01) - assert np.allclose(msg.liveDelay.lateralDelayEstimate, lag_frames * DT, atol=0.01) - assert np.allclose(msg.liveDelay.lateralDelayEstimateStd, 0.0, atol=0.01) - assert msg.liveDelay.validBlocks == BLOCK_NUM_NEEDED - assert msg.liveDelay.calPerc == 100 + assert msg.lateralDelay.status == 'estimated' + assert np.allclose(msg.lateralDelay.lateralDelay, lag_frames * DT, atol=0.01) + assert np.allclose(msg.lateralDelay.lateralDelayEstimate, lag_frames * DT, atol=0.01) + assert np.allclose(msg.lateralDelay.lateralDelayEstimateStd, 0.0, atol=0.01) + assert msg.lateralDelay.validBlocks == BLOCK_NUM_NEEDED + assert msg.lateralDelay.calPerc == 100 def test_estimator_masking(self): mocked_CP, lag_frames = car.CarParams(steerActuatorDelay=0.5), random.randint(LAGD_MIN_LAG_FRAMES, LAGD_MAX_LAG_FRAMES - 1) estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0, min_valid_block_count=1) process_messages(estimator, lag_frames, (int(MIN_OKAY_WINDOW_SEC / DT) + BLOCK_SIZE) * 2, rejection_threshold=0.4) msg = estimator.get_msg(True) - assert np.allclose(msg.liveDelay.lateralDelayEstimate, lag_frames * DT, atol=0.01) - assert np.allclose(msg.liveDelay.lateralDelayEstimateStd, 0.0, atol=0.01) - assert msg.liveDelay.calPerc == 100 + assert np.allclose(msg.lateralDelay.lateralDelayEstimate, lag_frames * DT, atol=0.01) + assert np.allclose(msg.lateralDelay.lateralDelayEstimateStd, 0.0, atol=0.01) + assert msg.lateralDelay.calPerc == 100 @unittest.skipIf(PC, "only on device") def test_estimator_performance(self): diff --git a/openpilot/selfdrive/locationd/test/test_locationd_scenarios.py b/openpilot/selfdrive/locationd/test/test_locationd_scenarios.py index c5fe1908bc..01fc3f1177 100644 --- a/openpilot/selfdrive/locationd/test/test_locationd_scenarios.py +++ b/openpilot/selfdrive/locationd/test/test_locationd_scenarios.py @@ -45,7 +45,7 @@ def get_select_fields_data(logs): for key in keys: val = getattr(val, key) if isinstance(key, str) else val[key] return val - lp = [x.livePose for x in logs if x.which() == 'livePose'] + lp = [x.deviceMotion for x in logs if x.which() == 'deviceMotion'] data = defaultdict(list) for msg in lp: for key, fields in SELECT_COMPARE_FIELDS.items(): diff --git a/openpilot/selfdrive/locationd/test/test_paramsd.py b/openpilot/selfdrive/locationd/test/test_paramsd.py index 09a067f4cf..515c7a814a 100644 --- a/openpilot/selfdrive/locationd/test/test_paramsd.py +++ b/openpilot/selfdrive/locationd/test/test_paramsd.py @@ -11,12 +11,12 @@ from openpilot.common.params import Params from openpilot.tools.lib.logreader import LogReader -def get_random_live_parameters(CP): - msg = messaging.new_message("liveParameters") - msg.liveParameters.steerRatio = (random.random() + 0.5) * CP.steerRatio - msg.liveParameters.stiffnessFactor = random.random() - msg.liveParameters.angleOffsetAverageDeg = random.random() - msg.liveParameters.debugFilterState.std = [random.random() for _ in range(CarKalman.P_initial.shape[0])] +def get_random_vehicle_parameters(CP): + msg = messaging.new_message("vehicleParameters") + msg.vehicleParameters.steerRatio = (random.random() + 0.5) * CP.steerRatio + msg.vehicleParameters.stiffnessFactor = random.random() + msg.vehicleParameters.angleOffsetAverageDeg = random.random() + msg.vehicleParameters.debugFilterState.std = [random.random() for _ in range(CarKalman.P_initial.shape[0])] return msg @@ -27,13 +27,13 @@ class TestParamsd(OpenpilotTestCase): lr = migrate(LogReader(TEST_ROUTE), [migrate_carParams]) CP = next(m for m in lr if m.which() == "carParams").carParams - msg = get_random_live_parameters(CP) + msg = get_random_vehicle_parameters(CP) params.put("LiveParametersV2", msg.to_bytes(), block=True) params.put("CarParamsPrevRoute", CP.as_builder().to_bytes(), block=True) sr, sf, offset, p_init = retrieve_initial_vehicle_params(params, CP, replay=True, debug=True) - np.testing.assert_allclose(sr, msg.liveParameters.steerRatio) - np.testing.assert_allclose(sf, msg.liveParameters.stiffnessFactor) - np.testing.assert_allclose(offset, msg.liveParameters.angleOffsetAverageDeg) + np.testing.assert_allclose(sr, msg.vehicleParameters.steerRatio) + np.testing.assert_allclose(sf, msg.vehicleParameters.stiffnessFactor) + np.testing.assert_allclose(offset, msg.vehicleParameters.angleOffsetAverageDeg) np.testing.assert_equal(p_init.shape, CarKalman.P_initial.shape) - np.testing.assert_allclose(np.diagonal(p_init), msg.liveParameters.debugFilterState.std) + np.testing.assert_allclose(np.diagonal(p_init), msg.vehicleParameters.debugFilterState.std) diff --git a/openpilot/selfdrive/locationd/test/test_torqued.py b/openpilot/selfdrive/locationd/test/test_torqued.py index af3aeb95d6..3c5cb29bfc 100644 --- a/openpilot/selfdrive/locationd/test/test_torqued.py +++ b/openpilot/selfdrive/locationd/test/test_torqued.py @@ -7,7 +7,7 @@ class TestTorqued(OpenpilotTestCase): def test_cal_percent(self): est = TorqueEstimator(car.CarParams()) msg = est.get_msg() - assert msg.liveTorqueParameters.calPerc == 0 + assert msg.lateralTorqueParameters.calPerc == 0 for (low, high), min_pts in zip(est.filtered_points.buckets.keys(), est.filtered_points.buckets_min_points.values(), strict=True): @@ -16,7 +16,7 @@ class TestTorqued(OpenpilotTestCase): # enough bucket points, but not enough total points msg = est.get_msg() - assert msg.liveTorqueParameters.calPerc == (len(est.filtered_points) / est.min_points_total * 100 + 100) / 2 + assert msg.lateralTorqueParameters.calPerc == (len(est.filtered_points) / est.min_points_total * 100 + 100) / 2 # add enough points to bucket with most capacity key = list(est.filtered_points.buckets)[0] @@ -24,4 +24,4 @@ class TestTorqued(OpenpilotTestCase): est.filtered_points.add_point((key[0] + key[1]) / 2.0, 0.0) msg = est.get_msg() - assert msg.liveTorqueParameters.calPerc == 100 + assert msg.lateralTorqueParameters.calPerc == 100 diff --git a/openpilot/selfdrive/locationd/torqued.py b/openpilot/selfdrive/locationd/torqued.py index d36684563c..21b2af7f12 100755 --- a/openpilot/selfdrive/locationd/torqued.py +++ b/openpilot/selfdrive/locationd/torqued.py @@ -102,11 +102,11 @@ class TorqueEstimator(ParameterEstimator): if params_cache is not None and torque_cache is not None: try: with log.Event.from_bytes(torque_cache) as log_evt: - cache_ltp = log_evt.liveTorqueParameters + cache_ltp = log_evt.lateralTorqueParameters with car.CarParams.from_bytes(params_cache) as msg: cache_CP = msg if self.get_restore_key(cache_CP, cache_ltp.version) == self.get_restore_key(CP, VERSION): - if cache_ltp.liveValid: + if cache_ltp.valid: initial_params = { 'latAccelFactor': cache_ltp.latAccelFactorFiltered, 'latAccelOffset': cache_ltp.latAccelOffsetFiltered, @@ -154,7 +154,7 @@ class TorqueEstimator(ParameterEstimator): _, spread = np.matmul(points[:, [0, 2]], slope2rot(slope)).T friction_coeff = np.std(spread) * FRICTION_FACTOR except np.linalg.LinAlgError as e: - cloudlog.exception(f"Error computing live torque params: {e}") + cloudlog.exception(f"Error computing lateral torque parameters: {e}") slope = offset = friction_coeff = np.nan return slope, offset, friction_coeff @@ -176,21 +176,21 @@ class TorqueEstimator(ParameterEstimator): # TODO: check if high aEgo affects resulting lateral accel self.raw_points["vego"].append(msg.vEgo) self.raw_points["steer_override"].append(msg.steeringPressed) - elif which == "liveCalibration": - self.calibrator.feed_live_calib(msg) - elif which == "liveDelay": + elif which == "extrinsicsCalibration": + self.calibrator.feed_extrinsics_calibration(msg) + elif which == "lateralDelay": self.lag = msg.lateralDelay # calculate lateral accel from past steering torque - elif which == "livePose": + elif which == "deviceMotion": is_valid = msg.angularVelocityDevice.valid and msg.orientationNED.valid and msg.inputsOK and msg.sensorsOK and msg.posenetOK if len(self.raw_points['steer_torque']) == self.hist_len and is_valid: t = msg.timestamp * 1e-9 - device_pose = Pose.from_live_pose(msg) - calibrated_pose = self.calibrator.build_calibrated_pose(device_pose) + device_motion = Pose.from_device_motion(msg) + calibrated_pose = self.calibrator.build_calibrated_pose(device_motion) angular_velocity_calibrated = calibrated_pose.angular_velocity yaw_rate = angular_velocity_calibrated.yaw - roll = device_pose.orientation.roll + roll = device_motion.orientation.roll # check lat active up to now (without lag compensation) lat_active = np.interp(np.arange(t - MIN_ENGAGE_BUFFER, t + self.lag, DT_MDL), self.raw_points['carControl_t'], self.raw_points['lat_active']).astype(bool) @@ -207,40 +207,40 @@ class TorqueEstimator(ParameterEstimator): self.all_torque_points.append([steer, lateral_acc]) def get_msg(self, valid=True, with_points=False): - msg = messaging.new_message('liveTorqueParameters') + msg = messaging.new_message('lateralTorqueParameters') msg.valid = valid - liveTorqueParameters = msg.liveTorqueParameters - liveTorqueParameters.version = VERSION - liveTorqueParameters.useParams = self.use_params + lateralTorqueParameters = msg.lateralTorqueParameters + lateralTorqueParameters.version = VERSION + lateralTorqueParameters.useParams = self.use_params # Calculate raw estimates when possible, only update filters when enough points are gathered if self.filtered_points.is_calculable(): latAccelFactor, latAccelOffset, frictionCoeff = self.estimate_params() - liveTorqueParameters.latAccelFactorRaw = float(latAccelFactor) - liveTorqueParameters.latAccelOffsetRaw = float(latAccelOffset) - liveTorqueParameters.frictionCoefficientRaw = float(frictionCoeff) + lateralTorqueParameters.latAccelFactorRaw = float(latAccelFactor) + lateralTorqueParameters.latAccelOffsetRaw = float(latAccelOffset) + lateralTorqueParameters.frictionCoefficientRaw = float(frictionCoeff) if self.filtered_points.is_valid(): if any(val is None or np.isnan(val) for val in [latAccelFactor, latAccelOffset, frictionCoeff]): - cloudlog.exception("Live torque parameters are invalid.") - liveTorqueParameters.liveValid = False + cloudlog.exception("Lateral torque parameters are invalid.") + lateralTorqueParameters.valid = False self.reset() else: - liveTorqueParameters.liveValid = True + lateralTorqueParameters.valid = True latAccelFactor = np.clip(latAccelFactor, self.min_lataccel_factor, self.max_lataccel_factor) frictionCoeff = np.clip(frictionCoeff, self.min_friction, self.max_friction) self.update_params({'latAccelFactor': latAccelFactor, 'latAccelOffset': latAccelOffset, 'frictionCoefficient': frictionCoeff}) if with_points: - liveTorqueParameters.points = self.filtered_points.get_points()[:, [0, 2]].tolist() + lateralTorqueParameters.points = self.filtered_points.get_points()[:, [0, 2]].tolist() - liveTorqueParameters.latAccelFactorFiltered = float(self.filtered_params['latAccelFactor'].x) - liveTorqueParameters.latAccelOffsetFiltered = float(self.filtered_params['latAccelOffset'].x) - liveTorqueParameters.frictionCoefficientFiltered = float(self.filtered_params['frictionCoefficient'].x) - liveTorqueParameters.totalBucketPoints = len(self.filtered_points) - liveTorqueParameters.calPerc = self.filtered_points.get_valid_percent() - liveTorqueParameters.decay = self.decay - liveTorqueParameters.maxResets = self.resets + lateralTorqueParameters.latAccelFactorFiltered = float(self.filtered_params['latAccelFactor'].x) + lateralTorqueParameters.latAccelOffsetFiltered = float(self.filtered_params['latAccelOffset'].x) + lateralTorqueParameters.frictionCoefficientFiltered = float(self.filtered_params['frictionCoefficient'].x) + lateralTorqueParameters.totalBucketPoints = len(self.filtered_points) + lateralTorqueParameters.calPerc = self.filtered_points.get_valid_percent() + lateralTorqueParameters.decay = self.decay + lateralTorqueParameters.maxResets = self.resets return msg @@ -249,8 +249,8 @@ def main(demo=False): DEBUG = bool(int(os.getenv("DEBUG", "0"))) - pm = messaging.PubMaster(['liveTorqueParameters']) - sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'liveCalibration', 'livePose', 'liveDelay'], poll='livePose') + pm = messaging.PubMaster(['lateralTorqueParameters']) + sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'extrinsicsCalibration', 'deviceMotion', 'lateralDelay'], poll='deviceMotion') params = Params() estimator = TorqueEstimator(messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)) @@ -263,9 +263,9 @@ def main(demo=False): t = sm.logMonoTime[which] * 1e-9 estimator.handle_log(t, which, sm[which]) - # 4Hz driven by livePose + # 4Hz driven by deviceMotion if sm.frame % 5 == 0: - pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks(), with_points=DEBUG)) + pm.send('lateralTorqueParameters', estimator.get_msg(valid=sm.all_checks(), with_points=DEBUG)) # Cache points every 60 seconds while onroad if sm.frame % 240 == 0: diff --git a/openpilot/selfdrive/modeld/dmonitoringmodeld.py b/openpilot/selfdrive/modeld/dmonitoringmodeld.py index 02391aeee3..67161c35bd 100755 --- a/openpilot/selfdrive/modeld/dmonitoringmodeld.py +++ b/openpilot/selfdrive/modeld/dmonitoringmodeld.py @@ -120,7 +120,7 @@ def main(): model = ModelState(vipc_client.width, vipc_client.height) cloudlog.warning("models loaded, dmonitoringmodeld starting") - sm = SubMaster(["liveCalibration"]) + sm = SubMaster(["extrinsicsCalibration"]) pm = PubMaster(["driverStateV2"]) calib = np.zeros(model.numpy_inputs['calib'].size, dtype=np.float32) @@ -136,8 +136,8 @@ def main(): model_transform = np.linalg.inv(np.dot(dmonitoringmodel_intrinsics, np.linalg.inv(cam.intrinsics))).astype(np.float32) sm.update(0) - if sm.updated["liveCalibration"]: - calib[:] = np.array(sm["liveCalibration"].rpyCalib) + if sm.updated["extrinsicsCalibration"]: + calib[:] = np.array(sm["extrinsicsCalibration"].rpyCalib) t1 = time.perf_counter() model_output, gpu_execution_time = model.run(buf, calib, model_transform) diff --git a/openpilot/selfdrive/modeld/fill_model_msg.py b/openpilot/selfdrive/modeld/fill_model_msg.py index d926ace3da..558f881b37 100644 --- a/openpilot/selfdrive/modeld/fill_model_msg.py +++ b/openpilot/selfdrive/modeld/fill_model_msg.py @@ -174,8 +174,8 @@ def fill_model_msg(msg: capnp._DynamicStructBuilder, net_output_data: dict[str, modelV2.rawPredictions = net_output_data['raw_pred'].tobytes() def fill_pose_msg(msg: capnp._DynamicStructBuilder, net_output_data: dict[str, np.ndarray], - vipc_frame_id: int, vipc_dropped_frames: int, timestamp_eof: int, live_calib_seen: bool) -> None: - msg.valid = live_calib_seen & (vipc_dropped_frames < 1) + vipc_frame_id: int, vipc_dropped_frames: int, timestamp_eof: int, extrinsics_calibration_seen: bool) -> None: + msg.valid = extrinsics_calibration_seen & (vipc_dropped_frames < 1) cameraOdometry = msg.cameraOdometry cameraOdometry.frameId = vipc_frame_id diff --git a/openpilot/selfdrive/modeld/modeld.py b/openpilot/selfdrive/modeld/modeld.py index f724e530e2..e87d4f3111 100755 --- a/openpilot/selfdrive/modeld/modeld.py +++ b/openpilot/selfdrive/modeld/modeld.py @@ -262,7 +262,7 @@ def main(demo=False): # messaging pub_socks = ["modelV2", "drivingModelData", "cameraOdometry"] + (["chestnutState"] if USBGPU else []) pm = PubMaster(pub_socks) - sm = SubMaster(["deviceState", "carState", "narrowRoadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"]) + sm = SubMaster(["deviceState", "carState", "narrowRoadCameraState", "extrinsicsCalibration", "driverMonitoringState", "carControl", "lateralDelay"]) publish_state = PublishState() params = Params() @@ -276,7 +276,7 @@ def main(demo=False): model_transform_main = np.zeros((3, 3), dtype=np.float32) model_transform_extra = np.zeros((3, 3), dtype=np.float32) - live_calib_seen = False + extrinsics_calibration_seen = False buf_main, buf_extra = None, None meta_main = FrameMeta() meta_extra = FrameMeta() @@ -332,16 +332,16 @@ def main(demo=False): is_rhd = sm["driverMonitoringState"].isRHD frame_id = sm["narrowRoadCameraState"].frameId v_ego = max(sm["carState"].vEgo, 0.) - lat_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS - if sm.updated["liveCalibration"] and sm.seen['narrowRoadCameraState'] and sm.seen['deviceState']: - device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) + lat_delay = sm["lateralDelay"].lateralDelay + LAT_SMOOTH_SECONDS + if sm.updated["extrinsicsCalibration"] and sm.seen['narrowRoadCameraState'] and sm.seen['deviceState']: + device_from_calib_euler = np.array(sm["extrinsicsCalibration"].rpyCalib, dtype=np.float32) dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['narrowRoadCameraState'].sensor))] main_intrinsics = dc.wide_road.intrinsics if main_wide_camera else dc.narrow_road.intrinsics model_transform_main = get_warp_matrix(device_from_calib_euler, main_intrinsics, False).astype(np.float32) has_wide_camera = use_extra_client or main_wide_camera extra_intrinsics = dc.wide_road.intrinsics if has_wide_camera else dc.narrow_road.intrinsics model_transform_extra = get_warp_matrix(device_from_calib_euler, extra_intrinsics, True).astype(np.float32) - live_calib_seen = True + extrinsics_calibration_seen = True traffic_convention = np.zeros(2) traffic_convention[int(is_rhd)] = 1 @@ -396,7 +396,7 @@ def main(demo=False): prev_action = action fill_model_msg(modelv2_send, model_output, action, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, - frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, live_calib_seen) + frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, extrinsics_calibration_seen) modelv2_send.modelV2.big = model.usbgpu desire_state = modelv2_send.modelV2.meta.desireState @@ -408,7 +408,7 @@ def main(demo=False): modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction fill_driving_model_data(drivingdata_send, modelv2_send) - fill_pose_msg(posenet_send, model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, live_calib_seen) + fill_pose_msg(posenet_send, model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, extrinsics_calibration_seen) pm.send('modelV2', modelv2_send) pm.send('drivingModelData', drivingdata_send) pm.send('cameraOdometry', posenet_send) diff --git a/openpilot/selfdrive/monitoring/dmonitoringd.py b/openpilot/selfdrive/monitoring/dmonitoringd.py index a70659cb06..e686266fe8 100755 --- a/openpilot/selfdrive/monitoring/dmonitoringd.py +++ b/openpilot/selfdrive/monitoring/dmonitoringd.py @@ -10,7 +10,7 @@ def dmonitoringd_thread(): params = Params() pm = messaging.PubMaster(['driverMonitoringState']) - sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'selfdriveState', 'modelV2'], poll='driverStateV2') + sm = messaging.SubMaster(['driverStateV2', 'extrinsicsCalibration', 'carState', 'selfdriveState', 'modelV2'], poll='driverStateV2') DM = DriverMonitoring(rhd_saved=params.get_bool("IsRhdDetected"), always_on=params.get_bool("AlwaysOnDM")) demo_mode=False diff --git a/openpilot/selfdrive/monitoring/policy.py b/openpilot/selfdrive/monitoring/policy.py index b58f1e66dc..06aca55407 100644 --- a/openpilot/selfdrive/monitoring/policy.py +++ b/openpilot/selfdrive/monitoring/policy.py @@ -441,7 +441,7 @@ class DriverMonitoring: driver_engaged = sm['carState'].steeringPressed or sm['carState'].gasPressed brake_disengage_prob = sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs[0] # brake disengage prob in next 2s steering_angle_deg = sm['carState'].steeringAngleDeg - rpyCalib = sm['liveCalibration'].rpyCalib + rpyCalib = sm['extrinsicsCalibration'].rpyCalib self._set_pose_strictness( brake_disengage_prob=brake_disengage_prob, diff --git a/openpilot/selfdrive/selfdrived/events.py b/openpilot/selfdrive/selfdrived/events.py index 2127cdeceb..1482a4e334 100755 --- a/openpilot/selfdrive/selfdrived/events.py +++ b/openpilot/selfdrive/selfdrived/events.py @@ -255,9 +255,9 @@ def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.S def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: - first_word = 'Recalibrating' if sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.recalibrating else 'Calibrating' + first_word = 'Recalibrating' if sm['extrinsicsCalibration'].calStatus == log.ExtrinsicsCalibration.Status.recalibrating else 'Calibrating' return Alert( - f"{first_word}: {sm['liveCalibration'].calPerc:.0f}%", + f"{first_word}: {sm['extrinsicsCalibration'].calPerc:.0f}%", f"Drive Above {get_display_speed(MIN_SPEED_FILTER, metric)}", AlertStatus.normal, AlertSize.mid, Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2) @@ -303,7 +303,7 @@ def camera_malfunction_alert(CP: car.CarParams, CS: car.CarState, sm: messaging. def calibration_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: - rpy = sm['liveCalibration'].rpyCalib + rpy = sm['extrinsicsCalibration'].rpyCalib yaw = math.degrees(rpy[2] if len(rpy) == 3 else math.nan) pitch = math.degrees(rpy[1] if len(rpy) == 3 else math.nan) angles = f"Remount Device (Pitch: {pitch:.1f}°, Yaw: {yaw:.1f}°)" @@ -311,16 +311,16 @@ def calibration_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging def paramsd_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: - if not sm['liveParameters'].angleOffsetValid: - angle_offset_deg = sm['liveParameters'].angleOffsetDeg + if not sm['vehicleParameters'].angleOffsetValid: + angle_offset_deg = sm['vehicleParameters'].angleOffsetDeg title = "Steering misalignment detected" text = f"Angle offset too high (Offset: {angle_offset_deg:.1f}°)" - elif not sm['liveParameters'].steerRatioValid: - steer_ratio = sm['liveParameters'].steerRatio + elif not sm['vehicleParameters'].steerRatioValid: + steer_ratio = sm['vehicleParameters'].steerRatio title = "Steer ratio mismatch" text = f"Steering rack geometry may be off (Ratio: {steer_ratio:.1f})" - elif not sm['liveParameters'].stiffnessFactorValid: - stiffness_factor = sm['liveParameters'].stiffnessFactor + elif not sm['vehicleParameters'].stiffnessFactorValid: + stiffness_factor = sm['vehicleParameters'].stiffnessFactor title = "Abnormal tire stiffness" text = f"Check tires, pressure, or alignment (Factor: {stiffness_factor:.1f})" else: diff --git a/openpilot/selfdrive/selfdrived/helpers.py b/openpilot/selfdrive/selfdrived/helpers.py index 3b3c44ecaf..e7dca0f9d5 100644 --- a/openpilot/selfdrive/selfdrived/helpers.py +++ b/openpilot/selfdrive/selfdrived/helpers.py @@ -31,7 +31,7 @@ class ExcessiveActuationCheck: # lateral yaw_rate = calibrated_pose.angular_velocity.yaw - roll = sm['liveParameters'].roll + roll = sm['vehicleParameters'].roll roll_compensated_lateral_accel = (CS.vEgo * yaw_rate) - (math.sin(roll) * ACCELERATION_DUE_TO_GRAVITY) # Prevent false positives after overriding @@ -41,9 +41,9 @@ class ExcessiveActuationCheck: if abs(roll_compensated_lateral_accel) > ISO_LATERAL_ACCEL * 2: excessive_lat_actuation = True - # livePose acceleration can be noisy due to bad mounting or aliased livePose measurements - livepose_valid = abs(CS.aEgo - accel_calibrated) < 2 - self._excessive_counter = self._excessive_counter + 1 if livepose_valid and (excessive_long_actuation or excessive_lat_actuation) else 0 + # deviceMotion acceleration can be noisy due to bad mounting or aliased deviceMotion measurements + device_motion_valid = abs(CS.aEgo - accel_calibrated) < 2 + self._excessive_counter = self._excessive_counter + 1 if device_motion_valid and (excessive_long_actuation or excessive_lat_actuation) else 0 excessive_type = None if self._excessive_counter > MIN_EXCESSIVE_ACTUATION_COUNT: diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index 59c5f2695e..73f382ac3c 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -88,9 +88,9 @@ class SelfdriveD: if REPLAY: # no vipc in replay will make them ignored anyways ignore += ['narrowRoadCameraState', 'wideRoadCameraState'] - self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', - 'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay', - 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', + self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'extrinsicsCalibration', + 'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'deviceMotion', 'lateralDelay', + 'managerState', 'vehicleParameters', 'radarState', 'lateralTorqueParameters', 'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'lateralManeuverPlan'] + \ self.camera_packets + self.sensor_packets + self.gps_packets, @@ -270,11 +270,11 @@ class SelfdriveD: self.last_functional_fan_frame = self.sm.frame # Handle calibration status - cal_status = self.sm['liveCalibration'].calStatus - if cal_status != log.LiveCalibrationData.Status.calibrated: - if cal_status == log.LiveCalibrationData.Status.uncalibrated: + cal_status = self.sm['extrinsicsCalibration'].calStatus + if cal_status != log.ExtrinsicsCalibration.Status.calibrated: + if cal_status == log.ExtrinsicsCalibration.Status.uncalibrated: self.events.add(EventName.calibrationIncomplete) - elif cal_status == log.LiveCalibrationData.Status.recalibrating: + elif cal_status == log.ExtrinsicsCalibration.Status.recalibrating: if not self.recalibrating_seen: set_offroad_alert("Offroad_Recalibration", True) self.recalibrating_seen = True @@ -291,11 +291,11 @@ class SelfdriveD: # NOTE: To fork maintainers. # Disabling or nerfing safety features will get you and your users banned from our servers. # We recommend that you do not change these numbers from the defaults. - if self.sm.updated['liveCalibration']: - self.pose_calibrator.feed_live_calib(self.sm['liveCalibration']) - if self.sm.updated['livePose']: - device_pose = Pose.from_live_pose(self.sm['livePose']) - self.calibrated_pose = self.pose_calibrator.build_calibrated_pose(device_pose) + if self.sm.updated['extrinsicsCalibration']: + self.pose_calibrator.feed_extrinsics_calibration(self.sm['extrinsicsCalibration']) + if self.sm.updated['deviceMotion']: + device_motion = Pose.from_device_motion(self.sm['deviceMotion']) + self.calibrated_pose = self.pose_calibrator.build_calibrated_pose(device_motion) if self.calibrated_pose is not None: excessive_actuation = self.excessive_actuation_check.update(self.sm, CS, self.calibrated_pose) @@ -395,11 +395,12 @@ class SelfdriveD: self.logged_comm_issue = None if not self.CP.notCar and not big_model_settling: # localization has nothing to work with during the load - if not self.sm['livePose'].posenetOK: + if not self.sm['deviceMotion'].posenetOK: self.events.add(EventName.posenetInvalid) - if not self.sm['livePose'].inputsOK: + if not self.sm['deviceMotion'].inputsOK: self.events.add(EventName.locationdTemporaryError) - if not self.sm['liveParameters'].valid and cal_status == log.LiveCalibrationData.Status.calibrated and not TESTING_CLOSET and (not SIMULATION or REPLAY): + if (not self.sm['vehicleParameters'].valid and cal_status == log.ExtrinsicsCalibration.Status.calibrated and + not TESTING_CLOSET and (not SIMULATION or REPLAY)): self.events.add(EventName.paramsdTemporaryError) # conservative HW alert. if the data or frequency are off, locationd will throw an error @@ -438,7 +439,7 @@ class SelfdriveD: # GPS checks gps_ok = self.sm.recv_frame[self.gps_location_service] > 0 and (self.sm.frame - self.sm.recv_frame[self.gps_location_service]) * DT_CTRL < 2.0 - if not gps_ok and self.sm['livePose'].inputsOK and (self.distance_traveled > 1500): + if not gps_ok and self.sm['deviceMotion'].inputsOK and (self.distance_traveled > 1500): self.events.add(EventName.noGps) if gps_ok: self.distance_traveled = 0 diff --git a/openpilot/selfdrive/test/helpers.py b/openpilot/selfdrive/test/helpers.py index 2bcb6d8409..71cdcee5e7 100644 --- a/openpilot/selfdrive/test/helpers.py +++ b/openpilot/selfdrive/test/helpers.py @@ -22,9 +22,9 @@ def set_params_enabled(): params.put_bool("OpenpilotEnabledToggle", True, block=True) # valid calib - msg = messaging.new_message('liveCalibration') - msg.liveCalibration.validBlocks = 20 - msg.liveCalibration.rpyCalib = [0.0, 0.0, 0.0] + msg = messaging.new_message('extrinsicsCalibration') + msg.extrinsicsCalibration.validBlocks = 20 + msg.extrinsicsCalibration.rpyCalib = [0.0, 0.0, 0.0] params.put("CalibrationParams", msg.to_bytes(), block=True) def release_only(f): diff --git a/openpilot/selfdrive/test/longitudinal_maneuvers/plant.py b/openpilot/selfdrive/test/longitudinal_maneuvers/plant.py index bdd5c51ee4..036aaa2ed3 100755 --- a/openpilot/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/openpilot/selfdrive/test/longitudinal_maneuvers/plant.py @@ -64,7 +64,7 @@ class Plant: control = messaging.new_message('controlsState') ss = messaging.new_message('selfdriveState') car_state = messaging.new_message('carState') - lp = messaging.new_message('liveParameters') + lp = messaging.new_message('vehicleParameters') car_control = messaging.new_message('carControl') model = messaging.new_message('modelV2') a_lead = (v_lead - self.v_lead_prev)/self.ts @@ -132,7 +132,7 @@ class Plant: 'carControl': car_control.carControl, 'controlsState': control.controlsState, 'selfdriveState': ss.selfdriveState, - 'liveParameters': lp.liveParameters, + 'vehicleParameters': lp.vehicleParameters, 'modelV2': model.modelV2} self.planner.update(sm) self.acceleration = self.planner.output_a_target diff --git a/openpilot/selfdrive/test/process_replay/README.md b/openpilot/selfdrive/test/process_replay/README.md index f39333a3c6..36d669dda6 100644 --- a/openpilot/selfdrive/test/process_replay/README.md +++ b/openpilot/selfdrive/test/process_replay/README.md @@ -85,7 +85,7 @@ Supported processes: * modeld * dmonitoringmodeld -Certain processes may require an initial state, which is usually supplied within `Params` and persisting from segment to segment (e.g CalibrationParams, LiveParameters). The `custom_params` is dictionary used to prepopulate `Params` with arbitrary values. The `get_custom_params_from_lr` helper is provided to fetch meaningful values from log files. +Certain processes may require an initial state, which is usually supplied within `Params` and persists from segment to segment (for example `CalibrationParams` or the learner cache keys like `LiveParametersV2`). The `custom_params` is a dictionary used to prepopulate `Params` with arbitrary values. The `get_custom_params_from_lr` helper is provided to fetch meaningful values from log files. ```py from openpilot.selfdrive.test.process_replay import get_custom_params_from_lr diff --git a/openpilot/selfdrive/test/process_replay/migration.py b/openpilot/selfdrive/test/process_replay/migration.py index 65d54221ee..2fb8932abc 100644 --- a/openpilot/selfdrive/test/process_replay/migration.py +++ b/openpilot/selfdrive/test/process_replay/migration.py @@ -40,7 +40,7 @@ def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: boo migrate_controlsState, migrate_carState, migrate_liveLocationKalman, - migrate_livePose, + migrate_deviceMotion, migrate_liveTracks, migrate_driverAssistance, migrate_drivingModelData, @@ -161,11 +161,11 @@ def migrate_drivingModelData(msgs): return [], add_ops, [] -@migration(inputs=["liveTracksDEPRECATED"], product="liveTracks") +@migration(inputs=["liveTracksDEPRECATED"], product="radarTracks") def migrate_liveTracks(msgs): ops = [] for index, msg in msgs: - new_msg = messaging.new_message('liveTracks') + new_msg = messaging.new_message('radarTracks') new_msg.valid = msg.valid new_msg.logMonoTime = msg.logMonoTime @@ -179,42 +179,42 @@ def migrate_liveTracks(msgs): pt.vRel = track.vRel pts.append(pt) - new_msg.liveTracks.points = pts + new_msg.radarTracks.points = pts ops.append((index, as_reader(new_msg))) return ops, [], [] -@migration(inputs=["liveLocationKalmanDEPRECATED"], product="livePose") +@migration(inputs=["liveLocationKalmanDEPRECATED"], product="deviceMotion") def migrate_liveLocationKalman(msgs): nans = [float('nan')] * 3 ops = [] for index, msg in msgs: - m = messaging.new_message('livePose') + m = messaging.new_message('deviceMotion') m.valid = msg.valid m.logMonoTime = msg.logMonoTime - m.livePose.timestamp = msg.logMonoTime + m.deviceMotion.timestamp = msg.logMonoTime for field in ["orientationNED", "velocityDevice", "accelerationDevice", "angularVelocityDevice"]: - lp_field, llk_field = getattr(m.livePose, field), getattr(msg.liveLocationKalmanDEPRECATED, field) + lp_field, llk_field = getattr(m.deviceMotion, field), getattr(msg.liveLocationKalmanDEPRECATED, field) lp_field.x, lp_field.y, lp_field.z = llk_field.value or nans lp_field.xStd, lp_field.yStd, lp_field.zStd = llk_field.std or nans lp_field.valid = llk_field.valid for flag in ["inputsOK", "posenetOK", "sensorsOK"]: - setattr(m.livePose, flag, getattr(msg.liveLocationKalmanDEPRECATED, flag)) + setattr(m.deviceMotion, flag, getattr(msg.liveLocationKalmanDEPRECATED, flag)) ops.append((index, as_reader(m))) return ops, [], [] -@migration(inputs=["livePose"]) -def migrate_livePose(msgs): +@migration(inputs=["deviceMotion"]) +def migrate_deviceMotion(msgs): ops = [] - needs_migration = all(msg.livePose.timestamp == 0 for _, msg in msgs if msg.which() == 'livePose') + needs_migration = all(msg.deviceMotion.timestamp == 0 for _, msg in msgs if msg.which() == 'deviceMotion') if not needs_migration: return [], [], [] for index, msg in msgs: - if msg.which() == "livePose": + if msg.which() == "deviceMotion": new_msg = msg.as_builder() - new_msg.livePose.timestamp = msg.logMonoTime + new_msg.deviceMotion.timestamp = msg.logMonoTime ops.append((index, as_reader(new_msg))) return ops, [], [] diff --git a/openpilot/selfdrive/test/process_replay/model_replay.py b/openpilot/selfdrive/test/process_replay/model_replay.py index 3b5038b24c..ba610c6a2b 100755 --- a/openpilot/selfdrive/test/process_replay/model_replay.py +++ b/openpilot/selfdrive/test/process_replay/model_replay.py @@ -152,11 +152,11 @@ def model_replay(lr, frs): dmodeld_logs = trim_logs(lr, START_FRAME, END_FRAME, {"cabinCameraState"}, {"cabinEncodeIdx", "carParams", "can"}) if not SEND_EXTRA_INPUTS: - modeld_logs = [msg for msg in modeld_logs if msg.which() != 'liveCalibration'] - dmodeld_logs = [msg for msg in dmodeld_logs if msg.which() != 'liveCalibration'] + modeld_logs = [msg for msg in modeld_logs if msg.which() != 'extrinsicsCalibration'] + dmodeld_logs = [msg for msg in dmodeld_logs if msg.which() != 'extrinsicsCalibration'] # initial setup - for s in ('liveCalibration', 'deviceState'): + for s in ('extrinsicsCalibration', 'deviceState'): msg = next(msg for msg in lr if msg.which() == s).as_builder() msg.logMonoTime = lr[0].logMonoTime modeld_logs.insert(1, msg.as_reader()) diff --git a/openpilot/selfdrive/test/process_replay/process_replay.py b/openpilot/selfdrive/test/process_replay/process_replay.py index 346d532140..5abfab2c35 100755 --- a/openpilot/selfdrive/test/process_replay/process_replay.py +++ b/openpilot/selfdrive/test/process_replay/process_replay.py @@ -432,9 +432,9 @@ CONFIGS = [ ProcessConfig( proc_name="selfdrived", pubs=[ - "carState", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState", - "longitudinalPlan", "livePose", "liveDelay", "liveParameters", "radarState", "modelV2", - "cabinCameraState", "narrowRoadCameraState", "wideRoadCameraState", "managerState", "liveTorqueParameters", + "carState", "deviceState", "pandaStates", "peripheralState", "extrinsicsCalibration", "driverMonitoringState", + "longitudinalPlan", "deviceMotion", "lateralDelay", "vehicleParameters", "radarState", "modelV2", + "cabinCameraState", "narrowRoadCameraState", "wideRoadCameraState", "managerState", "lateralTorqueParameters", "accelerometer", "gyroscope", "carOutput", "gpsLocationExternal", "gpsLocation", "controlsState", "carControl", "driverAssistance", "alertDebug", ], @@ -448,8 +448,8 @@ CONFIGS = [ ), ProcessConfig( proc_name="controlsd", - pubs=["liveParameters", "liveTorqueParameters", "modelV2", "selfdriveState", - "liveCalibration", "livePose", "longitudinalPlan", "carState", "carOutput", + pubs=["vehicleParameters", "lateralTorqueParameters", "modelV2", "selfdriveState", + "extrinsicsCalibration", "deviceMotion", "longitudinalPlan", "carState", "carOutput", "driverMonitoringState", "onroadEvents", "driverAssistance"], subs=["carControl", "controlsState"], ignore=["logMonoTime", ], @@ -460,7 +460,7 @@ CONFIGS = [ ProcessConfig( proc_name="card", pubs=["pandaStates", "carControl", "onroadEvents", "can"], - subs=["sendcan", "carState", "carParams", "carOutput", "liveTracks"], + subs=["sendcan", "carState", "carParams", "carOutput", "radarTracks"], ignore=["logMonoTime", "carState.cumLagMs"], init_callback=card_fingerprint_callback, should_recv_callback=card_rcv_callback, @@ -471,7 +471,7 @@ CONFIGS = [ ), ProcessConfig( proc_name="radard", - pubs=["liveTracks", "carState", "modelV2"], + pubs=["radarTracks", "carState", "modelV2"], subs=["radarState"], ignore=["logMonoTime"], init_callback=get_car_params_callback, @@ -479,7 +479,7 @@ CONFIGS = [ ), ProcessConfig( proc_name="plannerd", - pubs=["modelV2", "carControl", "carState", "controlsState", "liveParameters", "radarState", "selfdriveState"], + pubs=["modelV2", "carControl", "carState", "controlsState", "vehicleParameters", "radarState", "selfdriveState"], subs=["longitudinalPlan", "driverAssistance"], ignore=["logMonoTime", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime"], init_callback=get_car_params_callback, @@ -489,14 +489,14 @@ CONFIGS = [ ProcessConfig( proc_name="calibrationd", pubs=["carState", "cameraOdometry"], - subs=["liveCalibration"], + subs=["extrinsicsCalibration"], ignore=["logMonoTime"], init_callback=get_car_params_callback, should_recv_callback=MessageBasedRcvCallback("cameraOdometry", True), ), ProcessConfig( proc_name="dmonitoringd", - pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "selfdriveState"], + pubs=["driverStateV2", "extrinsicsCalibration", "carState", "modelV2", "selfdriveState"], subs=["driverMonitoringState"], ignore=["logMonoTime"], should_recv_callback=MessageBasedRcvCallback("driverStateV2"), @@ -505,9 +505,9 @@ CONFIGS = [ ProcessConfig( proc_name="locationd", pubs=[ - "cameraOdometry", "accelerometer", "gyroscope", "liveCalibration", "carState" + "cameraOdometry", "accelerometer", "gyroscope", "extrinsicsCalibration", "carState" ], - subs=["livePose"], + subs=["deviceMotion"], ignore=["logMonoTime"], should_recv_callback=MessageBasedRcvCallback("cameraOdometry"), tolerance=NUMPY_TOLERANCE, @@ -515,21 +515,21 @@ CONFIGS = [ ), ProcessConfig( proc_name="paramsd", - pubs=["livePose", "liveCalibration", "carState"], - subs=["liveParameters"], + pubs=["deviceMotion", "extrinsicsCalibration", "carState"], + subs=["vehicleParameters"], ignore=["logMonoTime"], init_callback=get_car_params_callback, - should_recv_callback=MessageBasedRcvCallback("livePose"), + should_recv_callback=MessageBasedRcvCallback("deviceMotion"), tolerance=NUMPY_TOLERANCE, processing_time=0.004, ), ProcessConfig( proc_name="lagd", - pubs=["livePose", "liveCalibration", "carState", "carControl", "controlsState"], - subs=["liveDelay"], + pubs=["deviceMotion", "extrinsicsCalibration", "carState", "carControl", "controlsState"], + subs=["lateralDelay"], ignore=["logMonoTime"], init_callback=get_car_params_callback, - should_recv_callback=MessageBasedRcvCallback("livePose"), + should_recv_callback=MessageBasedRcvCallback("deviceMotion"), tolerance=NUMPY_TOLERANCE, ), ProcessConfig( @@ -540,16 +540,17 @@ CONFIGS = [ ), ProcessConfig( proc_name="torqued", - pubs=["livePose", "liveCalibration", "liveDelay", "carState", "carControl", "carOutput"], - subs=["liveTorqueParameters"], + pubs=["deviceMotion", "extrinsicsCalibration", "lateralDelay", "carState", "carControl", "carOutput"], + subs=["lateralTorqueParameters"], ignore=["logMonoTime"], init_callback=get_car_params_callback, - should_recv_callback=MessageBasedRcvCallback("livePose", True), + should_recv_callback=MessageBasedRcvCallback("deviceMotion", True), tolerance=NUMPY_TOLERANCE, ), ProcessConfig( proc_name="modeld", - pubs=["deviceState", "narrowRoadCameraState", "wideRoadCameraState", "liveCalibration", "liveDelay", "driverMonitoringState", "carState", "carControl"], + pubs=["deviceState", "narrowRoadCameraState", "wideRoadCameraState", "extrinsicsCalibration", "lateralDelay", + "driverMonitoringState", "carState", "carControl"], subs=["modelV2", "drivingModelData", "cameraOdometry"], ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime", "drivingModelData.frameDropPerc", "drivingModelData.modelExecutionTime"], should_recv_callback=ModeldCameraSyncRcvCallback(), @@ -562,7 +563,7 @@ CONFIGS = [ ), ProcessConfig( proc_name="dmonitoringmodeld", - pubs=["liveCalibration", "cabinCameraState"], + pubs=["extrinsicsCalibration", "cabinCameraState"], subs=["driverStateV2"], ignore=["logMonoTime", "driverStateV2.modelExecutionTime", "driverStateV2.gpuExecutionTime"], should_recv_callback=MessageBasedRcvCallback("cabinCameraState"), @@ -586,30 +587,30 @@ def get_custom_params_from_lr(lr: LogIterable, initial_state: str = "first") -> """ Use this to get custom params dict based on provided logs. Useful when replaying following processes: calibrationd, paramsd, torqued - The params may be based on first or last message of given type (carParams, liveCalibration, liveParameters, liveTorqueParameters) in the logs. + The params may be based on first or last message of given type (carParams, extrinsicsCalibration, vehicleParameters, lateralTorqueParameters) in the logs. """ car_params = [m for m in lr if m.which() == "carParams"] - live_calibration = [m for m in lr if m.which() == "liveCalibration"] - live_parameters = [m for m in lr if m.which() == "liveParameters"] - live_torque_parameters = [m for m in lr if m.which() == "liveTorqueParameters"] + extrinsics_calibration = [m for m in lr if m.which() == "extrinsicsCalibration"] + vehicle_parameters = [m for m in lr if m.which() == "vehicleParameters"] + torque_parameters = [m for m in lr if m.which() == "lateralTorqueParameters"] assert initial_state in ["first", "last"] msg_index = 0 if initial_state == "first" else -1 - assert len(car_params) > 0, "carParams required for initial state of liveParameters and CarParamsPrevRoute" + assert len(car_params) > 0, "carParams required for initial state of vehicleParameters and CarParamsPrevRoute" CP = car_params[msg_index].carParams custom_params = { "CarParamsPrevRoute": CP.as_builder().to_bytes() } - if len(live_calibration) > 0: - custom_params["CalibrationParams"] = live_calibration[msg_index].as_builder().to_bytes() - if len(live_parameters) > 0: - custom_params["LiveParametersV2"] = live_parameters[msg_index].as_builder().to_bytes() - if len(live_torque_parameters) > 0: - custom_params["LiveTorqueParameters"] = live_torque_parameters[msg_index].as_builder().to_bytes() + if len(extrinsics_calibration) > 0: + custom_params["CalibrationParams"] = extrinsics_calibration[msg_index].as_builder().to_bytes() + if len(vehicle_parameters) > 0: + custom_params["LiveParametersV2"] = vehicle_parameters[msg_index].as_builder().to_bytes() + if len(torque_parameters) > 0: + custom_params["LiveTorqueParameters"] = torque_parameters[msg_index].as_builder().to_bytes() return custom_params diff --git a/openpilot/selfdrive/test/test_onroad.py b/openpilot/selfdrive/test/test_onroad.py index fb8cd1393f..6768dc1d98 100755 --- a/openpilot/selfdrive/test/test_onroad.py +++ b/openpilot/selfdrive/test/test_onroad.py @@ -87,8 +87,8 @@ TIMINGS = { "cabinCameraState": [2.5, 0.35], "modelV2": [2.5, 0.35], "driverStateV2": [2.5, 0.40], - "livePose": [2.5, 0.35], - "liveParameters": [2.5, 0.35], + "deviceMotion": [2.5, 0.35], + "vehicleParameters": [2.5, 0.35], "wideRoadCameraState": [1.5, 0.35], } diff --git a/openpilot/selfdrive/test/test_power_draw.py b/openpilot/selfdrive/test/test_power_draw.py index 8cad68978f..1d0be24ef0 100755 --- a/openpilot/selfdrive/test/test_power_draw.py +++ b/openpilot/selfdrive/test/test_power_draw.py @@ -95,7 +95,7 @@ class TestPowerDraw(OpenpilotTestCase): return now, msg_counts, time.monotonic() - start_time - SAMPLE_TIME - @mock_messages(['livePose']) + @mock_messages(['deviceMotion']) def test_camera_procs(self, subtests): baseline = get_power() diff --git a/openpilot/selfdrive/ui/layouts/settings/device.py b/openpilot/selfdrive/ui/layouts/settings/device.py index ddc5c23160..093fc738a0 100644 --- a/openpilot/selfdrive/ui/layouts/settings/device.py +++ b/openpilot/selfdrive/ui/layouts/settings/device.py @@ -116,9 +116,9 @@ class DeviceLayout(Widget): calib_bytes = self._params.get("CalibrationParams") if calib_bytes: try: - calib = messaging.log_from_bytes(calib_bytes, log.Event).liveCalibration + calib = messaging.log_from_bytes(calib_bytes, log.Event).extrinsicsCalibration - if calib.calStatus != log.LiveCalibrationData.Status.uncalibrated: + if calib.calStatus != log.ExtrinsicsCalibration.Status.uncalibrated: pitch = math.degrees(calib.rpyCalib[1]) yaw = math.degrees(calib.rpyCalib[2]) desc += tr(" Your device is pointed {:.1f}° {} and {:.1f}° {}.").format(abs(pitch), tr("down") if pitch > 0 else tr("up"), @@ -130,7 +130,7 @@ class DeviceLayout(Widget): lag_bytes = self._params.get("LiveDelay") if lag_bytes: try: - lag_perc = messaging.log_from_bytes(lag_bytes, log.Event).liveDelay.calPerc + lag_perc = messaging.log_from_bytes(lag_bytes, log.Event).lateralDelay.calPerc except Exception: cloudlog.exception("invalid LiveDelay") if lag_perc < 100: @@ -141,7 +141,7 @@ class DeviceLayout(Widget): torque_bytes = self._params.get("LiveTorqueParameters") if torque_bytes: try: - torque = messaging.log_from_bytes(torque_bytes, log.Event).liveTorqueParameters + torque = messaging.log_from_bytes(torque_bytes, log.Event).lateralTorqueParameters # don't add for non-torque cars if torque.useParams: torque_perc = torque.calPerc diff --git a/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py b/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py index 46348a9f16..4f4e4b2f6c 100644 --- a/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -20,7 +20,7 @@ from openpilot.common.transformations.orientation import rot_from_euler from enum import IntEnum OpState = log.SelfdriveState.OpenpilotState -CALIBRATED = log.LiveCalibrationData.Status.calibrated +CALIBRATED = log.ExtrinsicsCalibration.Status.calibrated NARROW_ROAD_CAM = VisionStreamType.VISION_STREAM_NARROW_ROAD WIDE_CAM = VisionStreamType.VISION_STREAM_WIDE_ROAD DEFAULT_DEVICE_CAMERA = DEVICE_CAMERAS["tici", "ar0231"] @@ -266,11 +266,11 @@ class AugmentedRoadView(CameraView): if not self.device_camera and sm.seen['narrowRoadCameraState'] and sm.seen['deviceState']: self.device_camera = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['narrowRoadCameraState'].sensor))] - # Check if live calibration data is available and valid - if not (sm.updated["liveCalibration"] and sm.valid['liveCalibration']): + # Check if camera calibration data is available and valid + if not (sm.updated["extrinsicsCalibration"] and sm.valid['extrinsicsCalibration']): return - calib = sm['liveCalibration'] + calib = sm['extrinsicsCalibration'] if len(calib.rpyCalib) != 3 or calib.calStatus != CALIBRATED: return @@ -285,7 +285,7 @@ class AugmentedRoadView(CameraView): def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: cache_key = ( - ui_state.sm.recv_frame['liveCalibration'], + ui_state.sm.recv_frame['extrinsicsCalibration'], int(self._content_rect.width), int(self._content_rect.height), self.stream_type, diff --git a/openpilot/selfdrive/ui/mici/onroad/model_renderer.py b/openpilot/selfdrive/ui/mici/onroad/model_renderer.py index 4d19850769..dd0d6d9734 100644 --- a/openpilot/selfdrive/ui/mici/onroad/model_renderer.py +++ b/openpilot/selfdrive/ui/mici/onroad/model_renderer.py @@ -100,7 +100,7 @@ class ModelRenderer(Widget): self._torque_filter.update(-ui_state.sm['carOutput'].actuatorsOutput.torque) # Check if data is up-to-date - if (sm.recv_frame["liveCalibration"] < ui_state.started_frame or + if (sm.recv_frame["extrinsicsCalibration"] < ui_state.started_frame or sm.recv_frame["modelV2"] < ui_state.started_frame): return @@ -112,8 +112,8 @@ class ModelRenderer(Widget): # Update state self._experimental_mode = sm['selfdriveState'].experimentalMode - live_calib = sm['liveCalibration'] - self._path_offset_z = live_calib.height[0] if live_calib.height else HEIGHT_INIT[0] + extrinsics_calibration = sm['extrinsicsCalibration'] + self._path_offset_z = extrinsics_calibration.height[0] if extrinsics_calibration.height else HEIGHT_INIT[0] if sm.updated['carParams']: self._longitudinal_control = sm['carParams'].openpilotLongitudinalControl diff --git a/openpilot/selfdrive/ui/mici/onroad/torque_bar.py b/openpilot/selfdrive/ui/mici/onroad/torque_bar.py index 6a1d12b6c4..3c0b963666 100644 --- a/openpilot/selfdrive/ui/mici/onroad/torque_bar.py +++ b/openpilot/selfdrive/ui/mici/onroad/torque_bar.py @@ -166,7 +166,7 @@ class TorqueBar(Widget): if ui_state.sm['controlsState'].lateralControlState.which() in ('angleState', 'curvatureState'): controls_state = ui_state.sm['controlsState'] car_state = ui_state.sm['carState'] - live_parameters = ui_state.sm['liveParameters'] + vehicle_parameters = ui_state.sm['vehicleParameters'] car_control = ui_state.sm['carControl'] # Include lateral accel error in estimated torque utilization @@ -176,7 +176,7 @@ class TorqueBar(Widget): # Include road roll in estimated torque utilization # Roll is less accurate near standstill, so reduce its effect at low speed - roll_compensation = live_parameters.roll * ACCELERATION_DUE_TO_GRAVITY * np.interp(car_state.vEgo, [5, 15], [0.0, 1.0]) + roll_compensation = vehicle_parameters.roll * ACCELERATION_DUE_TO_GRAVITY * np.interp(car_state.vEgo, [5, 15], [0.0, 1.0]) lateral_acceleration = actual_lateral_accel - roll_compensation max_lateral_acceleration = ui_state.CP.maxLateralAccel if ui_state.CP else DEFAULT_MAX_LAT_ACCEL diff --git a/openpilot/selfdrive/ui/onroad/augmented_road_view.py b/openpilot/selfdrive/ui/onroad/augmented_road_view.py index 44d8910a03..cae462e41d 100644 --- a/openpilot/selfdrive/ui/onroad/augmented_road_view.py +++ b/openpilot/selfdrive/ui/onroad/augmented_road_view.py @@ -14,7 +14,7 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCamera from openpilot.common.transformations.orientation import rot_from_euler OpState = log.SelfdriveState.OpenpilotState -CALIBRATED = log.LiveCalibrationData.Status.calibrated +CALIBRATED = log.ExtrinsicsCalibration.Status.calibrated NARROW_ROAD_CAM = VisionStreamType.VISION_STREAM_NARROW_ROAD WIDE_CAM = VisionStreamType.VISION_STREAM_WIDE_ROAD DEFAULT_DEVICE_CAMERA = DEVICE_CAMERAS["tici", "ar0231"] @@ -131,11 +131,11 @@ class AugmentedRoadView(CameraView): if not self.device_camera and sm.seen['narrowRoadCameraState'] and sm.seen['deviceState']: self.device_camera = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['narrowRoadCameraState'].sensor))] - # Check if live calibration data is available and valid - if not (sm.updated["liveCalibration"] and sm.valid['liveCalibration']): + # Check if camera calibration data is available and valid + if not (sm.updated["extrinsicsCalibration"] and sm.valid['extrinsicsCalibration']): return - calib = sm['liveCalibration'] + calib = sm['extrinsicsCalibration'] if len(calib.rpyCalib) != 3 or calib.calStatus != CALIBRATED: return @@ -151,7 +151,7 @@ class AugmentedRoadView(CameraView): def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: # Check if we can use cached matrix cache_key = ( - ui_state.sm.recv_frame['liveCalibration'], + ui_state.sm.recv_frame['extrinsicsCalibration'], self._content_rect.width, self._content_rect.height, self.stream_type diff --git a/openpilot/selfdrive/ui/onroad/model_renderer.py b/openpilot/selfdrive/ui/onroad/model_renderer.py index 8a40c90025..18fe45d8e1 100644 --- a/openpilot/selfdrive/ui/onroad/model_renderer.py +++ b/openpilot/selfdrive/ui/onroad/model_renderer.py @@ -85,7 +85,7 @@ class ModelRenderer(Widget): sm = ui_state.sm # Check if data is up-to-date - if (sm.recv_frame["liveCalibration"] < ui_state.started_frame or + if (sm.recv_frame["extrinsicsCalibration"] < ui_state.started_frame or sm.recv_frame["modelV2"] < ui_state.started_frame): return @@ -97,8 +97,8 @@ class ModelRenderer(Widget): # Update state self._experimental_mode = sm['selfdriveState'].experimentalMode - live_calib = sm['liveCalibration'] - self._path_offset_z = live_calib.height[0] if live_calib.height else HEIGHT_INIT[0] + extrinsics_calibration = sm['extrinsicsCalibration'] + self._path_offset_z = extrinsics_calibration.height[0] if extrinsics_calibration.height else HEIGHT_INIT[0] if sm.updated['carParams']: self._longitudinal_control = sm['carParams'].openpilotLongitudinalControl diff --git a/openpilot/selfdrive/ui/tests/diff/replay_script.py b/openpilot/selfdrive/ui/tests/diff/replay_script.py index 30e811dbc9..962d409344 100644 --- a/openpilot/selfdrive/ui/tests/diff/replay_script.py +++ b/openpilot/selfdrive/ui/tests/diff/replay_script.py @@ -144,19 +144,19 @@ def set_updater_state(state: str) -> None: def setup_calibration_params() -> None: params = Params() - # live calibration - calib = messaging.new_message('liveCalibration') - calib.liveCalibration.calStatus = log.LiveCalibrationData.Status.calibrated - calib.liveCalibration.rpyCalib = [0.0, math.radians(2.5), math.radians(-1.2)] + # camera calibration + calib = messaging.new_message('extrinsicsCalibration') + calib.extrinsicsCalibration.calStatus = log.ExtrinsicsCalibration.Status.calibrated + calib.extrinsicsCalibration.rpyCalib = [0.0, math.radians(2.5), math.radians(-1.2)] params.put("CalibrationParams", calib.to_bytes(), block=True) - # live delay - delay = messaging.new_message('liveDelay') - delay.liveDelay.calPerc = 75 + # lateral delay + delay = messaging.new_message('lateralDelay') + delay.lateralDelay.calPerc = 75 params.put("LiveDelay", delay.to_bytes(), block=True) - # live torque parameters - torque = messaging.new_message('liveTorqueParameters') - torque.liveTorqueParameters.useParams = True - torque.liveTorqueParameters.calPerc = 60 + # lateral torque parameters + torque = messaging.new_message('lateralTorqueParameters') + torque.lateralTorqueParameters.useParams = True + torque.lateralTorqueParameters.calPerc = 60 params.put("LiveTorqueParameters", torque.to_bytes(), block=True) diff --git a/openpilot/selfdrive/ui/ui_state.py b/openpilot/selfdrive/ui/ui_state.py index 943ba46014..42e8086351 100644 --- a/openpilot/selfdrive/ui/ui_state.py +++ b/openpilot/selfdrive/ui/ui_state.py @@ -40,7 +40,7 @@ class UIState: "modelV2", "controlsState", "onroadEvents", - "liveCalibration", + "extrinsicsCalibration", "radarState", "deviceState", "pandaStates", @@ -56,7 +56,7 @@ class UIState: "gpsLocationExternal", "carOutput", "carControl", - "liveParameters", + "vehicleParameters", "testJoystick", "rawAudioData", ] diff --git a/openpilot/tools/jotpluggler/layouts/locationd_debug.json b/openpilot/tools/jotpluggler/layouts/locationd_debug.json index 0541427bc1..53112f31d6 100644 --- a/openpilot/tools/jotpluggler/layouts/locationd_debug.json +++ b/openpilot/tools/jotpluggler/layouts/locationd_debug.json @@ -1 +1 @@ -{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.166588,0.167062,0.166113,0.166588,0.167062,0.166588],"children":[{"title":"...","range":{"left":0.0,"right":2280.128382,"top":1.025,"bottom":-0.025},"curves":[{"name":"/livePose/inputsOK","color":"#ff7f0e"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":14.542814,"bottom":-5.586039},"curves":[{"name":"/accelerometer/acceleration/v/0","color":"#f14cc1"},{"name":"/accelerometer/acceleration/v/1","color":"#9467bd"},{"name":"/accelerometer/acceleration/v/2","color":"#17becf"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":0.988911,"bottom":-0.745939},"curves":[{"name":"/gyroscope/gyroUncalibrated/v/0","color":"#d62728"},{"name":"/gyroscope/gyroUncalibrated/v/1","color":"#1ac938"},{"name":"/gyroscope/gyroUncalibrated/v/2","color":"#ff7f0e"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":1.025,"bottom":-0.025},"curves":[{"name":"/accelerometer/__valid","color":"#17becf"},{"name":"/gyroscope/__valid","color":"#bcbd22"},{"name":"/carState/__valid","color":"#f14cc1"},{"name":"/liveCalibration/__valid","color":"#1ac938"},{"name":"/cameraOdometry/__valid","color":"#9467bd"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":1000000000.292252,"bottom":999999999.735447},"curves":[{"name":"/gyroscope/__logMonoTime","color":"#1f77b4","transform":"derivative"},{"name":"/accelerometer/__logMonoTime","color":"#d62728","transform":"derivative"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":20790107743.93223,"bottom":-529653831.495853},"curves":[{"name":"/accelerometer/timestamp","color":"#bcbd22","transform":"derivative"},{"name":"/gyroscope/timestamp","color":"#1f77b4","transform":"derivative"}]}]}}]} +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.166588,0.167062,0.166113,0.166588,0.167062,0.166588],"children":[{"title":"...","range":{"left":0.0,"right":2280.128382,"top":1.025,"bottom":-0.025},"curves":[{"name":"/deviceMotion/inputsOK","color":"#ff7f0e"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":14.542814,"bottom":-5.586039},"curves":[{"name":"/accelerometer/acceleration/v/0","color":"#f14cc1"},{"name":"/accelerometer/acceleration/v/1","color":"#9467bd"},{"name":"/accelerometer/acceleration/v/2","color":"#17becf"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":0.988911,"bottom":-0.745939},"curves":[{"name":"/gyroscope/gyroUncalibrated/v/0","color":"#d62728"},{"name":"/gyroscope/gyroUncalibrated/v/1","color":"#1ac938"},{"name":"/gyroscope/gyroUncalibrated/v/2","color":"#ff7f0e"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":1.025,"bottom":-0.025},"curves":[{"name":"/accelerometer/__valid","color":"#17becf"},{"name":"/gyroscope/__valid","color":"#bcbd22"},{"name":"/carState/__valid","color":"#f14cc1"},{"name":"/extrinsicsCalibration/__valid","color":"#1ac938"},{"name":"/cameraOdometry/__valid","color":"#9467bd"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":1000000000.292252,"bottom":999999999.735447},"curves":[{"name":"/gyroscope/__logMonoTime","color":"#1f77b4","transform":"derivative"},{"name":"/accelerometer/__logMonoTime","color":"#d62728","transform":"derivative"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":20790107743.93223,"bottom":-529653831.495853},"curves":[{"name":"/accelerometer/timestamp","color":"#bcbd22","transform":"derivative"},{"name":"/gyroscope/timestamp","color":"#1f77b4","transform":"derivative"}]}]}}]} diff --git a/openpilot/tools/jotpluggler/layouts/max-torque-debug.json b/openpilot/tools/jotpluggler/layouts/max-torque-debug.json index 3a87fb3217..b587b695a9 100644 --- a/openpilot/tools/jotpluggler/layouts/max-torque-debug.json +++ b/openpilot/tools/jotpluggler/layouts/max-torque-debug.json @@ -1 +1 @@ -{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.249724,0.250829,0.249724,0.249724],"children":[{"title":"...","range":{"left":0.00045,"right":2483.624998,"top":6.050533,"bottom":-7.599037},"curves":[{"name":"Actual lateral accel (roll compensated)","color":"#1ac938","custom_python":{"linked_source":"/controlsState/curvature","additional_sources":["/carState/vEgo","/liveParameters/roll"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n return (value * v1 ** 2) - (v2 * 9.81)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"Desired lateral accel (roll compensated)","color":"#ff7f0e","custom_python":{"linked_source":"/controlsState/desiredCurvature","additional_sources":["/carState/vEgo","/liveParameters/roll"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n return (value * v1 ** 2) - (v2 * 9.81)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"...","range":{"left":0.00045,"right":2483.624998,"top":5.384416,"bottom":-7.503945},"curves":[{"name":"roll compensated lateral acceleration","color":"#1ac938","custom_python":{"linked_source":"/controlsState/curvature","additional_sources":["/carState/vEgo","/liveParameters/roll","/carState/steeringPressed","/carControl/latActive"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2, v3, v4):\n if (v3 == 0 and v4 == 1):\n return (value * v1 ** 2) - (v2 * 9.81)\n return 0\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i], v3[__jotpluggler_i], v4[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"...","range":{"left":0.00045,"right":2483.624998,"top":1.05,"bottom":-1.05},"curves":[{"name":"/carState/steeringPressed","color":"#0097ff"},{"name":"/carOutput/actuatorsOutput/torque","color":"#d62728"}]},{"title":"...","range":{"left":0.00045,"right":2483.624998,"top":80.762969,"bottom":-2.181837},"curves":[{"name":"/carState/vEgo","color":"#f14cc1","transform":"scale","scale":2.23694,"offset":0.0}]}]}}]} +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.249724,0.250829,0.249724,0.249724],"children":[{"title":"...","range":{"left":0.00045,"right":2483.624998,"top":6.050533,"bottom":-7.599037},"curves":[{"name":"Actual lateral accel (roll compensated)","color":"#1ac938","custom_python":{"linked_source":"/controlsState/curvature","additional_sources":["/carState/vEgo","/vehicleParameters/roll"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n return (value * v1 ** 2) - (v2 * 9.81)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"Desired lateral accel (roll compensated)","color":"#ff7f0e","custom_python":{"linked_source":"/controlsState/desiredCurvature","additional_sources":["/carState/vEgo","/vehicleParameters/roll"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n return (value * v1 ** 2) - (v2 * 9.81)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"...","range":{"left":0.00045,"right":2483.624998,"top":5.384416,"bottom":-7.503945},"curves":[{"name":"roll compensated lateral acceleration","color":"#1ac938","custom_python":{"linked_source":"/controlsState/curvature","additional_sources":["/carState/vEgo","/vehicleParameters/roll","/carState/steeringPressed","/carControl/latActive"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2, v3, v4):\n if (v3 == 0 and v4 == 1):\n return (value * v1 ** 2) - (v2 * 9.81)\n return 0\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i], v3[__jotpluggler_i], v4[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"...","range":{"left":0.00045,"right":2483.624998,"top":1.05,"bottom":-1.05},"curves":[{"name":"/carState/steeringPressed","color":"#0097ff"},{"name":"/carOutput/actuatorsOutput/torque","color":"#d62728"}]},{"title":"...","range":{"left":0.00045,"right":2483.624998,"top":80.762969,"bottom":-2.181837},"curves":[{"name":"/carState/vEgo","color":"#f14cc1","transform":"scale","scale":2.23694,"offset":0.0}]}]}}]} diff --git a/openpilot/tools/jotpluggler/layouts/torque-controller.json b/openpilot/tools/jotpluggler/layouts/torque-controller.json index 7e269e59e6..a794c725d6 100644 --- a/openpilot/tools/jotpluggler/layouts/torque-controller.json +++ b/openpilot/tools/jotpluggler/layouts/torque-controller.json @@ -1 +1 @@ -{"current_tab_index":0,"tabs":[{"name":"Lateral Plan Conformance","root":{"split":"vertical","sizes":[0.250949,0.249051,0.250949,0.249051],"children":[{"title":"desired vs actual lateral acceleration (closer means better conformance to plan)","range":{"left":0.000194,"right":1138.891674,"top":1.858161,"bottom":-1.823407},"curves":[{"name":"/controlsState/lateralControlState/torqueState/actualLateralAccel","color":"#1f77b4"},{"name":"/controlsState/lateralControlState/torqueState/desiredLateralAccel","color":"#d62728"}]},{"title":"desired vs actual lateral acceleration, road-roll factored out (closer means better conformance to plan)","range":{"left":0.000194,"right":1138.891674,"top":2.749816,"bottom":-3.723091},"curves":[{"name":"Actual lateral accel (roll compensated)","color":"#1ac938","custom_python":{"linked_source":"/controlsState/curvature","additional_sources":["/carState/vEgo","/liveParameters/roll"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n return (value * v1 ** 2) - (v2 * 9.81)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"Desired lateral accel (roll compensated)","color":"#ff7f0e","custom_python":{"linked_source":"/controlsState/desiredCurvature","additional_sources":["/carState/vEgo","/liveParameters/roll"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n return (value * v1 ** 2) - (v2 * 9.81)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"controller feed-forward vs actuator output (closer means controller prediction is more accurate)","range":{"left":0.000194,"right":1138.891674,"top":1.978032,"bottom":-1.570956},"curves":[{"name":"/carOutput/actuatorsOutput/torque","color":"#9467bd","transform":"scale","scale":-1.0,"offset":0.0},{"name":"/controlsState/lateralControlState/torqueState/f","color":"#1f77b4"},{"name":"/carState/steeringPressed","color":"#ff000f"}]},{"title":"vehicle speed","range":{"left":0.000194,"right":1138.891674,"top":105.981304,"bottom":-2.709314},"curves":[{"name":"carState.vEgo mph","color":"#d62728","custom_python":{"linked_source":"/carState/vEgo","additional_sources":[],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value):\n return value * 2.23694\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"carState.vEgo kmh","color":"#1ac938","custom_python":{"linked_source":"/carState/vEgo","additional_sources":[],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value):\n return value * 3.6\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"/carState/vEgo","color":"#ff7f0e"}]}]}},{"name":"Vehicle Dynamics","root":{"split":"vertical","sizes":[0.334282,0.331437,0.334282],"children":[{"title":"configured-initial vs online-learned steerRatio, set configured value to match learned","range":{"left":0.0,"right":1138.816328,"top":19.665784,"bottom":19.359553},"curves":[{"name":"/carParams/steerRatio","color":"#1f77b4"},{"name":"/liveParameters/steerRatio","color":"#1ac938"}]},{"title":"configured-initial vs online-learned tireStiffnessRatio, set configured value to match learned","range":{"left":0.0,"right":1138.816328,"top":1.11221,"bottom":0.995631},"curves":[{"name":"/carParams/tireStiffnessFactor","color":"#d62728"},{"name":"/liveParameters/stiffnessFactor","color":"#ff7f0e"}]},{"title":"live steering angle offsets for straight-ahead driving, large values here may indicate alignment problems","range":{"left":0.0,"right":1138.816328,"top":-1.081041,"bottom":-4.494133},"curves":[{"name":"/liveParameters/angleOffsetAverageDeg","color":"#f14cc1"},{"name":"/liveParameters/angleOffsetDeg","color":"#9467bd"}]}]}},{"name":"Actuator Performance","root":{"split":"vertical","sizes":[0.333333,0.333333,0.333333],"children":[{"title":"offline-calculated vs online-learned lateral accel scaling factor, accel obtained from 100% actuator output","range":{"left":0.0,"right":1138.920072,"top":1.21611,"bottom":0.539474},"curves":[{"name":"/liveTorqueParameters/latAccelFactorFiltered","color":"#1f77b4"},{"name":"/liveTorqueParameters/latAccelFactorRaw","color":"#d62728"},{"name":"/carParams/lateralTuning/torque/latAccelFactor","color":"#1c9222"}]},{"title":"learned lateral accel offset, vehicle-specific compensation to obtain true zero lateral accel","range":{"left":0.0,"right":1138.920072,"top":-0.304367,"bottom":-0.418688},"curves":[{"name":"/liveTorqueParameters/latAccelOffsetFiltered","color":"#1ac938"},{"name":"/liveTorqueParameters/latAccelOffsetRaw","color":"#ff7f0e"}]},{"title":"offline-calculated vs online-learned EPS friction factor, necessary to start moving the steering wheel","range":{"left":0.0,"right":1138.920072,"top":0.226389,"bottom":0.15805},"curves":[{"name":"/liveTorqueParameters/frictionCoefficientFiltered","color":"#f14cc1"},{"name":"/liveTorqueParameters/frictionCoefficientRaw","color":"#9467bd"},{"name":"/carParams/lateralTuning/torque/friction","color":"#1c9222"}]}]}},{"name":"Actuator Delay","root":{"split":"vertical","sizes":[0.30441,0.358464,0.337127],"children":[{"title":"actuator lag learning state, 0 = learning, 1 = learned/applying, 2 = invalid","range":{"left":0.0,"right":1138.749979,"top":1.025,"bottom":-0.025},"curves":[{"name":"/liveDelay/status","color":"#ff7f0e"}]},{"title":"offline default vs online estimated steering actuator lag","range":{"left":0.0,"right":1138.749979,"top":0.419648,"bottom":0.318362},"curves":[{"name":"/liveDelay/lateralDelay","color":"#1f77b4"},{"name":"/liveDelay/lateralDelayEstimate","color":"#d62728"},{"name":"opendbc default steering lag","color":"#1ac938","custom_python":{"linked_source":"/carParams/steerActuatorDelay","additional_sources":[],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value):\n return value + 0.2\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"online estimated steering actuator lag, standard deviation","range":{"left":0.0,"right":1138.749979,"top":0.06732,"bottom":-0.001642},"curves":[{"name":"/liveDelay/lateralDelayEstimateStd","color":"#f14cc1"}]}]}},{"name":"Controls Performance","root":{"split":"vertical","sizes":[0.265655,0.251898,0.245731,0.236717],"children":[{"title":"rate-of-change limits on steering actuator (blue = original, green = rate-limited before CAN output)","range":{"left":0.000194,"right":1138.891921,"top":1.05,"bottom":-1.05},"curves":[{"name":"/carControl/actuators/torque","color":"#0c00f2"},{"name":"/carOutput/actuatorsOutput/torque","color":"#2cd63a"}]},{"title":"controller feed-forward vs actuator output (closer means controller prediction is more accurate)","range":{"left":0.000194,"right":1138.891921,"top":1.978032,"bottom":-1.570956},"curves":[{"name":"/carOutput/actuatorsOutput/torque","color":"#9467bd","transform":"scale","scale":-1.0,"offset":0.0},{"name":"/controlsState/lateralControlState/torqueState/f","color":"#1f77b4"},{"name":"/carState/steeringPressed","color":"#ff000f"}]},{"title":"proportional, integral, and feed-forward terms (actuator output = sum of PIF terms)","range":{"left":0.000194,"right":1138.891921,"top":2.099784,"bottom":-4.027542},"curves":[{"name":"/controlsState/lateralControlState/torqueState/f","color":"#0ab027"},{"name":"/controlsState/lateralControlState/torqueState/p","color":"#d62728"},{"name":"/controlsState/lateralControlState/torqueState/i","color":"#ffaf00"},{"name":"Zero","color":"#756a6a","custom_python":{"linked_source":"/carState/canValid","additional_sources":[],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value):\n return (0)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"road roll angle, from openpilot localizer","range":{"left":0.000194,"right":1138.891921,"top":0.109446,"bottom":-0.045525},"curves":[{"name":"/liveParameters/roll","color":"#f14cc1"}]}]}}]} +{"current_tab_index":0,"tabs":[{"name":"Lateral Plan Conformance","root":{"split":"vertical","sizes":[0.250949,0.249051,0.250949,0.249051],"children":[{"title":"desired vs actual lateral acceleration (closer means better conformance to plan)","range":{"left":0.000194,"right":1138.891674,"top":1.858161,"bottom":-1.823407},"curves":[{"name":"/controlsState/lateralControlState/torqueState/actualLateralAccel","color":"#1f77b4"},{"name":"/controlsState/lateralControlState/torqueState/desiredLateralAccel","color":"#d62728"}]},{"title":"desired vs actual lateral acceleration, road-roll factored out (closer means better conformance to plan)","range":{"left":0.000194,"right":1138.891674,"top":2.749816,"bottom":-3.723091},"curves":[{"name":"Actual lateral accel (roll compensated)","color":"#1ac938","custom_python":{"linked_source":"/controlsState/curvature","additional_sources":["/carState/vEgo","/vehicleParameters/roll"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n return (value * v1 ** 2) - (v2 * 9.81)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"Desired lateral accel (roll compensated)","color":"#ff7f0e","custom_python":{"linked_source":"/controlsState/desiredCurvature","additional_sources":["/carState/vEgo","/vehicleParameters/roll"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n return (value * v1 ** 2) - (v2 * 9.81)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"controller feed-forward vs actuator output (closer means controller prediction is more accurate)","range":{"left":0.000194,"right":1138.891674,"top":1.978032,"bottom":-1.570956},"curves":[{"name":"/carOutput/actuatorsOutput/torque","color":"#9467bd","transform":"scale","scale":-1.0,"offset":0.0},{"name":"/controlsState/lateralControlState/torqueState/f","color":"#1f77b4"},{"name":"/carState/steeringPressed","color":"#ff000f"}]},{"title":"vehicle speed","range":{"left":0.000194,"right":1138.891674,"top":105.981304,"bottom":-2.709314},"curves":[{"name":"carState.vEgo mph","color":"#d62728","custom_python":{"linked_source":"/carState/vEgo","additional_sources":[],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value):\n return value * 2.23694\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"carState.vEgo kmh","color":"#1ac938","custom_python":{"linked_source":"/carState/vEgo","additional_sources":[],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value):\n return value * 3.6\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"/carState/vEgo","color":"#ff7f0e"}]}]}},{"name":"Vehicle Dynamics","root":{"split":"vertical","sizes":[0.334282,0.331437,0.334282],"children":[{"title":"configured-initial vs online-learned steerRatio, set configured value to match learned","range":{"left":0.0,"right":1138.816328,"top":19.665784,"bottom":19.359553},"curves":[{"name":"/carParams/steerRatio","color":"#1f77b4"},{"name":"/vehicleParameters/steerRatio","color":"#1ac938"}]},{"title":"configured-initial vs online-learned tireStiffnessRatio, set configured value to match learned","range":{"left":0.0,"right":1138.816328,"top":1.11221,"bottom":0.995631},"curves":[{"name":"/carParams/tireStiffnessFactor","color":"#d62728"},{"name":"/vehicleParameters/stiffnessFactor","color":"#ff7f0e"}]},{"title":"online-learned steering angle offsets for straight-ahead driving, large values here may indicate alignment problems","range":{"left":0.0,"right":1138.816328,"top":-1.081041,"bottom":-4.494133},"curves":[{"name":"/vehicleParameters/angleOffsetAverageDeg","color":"#f14cc1"},{"name":"/vehicleParameters/angleOffsetDeg","color":"#9467bd"}]}]}},{"name":"Actuator Performance","root":{"split":"vertical","sizes":[0.333333,0.333333,0.333333],"children":[{"title":"offline-calculated vs online-learned lateral accel scaling factor, accel obtained from 100% actuator output","range":{"left":0.0,"right":1138.920072,"top":1.21611,"bottom":0.539474},"curves":[{"name":"/lateralTorqueParameters/latAccelFactorFiltered","color":"#1f77b4"},{"name":"/lateralTorqueParameters/latAccelFactorRaw","color":"#d62728"},{"name":"/carParams/lateralTuning/torque/latAccelFactor","color":"#1c9222"}]},{"title":"learned lateral accel offset, vehicle-specific compensation to obtain true zero lateral accel","range":{"left":0.0,"right":1138.920072,"top":-0.304367,"bottom":-0.418688},"curves":[{"name":"/lateralTorqueParameters/latAccelOffsetFiltered","color":"#1ac938"},{"name":"/lateralTorqueParameters/latAccelOffsetRaw","color":"#ff7f0e"}]},{"title":"offline-calculated vs online-learned EPS friction factor, necessary to start moving the steering wheel","range":{"left":0.0,"right":1138.920072,"top":0.226389,"bottom":0.15805},"curves":[{"name":"/lateralTorqueParameters/frictionCoefficientFiltered","color":"#f14cc1"},{"name":"/lateralTorqueParameters/frictionCoefficientRaw","color":"#9467bd"},{"name":"/carParams/lateralTuning/torque/friction","color":"#1c9222"}]}]}},{"name":"Actuator Delay","root":{"split":"vertical","sizes":[0.30441,0.358464,0.337127],"children":[{"title":"actuator lag learning state, 0 = learning, 1 = learned/applying, 2 = invalid","range":{"left":0.0,"right":1138.749979,"top":1.025,"bottom":-0.025},"curves":[{"name":"/lateralDelay/status","color":"#ff7f0e"}]},{"title":"offline default vs online estimated steering actuator lag","range":{"left":0.0,"right":1138.749979,"top":0.419648,"bottom":0.318362},"curves":[{"name":"/lateralDelay/lateralDelay","color":"#1f77b4"},{"name":"/lateralDelay/lateralDelayEstimate","color":"#d62728"},{"name":"opendbc default steering lag","color":"#1ac938","custom_python":{"linked_source":"/carParams/steerActuatorDelay","additional_sources":[],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value):\n return value + 0.2\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"online estimated steering actuator lag, standard deviation","range":{"left":0.0,"right":1138.749979,"top":0.06732,"bottom":-0.001642},"curves":[{"name":"/lateralDelay/lateralDelayEstimateStd","color":"#f14cc1"}]}]}},{"name":"Controls Performance","root":{"split":"vertical","sizes":[0.265655,0.251898,0.245731,0.236717],"children":[{"title":"rate-of-change limits on steering actuator (blue = original, green = rate-limited before CAN output)","range":{"left":0.000194,"right":1138.891921,"top":1.05,"bottom":-1.05},"curves":[{"name":"/carControl/actuators/torque","color":"#0c00f2"},{"name":"/carOutput/actuatorsOutput/torque","color":"#2cd63a"}]},{"title":"controller feed-forward vs actuator output (closer means controller prediction is more accurate)","range":{"left":0.000194,"right":1138.891921,"top":1.978032,"bottom":-1.570956},"curves":[{"name":"/carOutput/actuatorsOutput/torque","color":"#9467bd","transform":"scale","scale":-1.0,"offset":0.0},{"name":"/controlsState/lateralControlState/torqueState/f","color":"#1f77b4"},{"name":"/carState/steeringPressed","color":"#ff000f"}]},{"title":"proportional, integral, and feed-forward terms (actuator output = sum of PIF terms)","range":{"left":0.000194,"right":1138.891921,"top":2.099784,"bottom":-4.027542},"curves":[{"name":"/controlsState/lateralControlState/torqueState/f","color":"#0ab027"},{"name":"/controlsState/lateralControlState/torqueState/p","color":"#d62728"},{"name":"/controlsState/lateralControlState/torqueState/i","color":"#ffaf00"},{"name":"Zero","color":"#756a6a","custom_python":{"linked_source":"/carState/canValid","additional_sources":[],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value):\n return (0)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"road roll angle, from openpilot localizer","range":{"left":0.000194,"right":1138.891921,"top":0.109446,"bottom":-0.045525},"curves":[{"name":"/vehicleParameters/roll","color":"#f14cc1"}]}]}}]} diff --git a/openpilot/tools/joystick/joystickd.py b/openpilot/tools/joystick/joystickd.py index 2b84683863..f8a2598361 100755 --- a/openpilot/tools/joystick/joystickd.py +++ b/openpilot/tools/joystick/joystickd.py @@ -21,7 +21,7 @@ def joystickd_thread(): CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) VM = VehicleModel(CP) - sm = messaging.SubMaster(['carState', 'onroadEvents', 'liveParameters', 'selfdriveState', 'testJoystick'], frequency=1. / DT_CTRL) + sm = messaging.SubMaster(['carState', 'onroadEvents', 'vehicleParameters', 'selfdriveState', 'testJoystick'], frequency=1. / DT_CTRL) pm = messaging.PubMaster(['carControl', 'controlsState']) rk = Ratekeeper(100, print_delay_threshold=None) @@ -55,7 +55,7 @@ def joystickd_thread(): if CC.latActive: max_curvature = MAX_LAT_ACCEL / max(sm['carState'].vEgo ** 2, 5) - max_angle = math.degrees(VM.get_steer_from_curvature(max_curvature, sm['carState'].vEgo, sm['liveParameters'].roll)) + max_angle = math.degrees(VM.get_steer_from_curvature(max_curvature, sm['carState'].vEgo, sm['vehicleParameters'].roll)) actuators.torque = float(np.clip(joystick_axes[1], -1, 1)) actuators.steeringAngleDeg, actuators.curvature = actuators.torque * max_angle, actuators.torque * -max_curvature @@ -67,7 +67,7 @@ def joystickd_thread(): controlsState = cs_msg.controlsState controlsState.lateralControlState.init('debugState') - lp = sm['liveParameters'] + lp = sm['vehicleParameters'] steer_angle_without_offset = math.radians(sm['carState'].steeringAngleDeg - lp.angleOffsetDeg) controlsState.curvature = -VM.calc_curvature(steer_angle_without_offset, sm['carState'].vEgo, lp.roll) diff --git a/openpilot/tools/longitudinal_maneuvers/generate_report.py b/openpilot/tools/longitudinal_maneuvers/generate_report.py index dbd9f6db91..2d7f81a7f8 100755 --- a/openpilot/tools/longitudinal_maneuvers/generate_report.py +++ b/openpilot/tools/longitudinal_maneuvers/generate_report.py @@ -44,14 +44,14 @@ def report(platform, route, _description, CP, ID, maneuvers): t_carControl, carControl = zip(*[(m.logMonoTime, m.carControl) for m in msgs if m.which() == 'carControl'], strict=True) t_carOutput, carOutput = zip(*[(m.logMonoTime, m.carOutput) for m in msgs if m.which() == 'carOutput'], strict=True) t_carState, carState = zip(*[(m.logMonoTime, m.carState) for m in msgs if m.which() == 'carState'], strict=True) - t_livePose, livePose = zip(*[(m.logMonoTime, m.livePose) for m in msgs if m.which() == 'livePose'], strict=True) + t_deviceMotion, deviceMotion = zip(*[(m.logMonoTime, m.deviceMotion) for m in msgs if m.which() == 'deviceMotion'], strict=True) t_longitudinalPlan, longitudinalPlan = zip(*[(m.logMonoTime, m.longitudinalPlan) for m in msgs if m.which() == 'longitudinalPlan'], strict=True) # make time relative seconds t_carControl = [(t - t_carControl[0]) / 1e9 for t in t_carControl] t_carOutput = [(t - t_carOutput[0]) / 1e9 for t in t_carOutput] t_carState = [(t - t_carState[0]) / 1e9 for t in t_carState] - t_livePose = [(t - t_livePose[0]) / 1e9 for t in t_livePose] + t_deviceMotion = [(t - t_deviceMotion[0]) / 1e9 for t in t_deviceMotion] t_longitudinalPlan = [(t - t_longitudinalPlan[0]) / 1e9 for t in t_longitudinalPlan] # maneuver validity @@ -70,7 +70,7 @@ def report(platform, route, _description, CP, ID, maneuvers): # Localizer is noisy, require two consecutive 20Hz frames above threshold prev_crossed = False - for t, lp in zip(t_livePose, livePose, strict=True): + for t, lp in zip(t_deviceMotion, deviceMotion, strict=True): crossed = (0 < aTarget < lp.accelerationDevice.x) or (0 > aTarget > lp.accelerationDevice.x) if crossed and prev_crossed: builder.append(f', crossed in {t:.3f}s') @@ -95,7 +95,7 @@ def report(platform, route, _description, CP, ID, maneuvers): ax[0].plot(t_carOutput, [m.actuatorsOutput.accel for m in carOutput], label='carOutput.actuatorsOutput.accel', linewidth=6) ax[0].plot(t_longitudinalPlan, [m.aTarget for m in longitudinalPlan], label='longitudinalPlan.aTarget', linewidth=6) ax[0].plot(t_carState, [m.aEgo for m in carState], label='carState.aEgo', linewidth=6) - ax[0].plot(t_livePose, [m.accelerationDevice.x for m in livePose], label='livePose.accelerationDevice.x', linewidth=6) + ax[0].plot(t_deviceMotion, [m.accelerationDevice.x for m in deviceMotion], label='deviceMotion.accelerationDevice.x', linewidth=6) # TODO localizer accel ax[0].set_ylabel('Acceleration (m/s^2)') #ax[0].set_ylim(-6.5, 6.5) diff --git a/openpilot/tools/plotjuggler/layouts/locationd_debug.xml b/openpilot/tools/plotjuggler/layouts/locationd_debug.xml index 5377e1535c..6e1cd35039 100644 --- a/openpilot/tools/plotjuggler/layouts/locationd_debug.xml +++ b/openpilot/tools/plotjuggler/layouts/locationd_debug.xml @@ -8,7 +8,7 @@ - + @@ -36,7 +36,7 @@ - + @@ -97,4 +97,3 @@ - diff --git a/openpilot/tools/plotjuggler/layouts/max-torque-debug.xml b/openpilot/tools/plotjuggler/layouts/max-torque-debug.xml index 9a6693165e..2089b826d9 100644 --- a/openpilot/tools/plotjuggler/layouts/max-torque-debug.xml +++ b/openpilot/tools/plotjuggler/layouts/max-torque-debug.xml @@ -62,7 +62,7 @@ return 0 /controlsState/curvature /carState/vEgo - /liveParameters/roll + /vehicleParameters/roll /carState/steeringPressed /carControl/latActive @@ -73,7 +73,7 @@ return 0 /controlsState/desiredCurvature /carState/vEgo - /liveParameters/roll + /vehicleParameters/roll @@ -82,11 +82,10 @@ return 0 /controlsState/curvature /carState/vEgo - /liveParameters/roll + /vehicleParameters/roll - diff --git a/openpilot/tools/plotjuggler/layouts/torque-controller.xml b/openpilot/tools/plotjuggler/layouts/torque-controller.xml index 8e9a1a8526..671b47c355 100644 --- a/openpilot/tools/plotjuggler/layouts/torque-controller.xml +++ b/openpilot/tools/plotjuggler/layouts/torque-controller.xml @@ -53,7 +53,7 @@ - + @@ -61,15 +61,15 @@ - + - + - - + + @@ -82,8 +82,8 @@ - - + + @@ -91,16 +91,16 @@ - - + + - - + + @@ -114,15 +114,15 @@ - + - - + + @@ -130,7 +130,7 @@ - + @@ -174,7 +174,7 @@ - + @@ -221,7 +221,7 @@ /controlsState/desiredCurvature /carState/vEgo - /liveParameters/roll + /vehicleParameters/roll @@ -230,7 +230,7 @@ /controlsState/curvature /carState/vEgo - /liveParameters/roll + /vehicleParameters/roll @@ -247,4 +247,3 @@ - diff --git a/openpilot/tools/replay/consoleui.cc b/openpilot/tools/replay/consoleui.cc index 2d21b4efc0..aa58cc959d 100644 --- a/openpilot/tools/replay/consoleui.cc +++ b/openpilot/tools/replay/consoleui.cc @@ -60,7 +60,7 @@ ExitHandler do_exit; } // namespace -ConsoleUI::ConsoleUI(Replay *replay) : replay(replay), sm({"carState", "liveParameters"}) { +ConsoleUI::ConsoleUI(Replay *replay) : replay(replay), sm({"carState", "vehicleParameters"}) { // Initialize curses initscr(); clear(); @@ -174,7 +174,7 @@ void ConsoleUI::updateStatus() { std::string current_segment = " - " + std::to_string((int)(replay->currentSeconds() / 60)); write_item(0, 25, "TIME: ", time_string, current_segment, true); - auto p = sm["liveParameters"].getLiveParameters(); + auto p = sm["vehicleParameters"].getVehicleParameters(); write_item(1, 0, "STIFFNESS: ", util::string_format("%.2f %%", p.getStiffnessFactor() * 100), " "); write_item(1, 25, "SPEED: ", util::string_format("%.2f", sm["carState"].getCarState().getVEgo()), " m/s"); write_item(2, 0, "STEER RATIO: ", util::string_format("%.2f", p.getSteerRatio()), ""); diff --git a/openpilot/tools/replay/ui.py b/openpilot/tools/replay/ui.py index 16c050f8a6..7f34f1303d 100755 --- a/openpilot/tools/replay/ui.py +++ b/openpilot/tools/replay/ui.py @@ -76,12 +76,12 @@ def ui_thread(addr): 'longitudinalPlan', 'carControl', 'radarState', - 'liveCalibration', + 'extrinsicsCalibration', 'controlsState', 'selfdriveState', - 'liveTracks', + 'radarTracks', 'modelV2', - 'liveParameters', + 'vehicleParameters', 'narrowRoadCameraState', ], addr=addr, @@ -195,10 +195,10 @@ def ui_thread(addr): plot_lead(sm['radarState'], top_down) # draw all radar points - maybe_update_radar_points(sm['liveTracks'].points, top_down[1]) + maybe_update_radar_points(sm['radarTracks'].points, top_down[1]) - if sm.updated['liveCalibration'] and num_px: - rpyCalib = np.asarray(sm['liveCalibration'].rpyCalib) + if sm.updated['extrinsicsCalibration'] and num_px: + rpyCalib = np.asarray(sm['extrinsicsCalibration'].rpyCalib) calibration = Calibration(num_px, rpyCalib, intrinsic_matrix, calib_scale) # Update overlay texture (RGB img -> RGBA with non-black pixels visible) @@ -232,10 +232,10 @@ def ui_thread(addr): ("LONG CONTROL STATE: " + str(sm['controlsState'].longControlState), YELLOW), ("LONG MPC SOURCE: " + str(sm['longitudinalPlan'].longitudinalPlanSource), YELLOW), None, - ("ANGLE OFFSET (AVG): " + str(round(sm['liveParameters'].angleOffsetAverageDeg, 2)) + " deg", YELLOW), - ("ANGLE OFFSET (INSTANT): " + str(round(sm['liveParameters'].angleOffsetDeg, 2)) + " deg", YELLOW), - ("STIFFNESS: " + str(round(sm['liveParameters'].stiffnessFactor * 100.0, 2)) + " %", YELLOW), - ("STEER RATIO: " + str(round(sm['liveParameters'].steerRatio, 2)), YELLOW), + ("ANGLE OFFSET (AVG): " + str(round(sm['vehicleParameters'].angleOffsetAverageDeg, 2)) + " deg", YELLOW), + ("ANGLE OFFSET (INSTANT): " + str(round(sm['vehicleParameters'].angleOffsetDeg, 2)) + " deg", YELLOW), + ("STIFFNESS: " + str(round(sm['vehicleParameters'].stiffnessFactor * 100.0, 2)) + " %", YELLOW), + ("STEER RATIO: " + str(round(sm['vehicleParameters'].steerRatio, 2)), YELLOW), ] for i, line in enumerate(lines): diff --git a/tools/scripts/car/max_lat_accel.py b/tools/scripts/car/max_lat_accel.py index dc44e8ac40..b76b3302cc 100755 --- a/tools/scripts/car/max_lat_accel.py +++ b/tools/scripts/car/max_lat_accel.py @@ -62,8 +62,8 @@ def find_events(lr: LogReader, extrapolate: bool = False, qlog: bool = False) -> elif msg.which() == 'controlsState': curvature = msg.controlsState.curvature - elif msg.which() == 'liveParameters': - roll = msg.liveParameters.roll + elif msg.which() == 'vehicleParameters': + roll = msg.vehicleParameters.roll if lat_active > min_lat_active and steering_unpressed > min_steering_unpressed and requesting_max > min_requesting_max: # TODO: record max lat accel at the end of the event, need to use the past lat accel as overriding can happen before we detect it diff --git a/tools/scripts/cycle_alerts.py b/tools/scripts/cycle_alerts.py index cf4a1d8999..f78dfe1090 100755 --- a/tools/scripts/cycle_alerts.py +++ b/tools/scripts/cycle_alerts.py @@ -54,8 +54,8 @@ def cycle_alerts(duration=200, is_metric=False): CS = car.CarState.new_message() CP = CarInterface.get_non_essential_params("HONDA_CIVIC") - sm = messaging.SubMaster(['deviceState', 'pandaStates', 'narrowRoadCameraState', 'modelV2', 'liveCalibration', - 'driverMonitoringState', 'longitudinalPlan', 'livePose', + sm = messaging.SubMaster(['deviceState', 'pandaStates', 'narrowRoadCameraState', 'modelV2', 'extrinsicsCalibration', + 'driverMonitoringState', 'longitudinalPlan', 'deviceMotion', 'managerState'] + cameras) pm = messaging.PubMaster(['selfdriveState', 'pandaStates', 'deviceState']) @@ -87,7 +87,7 @@ def cycle_alerts(duration=200, is_metric=False): procs[i].shouldBeRunning = True sm['managerState'].processes = procs - sm['liveCalibration'].rpyCalib = [-1 * random.random() for _ in range(random.randint(0, 3))] + sm['extrinsicsCalibration'].rpyCalib = [-1 * random.random() for _ in range(random.randint(0, 3))] for s in sm.data.keys(): prob = 0.3 if s in cameras else 0.08 From eecff738506eafb38debb527c42cb65426aa2e02 Mon Sep 17 00:00:00 2001 From: Vraj Parikh <58668812+TheConverseEngineer@users.noreply.github.com> Date: Mon, 10 Aug 2026 10:21:16 -0700 Subject: [PATCH 153/170] Sleep shadow mode (#38578) * dmvision c703670b-6a4a-4743-8f79-69d5486cadf7 (a62f9948 with new sleep head) * dmvision c703670b-6a4a-4743-8f79-69d5486cadf7 (a62f9948 with new sleep head and same batchnorms) --- openpilot/selfdrive/modeld/models/dmonitoring_model.onnx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpilot/selfdrive/modeld/models/dmonitoring_model.onnx b/openpilot/selfdrive/modeld/models/dmonitoring_model.onnx index 87a116cdba..1e592e2192 100644 --- a/openpilot/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/openpilot/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:607ed8f64a2d756657b621aaccf92a5cadb296b0a1d13d11605766901510cef1 -size 7486861 +oid sha256:dd299afabe7a3e0d04cbe2bd97fdb0c93bba8ad6d3cc3663a0e0ededaf243ac2 +size 7497335 From b5b156825d437483b0fdd54fa820caff938c9535 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 10 Aug 2026 20:02:52 -0700 Subject: [PATCH 154/170] ui: fix inverted DM head tracking on RHD (#38607) * ui: fix inverted DM head tracking on RHD b29d0a17af switched the mici driver state renderer from computing pose from driverStateV2 itself to consuming driverMonitoringState.visionPolicyState.pose. That published yaw is mirrored for RHD (policy.py:269) so the asymmetric yaw offsets/thresholds can be tuned in a single frame, so the UI's unconditional sign flip left RHD head tracking backwards. LHD was unaffected, which is why the refactor looked like a no-op. Undo the mirror on the UI side. * Apply suggestions from code review * fold the sign into the RHD multiplier --- openpilot/selfdrive/ui/mici/onroad/driver_state.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpilot/selfdrive/ui/mici/onroad/driver_state.py b/openpilot/selfdrive/ui/mici/onroad/driver_state.py index 85b90704ac..ad5a521518 100644 --- a/openpilot/selfdrive/ui/mici/onroad/driver_state.py +++ b/openpilot/selfdrive/ui/mici/onroad/driver_state.py @@ -169,8 +169,8 @@ class DriverStateRenderer(Widget): self._is_rhd = dm_state.isRHD self._face_detected = dm_state.visionPolicyState.faceDetected self._awareness_unfull = self.effective_active and dm_state.visionPolicyState.awarenessPercent < self.AWARENESS_UNFULL_PERCENT - self._face_pitch = dm_state.visionPolicyState.pose.pitch + math.radians(6) # calib or DM pose is not accurate, add a fake upward pitch to bias forward - self._face_yaw = -dm_state.visionPolicyState.pose.yaw # undo sign flip in face_orientation_from_model to match UI convention + self._face_pitch = dm_state.visionPolicyState.pose.pitch + math.radians(6) # calib or DM pose is not accurate, add a fake upward pitch to bias forward + self._face_yaw = dm_state.visionPolicyState.pose.yaw * (1 if self._is_rhd else -1) # undo sign flip in face_orientation_from_model to match UI convention driverstate = sm["driverStateV2"] driver_data = driverstate.rightDriverData if self._is_rhd else driverstate.leftDriverData From b1cdf387b5779d4bdc585122b085c4cd3990b85f Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Mon, 10 Aug 2026 21:23:59 -0700 Subject: [PATCH 155/170] sync after a build (#38606) * build: sync after a successful build * sync on failed builds too --- openpilot/system/manager/build.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpilot/system/manager/build.py b/openpilot/system/manager/build.py index 75a7dc63eb..c5fdd6d92a 100755 --- a/openpilot/system/manager/build.py +++ b/openpilot/system/manager/build.py @@ -50,6 +50,8 @@ def build() -> None: if scons.returncode == 0: break + os.sync() + if scons.returncode != 0: # Build failed log errors error_s = b"\n".join(compile_output).decode('utf8', 'replace') From 122a8ca00ab4ab78d62adeaf4fe26f7e5253815e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 10 Aug 2026 22:36:54 -0700 Subject: [PATCH 156/170] camerad: improve IFE robustness to lags (#38548) camerad: keep request IDs monotonic --- openpilot/system/camerad/cameras/spectra.cc | 25 ++++++++++++--------- openpilot/system/camerad/cameras/spectra.h | 5 +++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/openpilot/system/camerad/cameras/spectra.cc b/openpilot/system/camerad/cameras/spectra.cc index f2d849ae28..d4c3e5c646 100644 --- a/openpilot/system/camerad/cameras/spectra.cc +++ b/openpilot/system/camerad/cameras/spectra.cc @@ -297,7 +297,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v) { LOGD("camera init %d", cc.camera_num); buf.init(this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); - clearAndRequeue(1); + clearAndRequeue(); } void SpectraCamera::sensors_start() { @@ -942,7 +942,10 @@ void SpectraCamera::config_ife(int idx, int request_id, bool init) { assert(ret == 0); } -void SpectraCamera::enqueue_frame(uint64_t request_id) { +void SpectraCamera::enqueue_frame() { + // The kernel reports only requests newer than reported_req_id, which a flush does not reset. + // https://github.com/commaai/agnos-kernel-sdm845/blob/93ddd472ce522ab8669456b11bf3924ad32e9882/drivers/media/platform/msm/camera/cam_isp/cam_isp_context.c#L609-L613 + uint64_t request_id = next_request_id++; int i = request_id % ife_buf_depth; assert(sync_objs_ife[i] == 0); @@ -1428,14 +1431,14 @@ bool SpectraCamera::handle_camera_event(const cam_req_mgr_message *event_data) { if (!waitForFrameReady(request_id)) { // Reset queue on sync failure to prevent frame tearing LOGE("camera %d sync failure %ld %ld ", cc.camera_num, request_id, ife_frame_id); - clearAndRequeue(request_id + 1); + clearAndRequeue(); return false; } int buf_idx = request_id % ife_buf_depth; bool ret = processFrame(buf_idx, request_id, ife_frame_id, timestamp); destroySyncObjectAt(buf_idx); - enqueue_frame(request_id + ife_buf_depth); // request next frame for this slot + enqueue_frame(); // request next frame for this slot return ret; } @@ -1445,7 +1448,7 @@ bool SpectraCamera::validateEvent(uint64_t request_id, uint64_t ife_frame_id) { if (request_id == 0) { if (invalid_request_count++ > ife_buf_depth+2) { LOGE("camera %d reset after half second of invalid requests", cc.camera_num); - clearAndRequeue(last_valid_request_id + 1); + clearAndRequeue(); invalid_request_count = 0; } return false; @@ -1456,26 +1459,26 @@ bool SpectraCamera::validateEvent(uint64_t request_id, uint64_t ife_frame_id) { if (!skip_expected) { if (ife_frame_id != last_valid_ife_frame_id + 1) { LOGE("camera %d frame ID skipped, %lu -> %lu", cc.camera_num, last_valid_ife_frame_id, ife_frame_id); - clearAndRequeue(request_id + 1); + clearAndRequeue(); return false; } if (request_id != last_valid_request_id + 1) { LOGE("camera %d requests skipped %ld -> %ld", cc.camera_num, last_valid_request_id, request_id); - clearAndRequeue(request_id + 1); + clearAndRequeue(); return false; } } return true; } -void SpectraCamera::clearAndRequeue(uint64_t from_request_id) { +void SpectraCamera::clearAndRequeue() { // clear everything, then queue up a fresh set of frames - LOGW("clearing and requeuing camera %d from %lu", cc.camera_num, from_request_id); + LOGW("clearing and requeuing camera %d from %lu", cc.camera_num, next_request_id); clear_req_queue(); last_requeue_ts = nanos_since_boot(); - for (uint64_t id = from_request_id; id < from_request_id + ife_buf_depth; ++id) { - enqueue_frame(id); + for (int i = 0; i < ife_buf_depth; ++i) { + enqueue_frame(); } skip_expected = true; } diff --git a/openpilot/system/camerad/cameras/spectra.h b/openpilot/system/camerad/cameras/spectra.h index acb2000313..ebb2d3f1de 100644 --- a/openpilot/system/camerad/cameras/spectra.h +++ b/openpilot/system/camerad/cameras/spectra.h @@ -144,7 +144,7 @@ public: void config_ife(int idx, int request_id, bool init=false); int clear_req_queue(); - void enqueue_frame(uint64_t request_id); + void enqueue_frame(); int sensors_init(); void sensors_start(); @@ -205,6 +205,7 @@ public: int buf_handle_raw[MAX_IFE_BUFS] = {}; int sync_objs_ife[MAX_IFE_BUFS] = {}; int sync_objs_bps[MAX_IFE_BUFS] = {}; + uint64_t next_request_id = 1; uint64_t last_valid_request_id = 0; uint64_t last_requeue_ts = 0; uint64_t last_valid_ife_frame_id = 0; @@ -215,7 +216,7 @@ public: SpectraMaster *m; private: - void clearAndRequeue(uint64_t from_request_id); + void clearAndRequeue(); bool validateEvent(uint64_t request_id, uint64_t ife_frame_id); bool waitForFrameReady(uint64_t request_id); bool processFrame(int buf_idx, uint64_t request_id, uint64_t ife_frame_id, uint64_t timestamp); From 062cb672e8cbdb8b905b85120f375779fc6d2c9c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 11 Aug 2026 00:39:51 -0700 Subject: [PATCH 157/170] camerad: force BPS clock to max (#38539) * camerad: account for BPS clock overhead * force to max --- openpilot/system/camerad/cameras/spectra.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpilot/system/camerad/cameras/spectra.cc b/openpilot/system/camerad/cameras/spectra.cc index d4c3e5c646..0ce3867034 100644 --- a/openpilot/system/camerad/cameras/spectra.cc +++ b/openpilot/system/camerad/cameras/spectra.cc @@ -609,7 +609,7 @@ void SpectraCamera::config_bps(int idx, int request_id) { tmp.header = CAM_ICP_CMD_GENERIC_BLOB_CLK; tmp.header |= (sizeof(cam_icp_clk_bw_request)) << 8; tmp.clk.budget_ns = 0x1fca058; - tmp.clk.frame_cycles = sensor->frame_width * sensor->frame_height; // matches striping lib pixelCount + tmp.clk.frame_cycles = 20000000; // force max BPS clock (600 MHz) tmp.clk.rt_flag = 0x0; tmp.clk.uncompressed_bw = 0x38512180; tmp.clk.compressed_bw = 0x38512180; From 32b518d51d7660d59ad4b057c90d8c747a16badd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 11 Aug 2026 17:47:53 -0700 Subject: [PATCH 158/170] set long maneuvers to actuation limits --- openpilot/tools/longitudinal_maneuvers/maneuversd.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpilot/tools/longitudinal_maneuvers/maneuversd.py b/openpilot/tools/longitudinal_maneuvers/maneuversd.py index 0e20840274..cd5ebe76bc 100755 --- a/openpilot/tools/longitudinal_maneuvers/maneuversd.py +++ b/openpilot/tools/longitudinal_maneuvers/maneuversd.py @@ -117,8 +117,8 @@ MANEUVERS = [ initial_speed=20. * CV.MPH_TO_MS, ), Maneuver( - "brake step response: -4m/s^2 from 20mph", - [Action([-4], [3])], + "brake step response: -3.5m/s^2 from 20mph", + [Action([-3.5], [3])], repeat=2, initial_speed=20. * CV.MPH_TO_MS, ), @@ -129,8 +129,8 @@ MANEUVERS = [ initial_speed=20. * CV.MPH_TO_MS, ), Maneuver( - "gas step response: +4m/s^2 from 20mph", - [Action([4], [3])], + "gas step response: +2m/s^2 from 20mph", + [Action([2], [3])], repeat=2, initial_speed=20. * CV.MPH_TO_MS, ), From ad5afe222d543c59522906f0d2eae438d321bdb1 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 11 Aug 2026 18:09:25 -0700 Subject: [PATCH 159/170] log chestnut ASM at 10Hz (#38609) * modeld: only read the smu while the big model runs * chestnutState at 10Hz * smu polling --- openpilot/cereal/services.py | 2 +- openpilot/selfdrive/modeld/modeld.py | 41 ++++++++++++++++++---------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/openpilot/cereal/services.py b/openpilot/cereal/services.py index ebe8be1d60..08633d6975 100755 --- a/openpilot/cereal/services.py +++ b/openpilot/cereal/services.py @@ -25,7 +25,7 @@ _services: dict[str, tuple] = { "accelerometer": (True, 104., 104), "temperatureSensor": (True, 2., 200), "deviceState": (True, 2., 1), - "chestnutState": (True, 0.1, 1), + "chestnutState": (True, 10., 10), "touch": (True, 20., 1), "can": (True, 100., 2053, QueueSize.BIG), # decimation gives ~3 msgs in a full segment "controlsState": (True, 100., 10, QueueSize.MEDIUM), diff --git a/openpilot/selfdrive/modeld/modeld.py b/openpilot/selfdrive/modeld/modeld.py index e87d4f3111..7f049912dd 100755 --- a/openpilot/selfdrive/modeld/modeld.py +++ b/openpilot/selfdrive/modeld/modeld.py @@ -71,9 +71,12 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log. class ChestnutState: # only modeld can access chestnut - def __init__(self, pm: PubMaster): + def __init__(self, pm: PubMaster, big: bool): self.pm = pm + self.big = big self.valid = True + self.sends = 0 + self.metrics = {} @cached_property def power_limit(self) -> int: @@ -83,33 +86,41 @@ class ChestnutState: def send(self) -> None: msg = messaging.new_message('chestnutState') state = msg.chestnutState - valid = False - if "AMD" in Device._opened_devices: + self.sends += 1 + if self.big and "AMD" in Device._opened_devices and self.sends % 100 == 1: try: smu = Device["AMD"].iface.dev_impl.smu smu._send_msg(smu.smu_mod.PPSMC_MSG_TransferTableSmu2Dram, smu.smu_mod.TABLE_SMU_METRICS, timeout=100) metrics = smu.read_table(smu.smu_mod.SmuMetricsExternal_t, smu.smu_mod.TABLE_SMU_METRICS).SmuMetrics - state.tempC = metrics.AvgTemperature[smu.smu_mod.TEMP_HOTSPOT] - state.memoryTempC = metrics.AvgTemperature[smu.smu_mod.TEMP_MEM] - state.powerDrawW = metrics.AverageSocketPower - state.powerLimitW = self.power_limit - state.gpuUsagePercent = metrics.AverageGfxActivity - state.gpuClockMhz = metrics.AverageGfxclkFrequencyPostDs - state.fanSpeedRpm = metrics.AvgFanRpm - valid = True + self.metrics = {'tempC': metrics.AvgTemperature[smu.smu_mod.TEMP_HOTSPOT], + 'memoryTempC': metrics.AvgTemperature[smu.smu_mod.TEMP_MEM], + 'powerDrawW': metrics.AverageSocketPower, + 'powerLimitW': self.power_limit, + 'gpuUsagePercent': metrics.AverageGfxActivity, + 'gpuClockMhz': metrics.AverageGfxclkFrequencyPostDs, + 'fanSpeedRpm': metrics.AvgFanRpm} + self.valid = True except Exception: if self.valid: cloudlog.exception("chestnut state read failed") + self.valid = False + self.metrics.clear() + if self.big: + for k, v in self.metrics.items(): + setattr(state, k, v) + + asm_valid = False + if "AMD" in Device._opened_devices: try: # ASM runs on USB-C power, these still read without a gpu asm = Device["AMD"].iface.pci_dev.usb state.pcieLtssm = asm.read(0xB450, 1)[0] state.supplyVoltage, state.supplyCurrent = struct.unpack(' Date: Tue, 11 Aug 2026 18:13:02 -0700 Subject: [PATCH 160/170] Revert "camerad: improve IFE robustness to lags (#38548)" This reverts commit 122a8ca00ab4ab78d62adeaf4fe26f7e5253815e. --- openpilot/system/camerad/cameras/spectra.cc | 25 +++++++++------------ openpilot/system/camerad/cameras/spectra.h | 5 ++--- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/openpilot/system/camerad/cameras/spectra.cc b/openpilot/system/camerad/cameras/spectra.cc index 0ce3867034..2cbf3594c4 100644 --- a/openpilot/system/camerad/cameras/spectra.cc +++ b/openpilot/system/camerad/cameras/spectra.cc @@ -297,7 +297,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v) { LOGD("camera init %d", cc.camera_num); buf.init(this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); - clearAndRequeue(); + clearAndRequeue(1); } void SpectraCamera::sensors_start() { @@ -942,10 +942,7 @@ void SpectraCamera::config_ife(int idx, int request_id, bool init) { assert(ret == 0); } -void SpectraCamera::enqueue_frame() { - // The kernel reports only requests newer than reported_req_id, which a flush does not reset. - // https://github.com/commaai/agnos-kernel-sdm845/blob/93ddd472ce522ab8669456b11bf3924ad32e9882/drivers/media/platform/msm/camera/cam_isp/cam_isp_context.c#L609-L613 - uint64_t request_id = next_request_id++; +void SpectraCamera::enqueue_frame(uint64_t request_id) { int i = request_id % ife_buf_depth; assert(sync_objs_ife[i] == 0); @@ -1431,14 +1428,14 @@ bool SpectraCamera::handle_camera_event(const cam_req_mgr_message *event_data) { if (!waitForFrameReady(request_id)) { // Reset queue on sync failure to prevent frame tearing LOGE("camera %d sync failure %ld %ld ", cc.camera_num, request_id, ife_frame_id); - clearAndRequeue(); + clearAndRequeue(request_id + 1); return false; } int buf_idx = request_id % ife_buf_depth; bool ret = processFrame(buf_idx, request_id, ife_frame_id, timestamp); destroySyncObjectAt(buf_idx); - enqueue_frame(); // request next frame for this slot + enqueue_frame(request_id + ife_buf_depth); // request next frame for this slot return ret; } @@ -1448,7 +1445,7 @@ bool SpectraCamera::validateEvent(uint64_t request_id, uint64_t ife_frame_id) { if (request_id == 0) { if (invalid_request_count++ > ife_buf_depth+2) { LOGE("camera %d reset after half second of invalid requests", cc.camera_num); - clearAndRequeue(); + clearAndRequeue(last_valid_request_id + 1); invalid_request_count = 0; } return false; @@ -1459,26 +1456,26 @@ bool SpectraCamera::validateEvent(uint64_t request_id, uint64_t ife_frame_id) { if (!skip_expected) { if (ife_frame_id != last_valid_ife_frame_id + 1) { LOGE("camera %d frame ID skipped, %lu -> %lu", cc.camera_num, last_valid_ife_frame_id, ife_frame_id); - clearAndRequeue(); + clearAndRequeue(request_id + 1); return false; } if (request_id != last_valid_request_id + 1) { LOGE("camera %d requests skipped %ld -> %ld", cc.camera_num, last_valid_request_id, request_id); - clearAndRequeue(); + clearAndRequeue(request_id + 1); return false; } } return true; } -void SpectraCamera::clearAndRequeue() { +void SpectraCamera::clearAndRequeue(uint64_t from_request_id) { // clear everything, then queue up a fresh set of frames - LOGW("clearing and requeuing camera %d from %lu", cc.camera_num, next_request_id); + LOGW("clearing and requeuing camera %d from %lu", cc.camera_num, from_request_id); clear_req_queue(); last_requeue_ts = nanos_since_boot(); - for (int i = 0; i < ife_buf_depth; ++i) { - enqueue_frame(); + for (uint64_t id = from_request_id; id < from_request_id + ife_buf_depth; ++id) { + enqueue_frame(id); } skip_expected = true; } diff --git a/openpilot/system/camerad/cameras/spectra.h b/openpilot/system/camerad/cameras/spectra.h index ebb2d3f1de..acb2000313 100644 --- a/openpilot/system/camerad/cameras/spectra.h +++ b/openpilot/system/camerad/cameras/spectra.h @@ -144,7 +144,7 @@ public: void config_ife(int idx, int request_id, bool init=false); int clear_req_queue(); - void enqueue_frame(); + void enqueue_frame(uint64_t request_id); int sensors_init(); void sensors_start(); @@ -205,7 +205,6 @@ public: int buf_handle_raw[MAX_IFE_BUFS] = {}; int sync_objs_ife[MAX_IFE_BUFS] = {}; int sync_objs_bps[MAX_IFE_BUFS] = {}; - uint64_t next_request_id = 1; uint64_t last_valid_request_id = 0; uint64_t last_requeue_ts = 0; uint64_t last_valid_ife_frame_id = 0; @@ -216,7 +215,7 @@ public: SpectraMaster *m; private: - void clearAndRequeue(); + void clearAndRequeue(uint64_t from_request_id); bool validateEvent(uint64_t request_id, uint64_t ife_frame_id); bool waitForFrameReady(uint64_t request_id); bool processFrame(int buf_idx, uint64_t request_id, uint64_t ife_frame_id, uint64_t timestamp); From 473ab1787692a8a61df7b78fa5d84893f5bbb519 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 11 Aug 2026 18:15:10 -0700 Subject: [PATCH 161/170] add cupra to release notes --- RELEASES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 446ec88093..36ddadb431 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,4 @@ -Version 0.11.2 (2026-08-11) +Version 0.11.2 (2026-08-12) ======================= * New driving model * Big model with 880M parameters @@ -7,6 +7,7 @@ Version 0.11.2 (2026-08-11) * Generate dashcam clips from comma connect * Remote comma body control from comma connect * New alert sounds +* CUPRA Born 2021-2023 support thanks to DaHansi! * Volkswagen ID.4 2021-2025 support thanks to DaHansi! Version 0.11.1 (2026-05-18) From f1746f2e2de9c92ec2fc1bccacf6c9c715a66f96 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 11 Aug 2026 20:42:08 -0700 Subject: [PATCH 162/170] fix spinner crash (#38612) ui: keep spinner independent of build artifacts --- openpilot/system/ui/widgets/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpilot/system/ui/widgets/__init__.py b/openpilot/system/ui/widgets/__init__.py index 9904dfde2c..4e13920d60 100644 --- a/openpilot/system/ui/widgets/__init__.py +++ b/openpilot/system/ui/widgets/__init__.py @@ -16,7 +16,7 @@ def _get_device() -> DeviceLike: try: from openpilot.selfdrive.ui.ui_state import device return device - except ImportError: + except (ImportError, OSError): class Device: awake = True return Device() From 927e822b5a8bcc2448ec1572c733b7f92ca5bf35 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 11 Aug 2026 21:25:45 -0700 Subject: [PATCH 163/170] fix tap to factory reset (#38613) hardware: lazily import optional SIM support --- openpilot/common/hardware/comma/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpilot/common/hardware/comma/hardware.py b/openpilot/common/hardware/comma/hardware.py index dfbf1e3f17..effb4d9dbe 100644 --- a/openpilot/common/hardware/comma/hardware.py +++ b/openpilot/common/hardware/comma/hardware.py @@ -12,7 +12,6 @@ from openpilot.common.utils import sudo_read, sudo_write from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action from openpilot.common.esim.base import LPABase from openpilot.common.hardware.base import HardwareBase, ThermalConfig, ThermalZone -from openpilot.common.esim.lpa import LPA from openpilot.common.hardware.comma.pins import GPIO from openpilot.common.hardware.comma.amplifier import Amplifier @@ -155,6 +154,7 @@ class HardwareComma(HardwareBase): } def get_sim_lpa(self) -> LPABase: + from openpilot.common.esim.lpa import LPA return LPA() def get_imei(self): From 7a76a50c4fb6619923fe826be2a14d51af342c08 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 11 Aug 2026 21:48:27 -0700 Subject: [PATCH 164/170] Add release-chestnut branch --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0f248cf35a..6c0d944a2e 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,12 @@ We have detailed instructions for [how to install the harness and device in a ca Running `master` and other branches directly is supported, but it's recommended to run one of the following prebuilt branches: -| comma four branch | comma 3X branch | URL | description | -|------------------------|------------------------|----------------------------------------|-------------------------------------------------------------------------------------| -| `release-mici` | `release-tizi` | openpilot.comma.ai | This is openpilot's release branch. | -| `release-mici-staging` | `release-tizi-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. | -| `nightly` | `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. | -| `nightly-dev` | `nightly-dev` | installer.comma.ai/commaai/nightly-dev | Same as nightly, but includes experimental development features for some cars. | +| comma four branch | comma four + chestnut branch | comma 3X branch | URL | description | +|------------------------|------------------------------|------------------------|----------------------------------------|-------------------------------------------------------------------------------------| +| `release-mici` | `release-chestnut` | `release-tizi` | openpilot.comma.ai | This is openpilot's release branch. | +| `release-mici-staging` | | `release-tizi-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. | +| `nightly` | | `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. | +| `nightly-dev` | | `nightly-dev` | installer.comma.ai/commaai/nightly-dev | Same as nightly, but includes experimental development features for some cars. | To start developing openpilot ------ From 10502adf953a1dae835d2356186fdb04a0d7cf40 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 11 Aug 2026 23:27:16 -0700 Subject: [PATCH 165/170] AGNOS 19.6 (#38611) * bump version * add production --- launch_env.sh | 2 +- openpilot/common/hardware/comma/agnos.json | 28 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index a201fd6c47..094622005a 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="19.5" + export AGNOS_VERSION="19.6" fi export STAGING_ROOT="/data/safe_staging" diff --git a/openpilot/common/hardware/comma/agnos.json b/openpilot/common/hardware/comma/agnos.json index 3a8d03e104..ae1cbbcd38 100644 --- a/openpilot/common/hardware/comma/agnos.json +++ b/openpilot/common/hardware/comma/agnos.json @@ -23,14 +23,14 @@ }, { "name": "abl", - "url": "https://commadist.azureedge.net/agnosupdate/abl-b6fba807b9bcd66a31f2afb0eba5163ec239693ad32e2e4200f6c356adfe098c.img.xz", - "hash": "b6fba807b9bcd66a31f2afb0eba5163ec239693ad32e2e4200f6c356adfe098c", - "hash_raw": "b6fba807b9bcd66a31f2afb0eba5163ec239693ad32e2e4200f6c356adfe098c", + "url": "https://commadist.azureedge.net/agnosupdate/abl-29fd7ed1c012e599420764840f9f11286d34dbff4adaf102a447f06d8c5e0b35.img.xz", + "hash": "29fd7ed1c012e599420764840f9f11286d34dbff4adaf102a447f06d8c5e0b35", + "hash_raw": "29fd7ed1c012e599420764840f9f11286d34dbff4adaf102a447f06d8c5e0b35", "size": 274432, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "b6fba807b9bcd66a31f2afb0eba5163ec239693ad32e2e4200f6c356adfe098c" + "ondevice_hash": "29fd7ed1c012e599420764840f9f11286d34dbff4adaf102a447f06d8c5e0b35" }, { "name": "aop", @@ -56,28 +56,28 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-f716b81d557c274be707abc200276ba9e8320ad0386586f36f9065dd563fee94.img.xz", - "hash": "f716b81d557c274be707abc200276ba9e8320ad0386586f36f9065dd563fee94", - "hash_raw": "f716b81d557c274be707abc200276ba9e8320ad0386586f36f9065dd563fee94", + "url": "https://commadist.azureedge.net/agnosupdate/boot-b30f5eef65ec3878f3aa3dcaf2cc95c09e2c1e661cd3a38e94da37dee76f68bd.img.xz", + "hash": "b30f5eef65ec3878f3aa3dcaf2cc95c09e2c1e661cd3a38e94da37dee76f68bd", + "hash_raw": "b30f5eef65ec3878f3aa3dcaf2cc95c09e2c1e661cd3a38e94da37dee76f68bd", "size": 46897152, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "269bc216189c4532a3e796ef2f4d47d0a03e774c883203be93feb8a8ed3c3adb" + "ondevice_hash": "6650e4c46df99ae6dfd6ee895a34b8a2a3cc490a8ce18e16cc3c451c3f822b6e" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-7b3da36853ee71f7f43ff9eb0f59ca8c4e80a2ba96a9a4911731641a5d008ae2.img.xz", - "hash": "414a9145d6ba13bc04760b31545507109e9a2509958dd44782dd1912b1cb96e9", - "hash_raw": "7b3da36853ee71f7f43ff9eb0f59ca8c4e80a2ba96a9a4911731641a5d008ae2", + "url": "https://commadist.azureedge.net/agnosupdate/system-5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3.img.xz", + "hash": "b134fd04e9da27fa1d359ea0f2742c216fa21a08b5c47e9be22ab3b0563d9b9b", + "hash_raw": "5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "d4fadd9dfb4e20875650e60e37b9f8ccf2dc6c0fdc4f9a15912bee5ab8440a13", + "ondevice_hash": "91242772af771ae96fe2eebc105f2b80a7e1dbaaf6003c2574b62d51b806f468", "alt": { - "hash": "7b3da36853ee71f7f43ff9eb0f59ca8c4e80a2ba96a9a4911731641a5d008ae2", - "url": "https://commadist.azureedge.net/agnosupdate/system-7b3da36853ee71f7f43ff9eb0f59ca8c4e80a2ba96a9a4911731641a5d008ae2.img", + "hash": "5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3", + "url": "https://commadist.azureedge.net/agnosupdate/system-5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3.img", "size": 4718592000 } } From fe47e752f6d49feceecebc71cec1f58d4865c5ec Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 12 Aug 2026 20:15:10 -0700 Subject: [PATCH 166/170] log USB3 lane (#38617) --- openpilot/cereal/log.capnp | 7 +++++++ openpilot/common/hardware/usb.py | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp index 7b93e4aa73..fdf022fa63 100644 --- a/openpilot/cereal/log.capnp +++ b/openpilot/cereal/log.capnp @@ -704,6 +704,13 @@ struct UsbState { manufacturer @6 :Text; product @5 :Text; linkErrorCount @7 :UInt16; + usb3Lane @8 :Usb3Lane; + + enum Usb3Lane { + unknown @0; + a @1; + b @2; + } } } diff --git a/openpilot/common/hardware/usb.py b/openpilot/common/hardware/usb.py index 1ebcf571b5..b9f6db2757 100644 --- a/openpilot/common/hardware/usb.py +++ b/openpilot/common/hardware/usb.py @@ -5,6 +5,8 @@ CHESTNUT_FW_VERSION = "ed4e39b7" CHESTNUT_USB_IDS = ((0xADD1, 0x0001), (0x3801, 0x0001)) CHESTNUT_ROM_USB_IDS = ((0x174C, 0x2464), (0x174C, 0x2463)) USB_DEVICES_PATH = Path("/sys/bus/usb/devices") +TYPEC_CC_ORIENTATION_PATH = Path("/sys/class/power_supply/usb/typec_cc_orientation") +PRIMARY_USB_CONTROLLER = "a600000.ssusb" def get_usb_topology() -> set[str]: @@ -45,6 +47,7 @@ def controller(device: Path) -> Path | None: def get_usb_state() -> list[dict]: devices = [] + typec_orientation = read_int(TYPEC_CC_ORIENTATION_PATH) for device in usb_devices(): vendor_id = read_int(device / "idVendor", 16) product_id = read_int(device / "idProduct", 16) @@ -58,6 +61,7 @@ def get_usb_state() -> list[dict]: "manufacturer": read(device / "manufacturer") or "", "product": read(device / "product") or "", "linkErrorCount": read_int(ctrl / "portli", 0) & 0xFFFF if ctrl is not None else 0, + "usb3Lane": {1: "a", 2: "b"}.get(typec_orientation, "unknown") if ctrl is not None and ctrl.name == PRIMARY_USB_CONTROLLER else "unknown", }) return devices @@ -75,6 +79,7 @@ def set_usb_state(device_state, devices: list[dict]) -> None: entry.manufacturer = device["manufacturer"] entry.product = device["product"] entry.linkErrorCount = device["linkErrorCount"] + entry.usb3Lane = device.get("usb3Lane", "unknown") if (entry.vendorId, entry.productId) in CHESTNUT_USB_IDS: chestnut_present = True From b7c333cf3fee117779515c9ebfd7b2beb164fa81 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 12 Aug 2026 20:31:41 -0700 Subject: [PATCH 167/170] pigeond: add comma AGPS source (#38616) --- openpilot/system/ubloxd/pigeond.py | 44 ++++++++++++++++++------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/openpilot/system/ubloxd/pigeond.py b/openpilot/system/ubloxd/pigeond.py index 58fba4d4e1..3da9350241 100755 --- a/openpilot/system/ubloxd/pigeond.py +++ b/openpilot/system/ubloxd/pigeond.py @@ -8,6 +8,7 @@ import urllib.parse from datetime import datetime, UTC from openpilot.cereal import messaging +from openpilot.common.api import Api from openpilot.common.time_helpers import system_time_valid from openpilot.common.params import Params from openpilot.common.serial import Serial @@ -41,15 +42,24 @@ def add_ubx_checksum(msg: bytes) -> bytes: B = (B + A) % 256 return msg + bytes([A, B]) -def get_assistnow_messages(token: str) -> list[bytes]: - # make request - # TODO: implement adding the last known location - r = requests.get("https://online-live2.services.u-blox.com/GetOnlineData.ashx", params=urllib.parse.urlencode({ - 'token': token, - 'gnss': 'gps,glo', - 'datatype': 'eph,alm,aux', - }, safe=':,'), timeout=5) - assert r.status_code == 200, "Got invalid status code" +def get_assistnow_messages() -> list[bytes]: + params = Params() + if token := params.get('AssistNowToken'): + cloudlog.warning("Downloading AssistNow data directly from u-blox") + # TODO: implement adding the last known location + r = requests.get("https://online-live2.services.u-blox.com/GetOnlineData.ashx", params=urllib.parse.urlencode({ + 'token': token, + 'gnss': 'gps,glo', + 'datatype': 'eph,alm,aux', + }, safe=':,'), timeout=5) + elif dongle_id := params.get('DongleId'): + cloudlog.warning("Downloading AssistNow data from comma's AGPS proxy") + api = Api(dongle_id) + r = api.get(f"v1/{dongle_id}/assist", access_token=api.get_token(), timeout=5) + else: + raise RuntimeError("Neither AssistNowToken nor DongleId is configured") + + r.raise_for_status() dat = r.content # split up messages @@ -230,15 +240,13 @@ def init_pigeon(pigeon: TTYPigeon) -> bool: )) pigeon.send_with_ack(msg, ack=UBLOX_ASSIST_ACK) - # try getting AssistNow if we have a token - token = Params().get('AssistNowToken') - if token is not None: - try: - for msg in get_assistnow_messages(token): - pigeon.send_with_ack(msg, ack=UBLOX_ASSIST_ACK) - cloudlog.warning("AssistNow messages sent") - except Exception: - cloudlog.warning("failed to get AssistNow messages") + # A configured u-blox token takes precedence over comma's AGPS proxy. + try: + for msg in get_assistnow_messages(): + pigeon.send_with_ack(msg, ack=UBLOX_ASSIST_ACK) + cloudlog.warning("AssistNow messages sent") + except Exception: + cloudlog.warning("failed to get AssistNow messages") cloudlog.warning("Pigeon GPS on!") break From ac4ab9a9b84d11c9b3f3edc1f74475656697f1c7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 13 Aug 2026 17:47:37 -0400 Subject: [PATCH 168/170] tests: migrate sunnypilot tests to unittest and remove pytest --- conftest.py | 101 ---------- openpilot/common/parameterized.py | 1 + .../selfdrive/monitoring/test_monitoring.py | 50 ++--- .../mads/tests/test_mads_state_machine.py | 11 +- .../mads/tests/test_mads_steering_mode.py | 23 +-- .../mapd/tests/test_mapd_version.py | 3 +- .../tests/{conftest.py => helpers.py} | 14 +- .../tests/test_camera_offset_helper.py | 3 +- .../tests/test_combined_pkl_loader.py | 75 ++++---- .../modeld_v2/tests/test_compile_modeld.py | 31 +-- .../modeld_v2/tests/test_recovery_power.py | 96 +++++----- .../models/tests/test_default_model.py | 3 +- .../models/tests/test_tinygrad_ref.py | 22 ++- .../dec/tests/pytest_dynamic_controller.py | 94 --------- .../lib/dec/tests/test_dynamic_controller.py | 91 +++++++++ .../lib/nnlc/tests/test_fingerprint.py | 3 +- .../lib/nnlc/tests/test_load_model.py | 3 +- .../controls/lib/nnlc/tests/test_nnlc.py | 9 +- .../tests/test_map_controller.py | 6 +- .../tests/test_vision_controller.py | 17 +- .../tests/test_speed_limit_assist.py | 32 ++-- .../tests/test_speed_limit_resolver.py | 37 ++-- .../lib/tests/test_auto_lane_change.py | 3 +- .../lib/tests/test_blinker_pause_lateral.py | 3 +- .../lib/tests/test_lane_turn_desire.py | 178 +++++++++-------- .../lib/tests/test_latcontrol_torque_ext.py | 3 +- .../selfdrive/controls/lib/tests/test_relc.py | 179 +++++++++--------- .../locationd/tests/test_locationd.py | 16 +- .../tests/test_button_state_tracker.py | 3 +- .../sunnylink/athena/tests/test_sunnylinkd.py | 3 +- .../sunnylink/tests/test_capabilities.py | 9 +- .../tests/test_compile_settings_ui.py | 28 ++- .../sunnylink/tests/test_settings_changes.py | 31 +-- .../sunnylink/tests/test_settings_schema.py | 24 ++- .../system/sensord/tests/test_sensord.py | 7 +- .../tests/test_sp_branch_migrations.py | 116 ++++++------ pyproject.toml | 24 --- uv.lock | 108 ----------- 38 files changed, 621 insertions(+), 839 deletions(-) delete mode 100644 conftest.py rename openpilot/sunnypilot/modeld_v2/tests/{conftest.py => helpers.py} (97%) delete mode 100644 openpilot/sunnypilot/selfdrive/controls/lib/dec/tests/pytest_dynamic_controller.py create mode 100644 openpilot/sunnypilot/selfdrive/controls/lib/dec/tests/test_dynamic_controller.py diff --git a/conftest.py b/conftest.py deleted file mode 100644 index 7e7237457b..0000000000 --- a/conftest.py +++ /dev/null @@ -1,101 +0,0 @@ -# TODO-SP: upstream migrated from pytest to unittest with a custom test runner (tools/test_runner.py). -# Once sunnypilot test files are converted to unittest, this conftest and the pytest deps can be removed. -import contextlib -import gc -import os -import pytest - -from openpilot.common.prefix import OpenpilotPrefix -from openpilot.system.manager import manager -from openpilot.common.hardware import COMMA_HARDWARE, HARDWARE - -# these are heavy CI-only tests, invoked explicitly in .github/workflows/tests.yaml -collect_ignore = [ - "openpilot/selfdrive/test/process_replay/test_processes.py", - "openpilot/selfdrive/test/process_replay/test_regen.py", - - "openpilot/tools/sim/", - - # tinygrad JIT has process-global state. Other test files import modeld → tinygrad, - # which corrupts JIT captures for test_warp.py in the same process. Run separately in CI. - "openpilot/sunnypilot/modeld_v2/tests/test_warp.py", -] - - -def pytest_sessionstart(session): - # TODO: fix tests and enable test order randomization - if session.config.pluginmanager.hasplugin('randomly'): - session.config.option.randomly_reorganize = False - - -@pytest.hookimpl(hookwrapper=True, trylast=True) -def pytest_runtest_call(item): - # ensure we run as a hook after capturemanager's - if item.get_closest_marker("nocapture") is not None: - capmanager = item.config.pluginmanager.getplugin("capturemanager") - with capmanager.global_and_fixture_disabled(): - yield - else: - yield - - -@contextlib.contextmanager -def clean_env(): - starting_env = dict(os.environ) - yield - os.environ.clear() - os.environ.update(starting_env) - - -@pytest.fixture(scope="function", autouse=True) -def openpilot_function_fixture(request): - with clean_env(): - # setup a clean environment for each test - with OpenpilotPrefix(shared_download_cache=request.node.get_closest_marker("shared_download_cache") is not None) as prefix: - prefix = os.environ["OPENPILOT_PREFIX"] - - yield - - # ensure the test doesn't change the prefix - assert "OPENPILOT_PREFIX" in os.environ and prefix == os.environ["OPENPILOT_PREFIX"] - - # cleanup any started processes - manager.manager_cleanup() - - # some processes disable gc for performance, re-enable here - if not gc.isenabled(): - gc.enable() - gc.collect() - -# If you use setUpClass, the environment variables won't be cleared properly, -# so we need to hook both the function and class pytest fixtures -@pytest.fixture(scope="class", autouse=True) -def openpilot_class_fixture(): - with clean_env(): - yield - - -@pytest.fixture(scope="function") -def tici_setup_fixture(request, openpilot_function_fixture): - """Ensure a consistent state for tests on-device. Needs the openpilot function fixture to run first.""" - if 'skip_tici_setup' in request.keywords: - return - HARDWARE.initialize_hardware() - HARDWARE.set_power_save(False) - os.system("pkill -9 -f athena") - - -@pytest.hookimpl(tryfirst=True) -def pytest_collection_modifyitems(config, items): - skipper = pytest.mark.skip(reason="Skipping tici test on PC") - for item in items: - if "tici" in item.keywords: - if not COMMA_HARDWARE: - item.add_marker(skipper) - else: - item.fixturenames.append('tici_setup_fixture') - - if "xdist_group_class_property" in item.keywords: - class_property_name = item.get_closest_marker('xdist_group_class_property').args[0] - class_property_value = getattr(item.cls, class_property_name) - item.add_marker(pytest.mark.xdist_group(class_property_value)) diff --git a/openpilot/common/parameterized.py b/openpilot/common/parameterized.py index 2782fb9a57..16b89553c5 100644 --- a/openpilot/common/parameterized.py +++ b/openpilot/common/parameterized.py @@ -83,6 +83,7 @@ def parameterized_class(attrs, input_list=None): new_cls = type(name, (cls,), dict(params)) new_cls.__module__ = cls.__module__ new_cls.__unittest_skip__ = False + new_cls.__unittest_skip_why__ = "" # else inherited from the base and the collector drops it globs[name] = new_cls # Don't collect the un-parametrised base. cls.__unittest_skip__ = True diff --git a/openpilot/selfdrive/monitoring/test_monitoring.py b/openpilot/selfdrive/monitoring/test_monitoring.py index 95f3f4431c..d9889860f5 100644 --- a/openpilot/selfdrive/monitoring/test_monitoring.py +++ b/openpilot/selfdrive/monitoring/test_monitoring.py @@ -1,4 +1,4 @@ -import pytest +from openpilot.common.parameterized import parameterized from openpilot.common.test import OpenpilotTestCase from openpilot.cereal import log @@ -251,28 +251,30 @@ def _build_sm(selfdrive_enabled, lat_active, steering_pressed, gas_pressed): } -@pytest.mark.parametrize("selfdrive_enabled, lat_active, steering, gas, expected_op_engaged, expected_driver_engaged", [ - (False, False, False, False, False, False), # disabled - (True, False, False, False, True, False), # OP enabled - (False, True, False, False, True, False), # MADS lat-only - (True, True, False, False, True, False), # both active - (False, True, False, True, True, False), # MADS lat-only + gas - (True, True, False, True, True, True), # full op + gas: override - (False, True, True, False, True, True), # MADS lat-only + wheel touch: override -]) -def test_run_step_engagement(selfdrive_enabled, lat_active, steering, gas, - expected_op_engaged, expected_driver_engaged): - sm = _build_sm(selfdrive_enabled, lat_active, steering, gas) - dm = DriverMonitoring() - captured = {} - orig = dm._update_events +class TestRunStepEngagement(OpenpilotTestCase): + @parameterized.expand([ + (False, False, False, False, False, False), # disabled + (True, False, False, False, True, False), # OP enabled + (False, True, False, False, True, False), # MADS lat-only + (True, True, False, False, True, False), # both active + (False, True, False, True, True, False), # MADS lat-only + gas + (True, True, False, True, True, True), # full op + gas: override + (False, True, True, False, True, True), # MADS lat-only + wheel touch: override + ], names=["selfdrive_enabled", "lat_active", "steering", "gas", + "expected_op_engaged", "expected_driver_engaged"]) + def test_run_step_engagement(self, selfdrive_enabled, lat_active, steering, gas, + expected_op_engaged, expected_driver_engaged): + sm = _build_sm(selfdrive_enabled, lat_active, steering, gas) + dm = DriverMonitoring() + captured = {} + orig = dm._update_events - def spy(driver_engaged, op_engaged, lowspeed, wrong_gear): - captured['driver_engaged'] = driver_engaged - captured['op_engaged'] = op_engaged - return orig(driver_engaged, op_engaged, lowspeed, wrong_gear) + def spy(driver_engaged, op_engaged, lowspeed, wrong_gear): + captured['driver_engaged'] = driver_engaged + captured['op_engaged'] = op_engaged + return orig(driver_engaged, op_engaged, lowspeed, wrong_gear) - object.__setattr__(dm, '_update_events', spy) - dm.run_step(sm, demo=False) - assert captured['op_engaged'] == expected_op_engaged - assert captured['driver_engaged'] == expected_driver_engaged + object.__setattr__(dm, '_update_events', spy) + dm.run_step(sm, demo=False) + assert captured['op_engaged'] == expected_op_engaged + assert captured['driver_engaged'] == expected_driver_engaged diff --git a/openpilot/sunnypilot/mads/tests/test_mads_state_machine.py b/openpilot/sunnypilot/mads/tests/test_mads_state_machine.py index 14549119fd..0147f2f4fb 100644 --- a/openpilot/sunnypilot/mads/tests/test_mads_state_machine.py +++ b/openpilot/sunnypilot/mads/tests/test_mads_state_machine.py @@ -5,14 +5,13 @@ 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 pytest -from pytest_mock import MockerFixture from openpilot.cereal import custom from openpilot.common.realtime import DT_CTRL from openpilot.sunnypilot.mads.state import StateMachine, SOFT_DISABLE_TIME from openpilot.selfdrive.selfdrived.events import ET, NormalPermanentAlert, Events from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP, EVENTS_SP +from openpilot.common.test import OpenpilotTestCase State = custom.ModularAssistiveDrivingSystem.ModularAssistiveDrivingSystemState EventNameSP = custom.OnroadEventSP.EventName @@ -34,16 +33,16 @@ def make_event(event_types): class MockMADS: - def __init__(self, mocker: MockerFixture): + def __init__(self, mocker): self.selfdrive = mocker.MagicMock() self.selfdrive.state_machine = mocker.MagicMock() self.selfdrive.events = Events() self.selfdrive.events_sp = EventsSP() -class TestMADSStateMachine: - @pytest.fixture(autouse=True) - def setup_method(self, mocker: MockerFixture): +class TestMADSStateMachine(OpenpilotTestCase): + def setup_method(self): + mocker = self._fixture("mocker") self.mads = MockMADS(mocker) self.state_machine = StateMachine(self.mads) self.events = self.mads.selfdrive.events diff --git a/openpilot/sunnypilot/mads/tests/test_mads_steering_mode.py b/openpilot/sunnypilot/mads/tests/test_mads_steering_mode.py index 2bc6130ece..2058a8b10c 100644 --- a/openpilot/sunnypilot/mads/tests/test_mads_steering_mode.py +++ b/openpilot/sunnypilot/mads/tests/test_mads_steering_mode.py @@ -5,7 +5,7 @@ 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 pytest +from openpilot.common.parameterized import parameterized from openpilot.cereal import log, custom from opendbc.car import structs @@ -14,6 +14,7 @@ from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP from openpilot.sunnypilot.mads.helpers import MadsSteeringModeOnBrake, read_steering_mode_param from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem from opendbc.sunnypilot.car.tesla.values import MadsScreenButtonType, TeslaFlagsSP +from openpilot.common.test import OpenpilotTestCase State = custom.ModularAssistiveDrivingSystem.ModularAssistiveDrivingSystemState EventName = log.OnroadEvent.EventName @@ -80,8 +81,8 @@ def run_frames(mads, sd, cs, n=1): # should_silent_lkas_enable across all modes -class TestShouldSilentLkasEnable: - @pytest.mark.parametrize("brake,regen", [(True, False), (False, True)]) +class TestShouldSilentLkasEnable(OpenpilotTestCase): + @parameterized.expand([(True, False), (False, True)], names=["brake", "regen"]) def test_pause_blocks_reenable_on_braking_at_standstill(self, mocker, brake, regen): mads, _ = make_mads(mocker, MadsSteeringModeOnBrake.PAUSE) cs = make_car_state(brake_pressed=brake, regen_braking=regen, standstill=True) @@ -105,7 +106,7 @@ class TestShouldSilentLkasEnable: # pause -class TestPauseMode: +class TestPauseMode(OpenpilotTestCase): def test_stays_paused_at_standstill_brake_held(self, mocker): mads, sd = make_mads(mocker, MadsSteeringModeOnBrake.PAUSE) mads.state_machine.state = State.enabled @@ -150,7 +151,7 @@ class TestPauseMode: # disengage -class TestDisengageMode: +class TestDisengageMode(OpenpilotTestCase): def test_brake_while_enabled_disables(self, mocker): mads, sd = make_mads(mocker, MadsSteeringModeOnBrake.DISENGAGE) mads.state_machine.state = State.enabled @@ -174,7 +175,7 @@ class TestDisengageMode: # remain active -class TestRemainActiveMode: +class TestRemainActiveMode(OpenpilotTestCase): def test_brake_does_not_pause_or_disable(self, mocker): mads, sd = make_mads(mocker, MadsSteeringModeOnBrake.REMAIN_ACTIVE) mads.state_machine.state = State.enabled @@ -188,7 +189,7 @@ class TestRemainActiveMode: # lateral mismatch counter -class TestLateralMismatchCounter: +class TestLateralMismatchCounter(OpenpilotTestCase): def test_no_accumulation_while_paused(self, mocker): mads, sd = make_mads(mocker, MadsSteeringModeOnBrake.PAUSE) mads.state_machine.state = State.paused @@ -212,7 +213,7 @@ class TestLateralMismatchCounter: # brand restrictions -class TestBrandSteeringModeRestrictions: +class TestBrandSteeringModeRestrictions(OpenpilotTestCase): def test_rivian_forced_to_disengage(self, mocker): CP = structs.CarParams() CP.brand = "rivian" @@ -229,9 +230,9 @@ class TestBrandSteeringModeRestrictions: params = mocker.MagicMock() assert read_steering_mode_param(CP, CP_SP, params) == MadsSteeringModeOnBrake.DISENGAGE - @pytest.mark.parametrize("screen_button", [MadsScreenButtonType.THREE_FINGER, + @parameterized.expand([MadsScreenButtonType.THREE_FINGER, MadsScreenButtonType.FOUR_FINGER, - MadsScreenButtonType.FIVE_FINGER]) + MadsScreenButtonType.FIVE_FINGER], names=["screen_button"]) def test_tesla_with_vehicle_bus_uses_param(self, mocker, screen_button): CP = structs.CarParams() CP.brand = "tesla" @@ -250,7 +251,7 @@ class TestBrandSteeringModeRestrictions: "MadsSteeringMode": MadsSteeringModeOnBrake.REMAIN_ACTIVE}) assert read_steering_mode_param(CP, CP_SP, params) == MadsSteeringModeOnBrake.DISENGAGE - @pytest.mark.parametrize("brand", ["hyundai", "toyota", "honda", "gm"]) + @parameterized.expand(["hyundai", "toyota", "honda", "gm"], names=["brand"]) def test_other_brands_use_param(self, mocker, brand): CP = structs.CarParams() CP.brand = brand diff --git a/openpilot/sunnypilot/mapd/tests/test_mapd_version.py b/openpilot/sunnypilot/mapd/tests/test_mapd_version.py index 5619d2ec29..acbbe51c37 100644 --- a/openpilot/sunnypilot/mapd/tests/test_mapd_version.py +++ b/openpilot/sunnypilot/mapd/tests/test_mapd_version.py @@ -7,9 +7,10 @@ See the LICENSE.md file in the root directory for more details. from openpilot.sunnypilot import get_file_hash from openpilot.sunnypilot.mapd import MAPD_PATH from openpilot.sunnypilot.mapd.update_version import MAPD_HASH_PATH +from openpilot.common.test import OpenpilotTestCase -class TestMapdVersion: +class TestMapdVersion(OpenpilotTestCase): def test_compare_versions(self): mapd_hash = get_file_hash(MAPD_PATH) diff --git a/openpilot/sunnypilot/modeld_v2/tests/conftest.py b/openpilot/sunnypilot/modeld_v2/tests/helpers.py similarity index 97% rename from openpilot/sunnypilot/modeld_v2/tests/conftest.py rename to openpilot/sunnypilot/modeld_v2/tests/helpers.py index f79cbe10b2..6925a61f08 100644 --- a/openpilot/sunnypilot/modeld_v2/tests/conftest.py +++ b/openpilot/sunnypilot/modeld_v2/tests/helpers.py @@ -5,8 +5,9 @@ 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 pathlib import pickle -import pytest +import tempfile import openpilot.sunnypilot.models.helpers as helpers import openpilot.sunnypilot.modeld_v2.modeld as modeld_module @@ -181,16 +182,19 @@ def make_bundle(archetype): ) -@pytest.fixture +def tmp_path(): + with tempfile.TemporaryDirectory() as d: + yield pathlib.Path(d) + + def patch_modeld(monkeypatch): def _patch(bundle): - monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None: bundle, raising=False) - monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None: bundle, raising=False) + monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None: bundle) + monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None: bundle) return _patch -@pytest.fixture def model_state_factory(tmp_path, monkeypatch, patch_modeld): from openpilot.common.hardware import hw diff --git a/openpilot/sunnypilot/modeld_v2/tests/test_camera_offset_helper.py b/openpilot/sunnypilot/modeld_v2/tests/test_camera_offset_helper.py index d7e209390f..5398ac0ff1 100644 --- a/openpilot/sunnypilot/modeld_v2/tests/test_camera_offset_helper.py +++ b/openpilot/sunnypilot/modeld_v2/tests/test_camera_offset_helper.py @@ -9,6 +9,7 @@ import numpy as np from openpilot.common.transformations.camera import DEVICE_CAMERAS from openpilot.common.transformations.model import get_warp_matrix from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper +from openpilot.common.test import OpenpilotTestCase class MockStruct: @@ -20,7 +21,7 @@ class MockStruct: return getattr(self, item) -class TestCameraOffset: +class TestCameraOffset(OpenpilotTestCase): def setup_method(self): self.camera_offset = CameraOffsetHelper() self.dc = DEVICE_CAMERAS[('mici', 'os04c10')] diff --git a/openpilot/sunnypilot/modeld_v2/tests/test_combined_pkl_loader.py b/openpilot/sunnypilot/modeld_v2/tests/test_combined_pkl_loader.py index 3c544586f8..3396649a1d 100644 --- a/openpilot/sunnypilot/modeld_v2/tests/test_combined_pkl_loader.py +++ b/openpilot/sunnypilot/modeld_v2/tests/test_combined_pkl_loader.py @@ -5,20 +5,27 @@ 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 pytest +from openpilot.common.parameterized import parameterized import openpilot.sunnypilot.models.helpers as helpers import openpilot.sunnypilot.modeld_v2.modeld as modeld_module from openpilot.sunnypilot.modeld_v2.modeld import _find_driving_pkl -from openpilot.sunnypilot.modeld_v2.tests.conftest import DummyModel, DummyBundle, ARCHETYPES, CAM_W, CAM_H, \ +from openpilot.sunnypilot.modeld_v2.tests import helpers as tests_helpers +from openpilot.sunnypilot.modeld_v2.tests.helpers import DummyModel, DummyBundle, ARCHETYPES, CAM_W, CAM_H, \ SPLIT_VISION_INPUT_SHAPES, SPLIT_POLICY_INPUT_SHAPES +from openpilot.common.test import OpenpilotTestCase + +# resolved by name from this module when a test asks for them +tmp_path = tests_helpers.tmp_path +patch_modeld = tests_helpers.patch_modeld +model_state_factory = tests_helpers.model_state_factory ModelState = modeld_module.ModelState # Pkl discovery -class TestFindDrivingPkl: +class TestFindDrivingPkl(OpenpilotTestCase): def test_returns_none_when_no_bundle(self): assert _find_driving_pkl(None) is None @@ -49,16 +56,16 @@ class TestFindDrivingPkl: # Init — assertion guard -class TestModelStateCombinedInit: +class TestModelStateCombinedInit(OpenpilotTestCase): def test_asserts_when_no_pkl(self, monkeypatch): bundle = DummyBundle(models=[], is_20hz=True) - monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None: bundle, raising=False) - monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None: bundle, raising=False) - with pytest.raises(AssertionError, match="No driving pkl found"): + monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None: bundle) + monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None: bundle) + with self.assertRaisesRegex(AssertionError, "No driving pkl found"): ModelState(cam_w=CAM_W, cam_h=CAM_H) -class TestStockEquivalence: +class TestStockEquivalence(OpenpilotTestCase): def test_split_queue_keys_match_stock(self, model_state_factory): from openpilot.selfdrive.modeld.compile_modeld import make_input_queues @@ -100,8 +107,8 @@ class TestStockEquivalence: ARCHETYPE_NAMES = list(ARCHETYPES.keys()) -class TestModelTypeDetection: - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) +class TestModelTypeDetection(OpenpilotTestCase): + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_combined_model_type(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) @@ -109,8 +116,8 @@ class TestModelTypeDetection: f"{arch.name}: got {state._combined_model_type}, expected {arch.expected_model_type}" -class TestConstantsSelection: - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) +class TestConstantsSelection(OpenpilotTestCase): + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_constants_class(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) @@ -118,8 +125,8 @@ class TestConstantsSelection: f"{arch.name}: got {type(state.constants).__name__}, expected {arch.expected_constants_class.__name__}" -class TestParserSelection: - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) +class TestParserSelection(OpenpilotTestCase): + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_parser_module(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) @@ -128,8 +135,8 @@ class TestParserSelection: f"{arch.name}: parser from {parser_module}, expected module ending with {arch.expected_parser_module}" -class TestDesireKeyDetection: - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) +class TestDesireKeyDetection(OpenpilotTestCase): + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_desire_key(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) @@ -137,8 +144,8 @@ class TestDesireKeyDetection: f"{arch.name}: got {state.desire_key}, expected {arch.expected_desire_key}" -class TestVisionInputNames: - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) +class TestVisionInputNames(OpenpilotTestCase): + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_vision_names_contain_img(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) @@ -147,14 +154,14 @@ class TestVisionInputNames: assert 'img' in name, f"{arch.name}: vision input name '{name}' missing 'img'" -class TestOutputSlices: - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) +class TestOutputSlices(OpenpilotTestCase): + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_vision_slices_populated(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) assert len(state.vision_output_slices) > 0, f"{arch.name}: vision_output_slices empty" - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_policy_slices_match_type(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) @@ -164,14 +171,14 @@ class TestOutputSlices: assert len(state.policy_output_slices) > 0, f"{arch.name}: split/multi should have policy slices" -class TestInputQueueCreation: - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) +class TestInputQueueCreation(OpenpilotTestCase): + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_queues_not_empty(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) assert len(state.input_queues) > 0, f"{arch.name}: input_queues empty" - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_npy_contains_transforms(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) @@ -180,7 +187,7 @@ class TestInputQueueCreation: assert state.numpy_inputs['tfm'].shape == (3, 3) assert state.numpy_inputs['big_tfm'].shape == (3, 3) - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_npy_contains_desire(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) @@ -188,8 +195,8 @@ class TestInputQueueCreation: f"{arch.name}: '{arch.expected_desire_key}' missing from npy" -class TestFrameBufferParams: - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) +class TestFrameBufferParams(OpenpilotTestCase): + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_frame_buf_params_per_vision_input(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) @@ -199,29 +206,29 @@ class TestFrameBufferParams: assert len(nv12_info) >= 4, f"{arch.name}: nv12_info for '{name}' too short" -class TestBundleOverrides: - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) +class TestBundleOverrides(OpenpilotTestCase): + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_smoothing_params_from_overrides(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) assert state.LAT_SMOOTH_SECONDS == 0.1 assert state.LONG_SMOOTH_SECONDS == 0.3 - @pytest.mark.parametrize("archetype_name", ARCHETYPE_NAMES) + @parameterized.expand(ARCHETYPE_NAMES, names=["archetype_name"]) def test_generation_from_bundle(self, archetype_name, model_state_factory): arch = ARCHETYPES[archetype_name] state = model_state_factory(arch) assert state.generation == 10 -class TestMlsimProperty: +class TestMlsimProperty(OpenpilotTestCase): def test_mlsim_false_for_gen10(self, model_state_factory): state = model_state_factory(ARCHETYPES['supercombo_non20hz']) assert state.mlsim is False def test_mlsim_true_for_gen11(self, tmp_path, monkeypatch, patch_modeld): from openpilot.common.hardware import hw - from openpilot.sunnypilot.modeld_v2.tests.conftest import write_pkl, ARCHETYPES as A + from openpilot.sunnypilot.modeld_v2.tests.helpers import write_pkl, ARCHETYPES as A arch = A['supercombo_non20hz'] write_pkl(tmp_path, arch) @@ -233,10 +240,10 @@ class TestMlsimProperty: assert state.mlsim is True -class TestCrossArchetypeMismatch: +class TestCrossArchetypeMismatch(OpenpilotTestCase): def test_wrong_is_20hz_changes_constants(self, tmp_path, monkeypatch, patch_modeld): from openpilot.common.hardware import hw - from openpilot.sunnypilot.modeld_v2.tests.conftest import write_pkl + from openpilot.sunnypilot.modeld_v2.tests.helpers import write_pkl from openpilot.sunnypilot.modeld_v2.constants import ModelConstants arch = ARCHETYPES['vision_policy_split'] diff --git a/openpilot/sunnypilot/modeld_v2/tests/test_compile_modeld.py b/openpilot/sunnypilot/modeld_v2/tests/test_compile_modeld.py index c30dd46e33..885696b853 100644 --- a/openpilot/sunnypilot/modeld_v2/tests/test_compile_modeld.py +++ b/openpilot/sunnypilot/modeld_v2/tests/test_compile_modeld.py @@ -6,12 +6,13 @@ See the LICENSE.md file in the root directory for more details. """ import numpy as np -import pytest +from openpilot.common.parameterized import parameterized from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, _detect_desire_key +from openpilot.common.test import OpenpilotTestCase -class TestDeriveFrameSkip: +class TestDeriveFrameSkip(OpenpilotTestCase): def test_non20hz_supercombo(self): vision = {} policy = {'features_buffer': (1, 99, 512), 'desire': (1, 100, 8)} @@ -31,21 +32,21 @@ class TestDeriveFrameSkip: assert derive_frame_skip({}, {}) == 1 -class TestFrameSkipBufferLengthEquivalence: - @pytest.mark.parametrize("frame_skip,expected_buffer_length", [ +class TestFrameSkipBufferLengthEquivalence(OpenpilotTestCase): + @parameterized.expand([ (1, 2), (4, 5), - ]) + ], names=["frame_skip", "expected_buffer_length"]) def test_img_buffer_size_matches_warp_buffer_length(self, frame_skip, expected_buffer_length): n_frames = 2 img_buf_dim0 = frame_skip * (n_frames - 1) + 1 assert img_buf_dim0 == expected_buffer_length, \ f"frame_skip={frame_skip}: img_buf[0]={img_buf_dim0}, expected {expected_buffer_length}" - @pytest.mark.parametrize("is_20hz,expected_frame_skip,expected_buffer_length", [ + @parameterized.expand([ (False, 1, 2), (True, 4, 5), - ]) + ], names=["is_20hz", "expected_frame_skip", "expected_buffer_length"]) def test_is_20hz_to_frame_skip_to_buffer_length(self, is_20hz, expected_frame_skip, expected_buffer_length): if is_20hz: policy_shapes = {'features_buffer': (1, 24, 512)} @@ -59,7 +60,7 @@ class TestFrameSkipBufferLengthEquivalence: assert img_buf_dim0 == expected_buffer_length -class TestTemporalSamplingEquivalence: +class TestTemporalSamplingEquivalence(OpenpilotTestCase): def test_non20hz_desire_sampling_identity(self): buf = np.random.default_rng(0).standard_normal((100, 1, 8)).astype(np.float32) frame_skip = 1 @@ -94,12 +95,12 @@ class TestTemporalSamplingEquivalence: np.testing.assert_array_equal(sampled, buf[:, 0, :]) -class TestTemporalIdxEquivalence: - @pytest.mark.parametrize("mode,desire_shape,fb_shape,frame_skip", [ +class TestTemporalIdxEquivalence(OpenpilotTestCase): + @parameterized.expand([ ('non20hz', (1, 100, 8), (1, 99, 512), 1), ('20hz', (1, 25, 8), (1, 24, 512), 4), ('split', (1, 25, 8), (1, 25, 512), 4), - ]) + ], names=["mode", "desire_shape", "fb_shape", "frame_skip"]) def test_features_buffer_idx_equivalence(self, mode, desire_shape, fb_shape, frame_skip): history = fb_shape[1] @@ -118,11 +119,11 @@ class TestTemporalIdxEquivalence: assert len(modelstate_idxs) == fb_shape[1], \ f"{mode}: ModelState idx count {len(modelstate_idxs)} != input shape {fb_shape[1]}" - @pytest.mark.parametrize("mode,desire_shape,fb_shape,frame_skip", [ + @parameterized.expand([ ('non20hz', (1, 100, 8), (1, 99, 512), 1), ('20hz', (1, 25, 8), (1, 24, 512), 4), ('split', (1, 25, 8), (1, 25, 512), 4), - ]) + ], names=["mode", "desire_shape", "fb_shape", "frame_skip"]) def test_desire_idx_equivalence(self, mode, desire_shape, fb_shape, frame_skip): history = desire_shape[1] @@ -132,7 +133,7 @@ class TestTemporalIdxEquivalence: f"{mode}: compile desire samples {compile_sampled_count} != model input {history}" -class TestDetectDesireKey: +class TestDetectDesireKey(OpenpilotTestCase): def test_finds_desire(self): shapes = {'features_buffer': (1, 99, 512), 'desire': (1, 100, 8), 'traffic_convention': (1, 2)} assert _detect_desire_key(shapes) == 'desire' @@ -146,7 +147,7 @@ class TestDetectDesireKey: assert _detect_desire_key(shapes) is None -class TestOutputSlicePreservation: +class TestOutputSlicePreservation(OpenpilotTestCase): def test_vision_hidden_state_slice_used_for_features(self): mock_slices = {'hidden_state': slice(0, 512), 'plan': slice(512, 1024)} features_slice = mock_slices['hidden_state'] diff --git a/openpilot/sunnypilot/modeld_v2/tests/test_recovery_power.py b/openpilot/sunnypilot/modeld_v2/tests/test_recovery_power.py index d7c8563e64..85305395ea 100644 --- a/openpilot/sunnypilot/modeld_v2/tests/test_recovery_power.py +++ b/openpilot/sunnypilot/modeld_v2/tests/test_recovery_power.py @@ -7,6 +7,7 @@ from openpilot.cereal import log from openpilot.sunnypilot.modeld_v2.constants import Plan from openpilot.sunnypilot.modeld_v2.modeld import ModelState import openpilot.sunnypilot.modeld_v2.modeld as modeld +from openpilot.common.test import OpenpilotTestCase class MockStruct: @@ -15,58 +16,59 @@ class MockStruct: setattr(self, k, v) -def test_recovery_power_scaling(): - state: Any = MockStruct( - PLANPLUS_CONTROL=0.75, - LONG_SMOOTH_SECONDS=0.3, - LAT_SMOOTH_SECONDS=0.1, - MIN_LAT_CONTROL_SPEED=0.3, - mlsim=True, - generation=12, - constants=MockStruct(T_IDXS=np.arange(100), DESIRE_LEN=8) - ) - prev_action = log.ModelDataV2.Action() - recorded_vel: list = [] - recorded_curv_plans: list = [] +class TestRecoveryPower(OpenpilotTestCase): + def test_recovery_power_scaling(self): + state: Any = MockStruct( + PLANPLUS_CONTROL=0.75, + LONG_SMOOTH_SECONDS=0.3, + LAT_SMOOTH_SECONDS=0.1, + MIN_LAT_CONTROL_SPEED=0.3, + mlsim=True, + generation=12, + constants=MockStruct(T_IDXS=np.arange(100), DESIRE_LEN=8) + ) + prev_action = log.ModelDataV2.Action() + recorded_vel: list = [] + recorded_curv_plans: list = [] - def mock_accel(plan_vel, plan_accel, t_idxs, action_t=0.0): - recorded_vel.append(plan_vel.copy()) - return 0.0, False + def mock_accel(plan_vel, plan_accel, t_idxs, action_t=0.0): + recorded_vel.append(plan_vel.copy()) + return 0.0, False - def mock_curvature(output, plan, vego, lat_action_t, mlsim): - recorded_curv_plans.append(plan.copy()) - return 0.0 + def mock_curvature(output, plan, vego, lat_action_t, mlsim): + recorded_curv_plans.append(plan.copy()) + return 0.0 - modeld.get_accel_from_plan = mock_accel # ty: ignore[invalid-assignment] - modeld.get_curvature_from_output = mock_curvature # ty: ignore[invalid-assignment] - plan = np.random.default_rng(0).random((1, 100, 15)).astype(np.float32) - planplus = np.random.default_rng(1).random((1, 100, 15)).astype(np.float32) - merged_plan = plan + planplus + modeld.get_accel_from_plan = mock_accel # ty: ignore[invalid-assignment] + modeld.get_curvature_from_output = mock_curvature # ty: ignore[invalid-assignment] + plan = np.random.default_rng(0).random((1, 100, 15)).astype(np.float32) + planplus = np.random.default_rng(1).random((1, 100, 15)).astype(np.float32) + merged_plan = plan + planplus - model_output: dict = { - 'plan': merged_plan.copy(), - 'planplus': planplus.copy() - } + model_output: dict = { + 'plan': merged_plan.copy(), + 'planplus': planplus.copy() + } - test_cases: list = [ - # (control, v_ego) - (0.55, 20.0), - (1.0, 25.0), - (1.5, 25.1), - (2.0, 20.0), - (0.75, 19.0), - (0.8, 25.1), - ] + test_cases: list = [ + # (control, v_ego) + (0.55, 20.0), + (1.0, 25.0), + (1.5, 25.1), + (2.0, 20.0), + (0.75, 19.0), + (0.8, 25.1), + ] - for control, v_ego in test_cases: - state.PLANPLUS_CONTROL = control - recorded_vel.clear() - recorded_curv_plans.clear() - ModelState.get_action_from_model(state, model_output, prev_action, 0.0, 0.0, v_ego) # type: ignore[arg-type] + for control, v_ego in test_cases: + state.PLANPLUS_CONTROL = control + recorded_vel.clear() + recorded_curv_plans.clear() + ModelState.get_action_from_model(state, model_output, prev_action, 0.0, 0.0, v_ego) # type: ignore[arg-type] - expected_accel_plan_vel = plan[0, :, Plan.VELOCITY][:, 0] + planplus[0, :, Plan.VELOCITY][:, 0] - np.testing.assert_allclose(recorded_vel[0], expected_accel_plan_vel, rtol=1e-5, atol=1e-6) + expected_accel_plan_vel = plan[0, :, Plan.VELOCITY][:, 0] + planplus[0, :, Plan.VELOCITY][:, 0] + np.testing.assert_allclose(recorded_vel[0], expected_accel_plan_vel, rtol=1e-5, atol=1e-6) - # For the below, yes, I know this isn't the same slicing as fillmodlmsg. This is to show that the values are only scaled on curv - expected_curv_plan_vel = plan[0, :, Plan.VELOCITY][:, 0] + control * planplus[0, :, Plan.VELOCITY][:, 0] - np.testing.assert_allclose(recorded_curv_plans[0][:, Plan.VELOCITY][:, 0], expected_curv_plan_vel, rtol=1e-5, atol=1e-6) + # For the below, yes, I know this isn't the same slicing as fillmodlmsg. This is to show that the values are only scaled on curv + expected_curv_plan_vel = plan[0, :, Plan.VELOCITY][:, 0] + control * planplus[0, :, Plan.VELOCITY][:, 0] + np.testing.assert_allclose(recorded_curv_plans[0][:, Plan.VELOCITY][:, 0], expected_curv_plan_vel, rtol=1e-5, atol=1e-6) diff --git a/openpilot/sunnypilot/models/tests/test_default_model.py b/openpilot/sunnypilot/models/tests/test_default_model.py index ab51027442..450237e0e9 100644 --- a/openpilot/sunnypilot/models/tests/test_default_model.py +++ b/openpilot/sunnypilot/models/tests/test_default_model.py @@ -8,9 +8,10 @@ See the LICENSE.md file in the root directory for more details. from openpilot.sunnypilot import get_file_hash from openpilot.sunnypilot.models.default_model import MODEL_HASH_PATH, SUPERCOMBO_ONNX_PATH import hashlib +from openpilot.common.test import OpenpilotTestCase -class TestDefaultModel: +class TestDefaultModel(OpenpilotTestCase): def test_compare_onnx_hashes(self): supercombo_hash = get_file_hash(SUPERCOMBO_ONNX_PATH) diff --git a/openpilot/sunnypilot/models/tests/test_tinygrad_ref.py b/openpilot/sunnypilot/models/tests/test_tinygrad_ref.py index 3e60ab5308..1712c60410 100644 --- a/openpilot/sunnypilot/models/tests/test_tinygrad_ref.py +++ b/openpilot/sunnypilot/models/tests/test_tinygrad_ref.py @@ -2,6 +2,7 @@ import requests from openpilot.sunnypilot.models.tinygrad_ref import get_tinygrad_ref from openpilot.sunnypilot.models.fetcher import ModelFetcher +from openpilot.common.test import OpenpilotTestCase def fetch_tinygrad_ref(): @@ -11,13 +12,14 @@ def fetch_tinygrad_ref(): return json_data.get("tinygrad_ref") -def test_tinygrad_ref(): - current_ref = get_tinygrad_ref() - remote_ref = fetch_tinygrad_ref() - assert remote_ref == current_ref, ( - f"""tinygrad_repo ref does not match remote tinygrad_ref of current compiled driving models json. - Current: {current_ref} - Remote: {remote_ref} - Please run build-all workflow to update models.""" - ) - print("tinygrad_repo ref matches current compiled driving models json ref.") +class TestTinygradRef(OpenpilotTestCase): + def test_tinygrad_ref(self): + current_ref = get_tinygrad_ref() + remote_ref = fetch_tinygrad_ref() + assert remote_ref == current_ref, ( + f"""tinygrad_repo ref does not match remote tinygrad_ref of current compiled driving models json. + Current: {current_ref} + Remote: {remote_ref} + Please run build-all workflow to update models.""" + ) + print("tinygrad_repo ref matches current compiled driving models json ref.") diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/dec/tests/pytest_dynamic_controller.py b/openpilot/sunnypilot/selfdrive/controls/lib/dec/tests/pytest_dynamic_controller.py deleted file mode 100644 index 407ed0af3a..0000000000 --- a/openpilot/sunnypilot/selfdrive/controls/lib/dec/tests/pytest_dynamic_controller.py +++ /dev/null @@ -1,94 +0,0 @@ -import pytest - -from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController - -class MockLeadOne: - def __init__(self, status=0.0): - self.status = status - -class MockRadarState: - def __init__(self, status=0.0): - self.leadOne = MockLeadOne(status=status) - -class MockCarState: - def __init__(self, vEgo=0.0, vCruise=0.0, standstill=False): - self.vEgo = vEgo - self.vCruise = vCruise - self.standstill = standstill - -class MockModelData: - def __init__(self, valid=True): - size = 33 if valid else 10 # incomplete if invalid - self.position = type("Pos", (), {"x": [0.0] * size})() - self.orientation = type("Ori", (), {"x": [0.0] * size})() - -class MockSelfDriveState: - def __init__(self, experimentalMode=False): - self.experimentalMode = experimentalMode - -class MockParams: - def get_bool(self, name): - return True - -@pytest.fixture -def default_sm(): - sm = { - 'carState': MockCarState(vEgo=10.0, vCruise=20.0), - 'radarState': MockRadarState(status=1.0), - 'modelV2': MockModelData(valid=True), - 'selfdriveState': MockSelfDriveState(experimentalMode=True), - } - return sm - -@pytest.fixture -def mock_cp(): - class CP: - radarUnavailable = False - return CP() - -@pytest.fixture -def mock_mpc(): - class MPC: - crash_cnt = 0 - return MPC() - -# Fake Kalman Filter that always returns a given value -class FakeKalman: - def __init__(self, value=1.0): - self.value = value - def add_data(self, v): pass - def get_value(self): return self.value - def get_confidence(self): return 1.0 - def reset_data(self): pass - -def test_initial_mode_is_acc(mock_cp, mock_mpc): - controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams()) - assert controller.mode() == "acc" - -def test_standstill_triggers_blended(mock_cp, mock_mpc, default_sm): - controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams()) - default_sm['carState'].standstill = True - for _ in range(10): - controller.update(default_sm) - assert controller.mode() == "blended" - -def test_emergency_blended_on_fcw(mock_cp, mock_mpc, default_sm): - controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams()) - mock_mpc.crash_cnt = 1 # simulate FCW - for _ in range(2): - controller.update(default_sm) - assert controller.mode() == "blended" - -def test_radarless_slowdown_triggers_blended(mock_cp, mock_mpc, default_sm): - mock_cp.radarUnavailable = True - controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams()) - - # Force conditions to simulate slowdown - controller._slow_down_filter = FakeKalman(value=1.0) # ty: ignore[invalid-assignment] - controller._v_ego_kph = 35.0 - default_sm['modelV2'] = MockModelData(valid=False) # Incomplete trajectory - - for _ in range(3): - controller.update(default_sm) - - assert controller.mode() == "blended" diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/dec/tests/test_dynamic_controller.py b/openpilot/sunnypilot/selfdrive/controls/lib/dec/tests/test_dynamic_controller.py new file mode 100644 index 0000000000..4fec6eaa52 --- /dev/null +++ b/openpilot/sunnypilot/selfdrive/controls/lib/dec/tests/test_dynamic_controller.py @@ -0,0 +1,91 @@ +from openpilot.common.test import OpenpilotTestCase +from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController + +class MockLeadOne: + def __init__(self, present=0.0): + self.present = present + +class MockRadarState: + def __init__(self, present=0.0): + self.leadOne = MockLeadOne(present=present) + +class MockCarState: + def __init__(self, vEgo=0.0, vCruise=0.0, standstill=False): + self.vEgo = vEgo + self.vCruise = vCruise + self.standstill = standstill + +class MockModelData: + def __init__(self, valid=True): + size = 33 if valid else 10 # incomplete if invalid + self.position = type("Pos", (), {"x": [0.0] * size})() + self.orientation = type("Ori", (), {"x": [0.0] * size})() + +class MockSelfDriveState: + def __init__(self, experimentalMode=False): + self.experimentalMode = experimentalMode + +class MockParams: + def get_bool(self, name): + return True + +def default_sm(): + sm = { + 'carState': MockCarState(vEgo=10.0, vCruise=20.0), + 'radarState': MockRadarState(present=1.0), + 'modelV2': MockModelData(valid=True), + 'selfdriveState': MockSelfDriveState(experimentalMode=True), + } + return sm + +def mock_cp(): + class CP: + radarUnavailable = False + return CP() + +def mock_mpc(): + class MPC: + crash_cnt = 0 + return MPC() + +# Fake Kalman Filter that always returns a given value +class FakeKalman: + def __init__(self, value=1.0): + self.value = value + def add_data(self, v): pass + def get_value(self): return self.value + def get_confidence(self): return 1.0 + def reset_data(self): pass + +class TestDynamicExperimentalController(OpenpilotTestCase): + def test_initial_mode_is_acc(self, mock_cp, mock_mpc): + controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams()) + assert controller.mode() == "acc" + + def test_standstill_triggers_blended(self, mock_cp, mock_mpc, default_sm): + controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams()) + default_sm['carState'].standstill = True + for _ in range(10): + controller.update(default_sm) + assert controller.mode() == "blended" + + def test_emergency_blended_on_fcw(self, mock_cp, mock_mpc, default_sm): + controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams()) + mock_mpc.crash_cnt = 1 # simulate FCW + for _ in range(2): + controller.update(default_sm) + assert controller.mode() == "blended" + + def test_radarless_slowdown_triggers_blended(self, mock_cp, mock_mpc, default_sm): + mock_cp.radarUnavailable = True + controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams()) + + # Force conditions to simulate slowdown + controller._slow_down_filter = FakeKalman(value=1.0) # ty: ignore[invalid-assignment] + controller._v_ego_kph = 35.0 + default_sm['modelV2'] = MockModelData(valid=False) # Incomplete trajectory + + for _ in range(3): + controller.update(default_sm) + + assert controller.mode() == "blended" diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_fingerprint.py b/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_fingerprint.py index 07e7d2852d..8eaa90babd 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_fingerprint.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_fingerprint.py @@ -7,6 +7,7 @@ from opendbc.car.tesla.values import CAR as TESLA from openpilot.common.parameterized import parameterized from openpilot.common.params import Params from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces +from openpilot.common.test import OpenpilotTestCase FINGERPRINT_EXACT_MATCH = [HONDA.HONDA_CIVIC_BOSCH, TOYOTA.TOYOTA_RAV4_TSS2_2022, HYUNDAI.HYUNDAI_IONIQ_5] @@ -14,7 +15,7 @@ FINGERPRINT_FUZZY_MATCH = [HONDA.HONDA_CIVIC_BOSCH_DIESEL, HYUNDAI.GENESIS_G70_2 FINGERPRINT_ANGLE_NO_MATCH = [TOYOTA.TOYOTA_RAV4_TSS2_2023, NISSAN.NISSAN_LEAF, TESLA.TESLA_MODEL_3] -class TestNNLCFingerprintBase: +class TestNNLCFingerprintBase(OpenpilotTestCase): @staticmethod def _setup_platform(car_name): diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_load_model.py b/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_load_model.py index 2f4e0d6993..4e0da4eeed 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_load_model.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_load_model.py @@ -8,9 +8,10 @@ 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.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces +from openpilot.common.test import OpenpilotTestCase -class TestNNTorqueModel: +class TestNNTorqueModel(OpenpilotTestCase): @parameterized.expand([HONDA.HONDA_CIVIC, TOYOTA.TOYOTA_RAV4, HYUNDAI.HYUNDAI_SANTA_CRUZ_1ST_GEN]) def test_load_model(self, car_name): diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_nnlc.py b/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_nnlc.py index ca5127c1bc..4782b0c4f3 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_nnlc.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_nnlc.py @@ -17,6 +17,7 @@ from openpilot.selfdrive.locationd.helpers import Pose from openpilot.common.mock.generators import generate_deviceMotion from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces from openpilot.selfdrive.modeld.constants import ModelConstants +from openpilot.common.test import OpenpilotTestCase def generate_modelV2(): @@ -42,7 +43,7 @@ def generate_modelV2(): return model -class TestNeuralNetworkLateralControl: +class TestNeuralNetworkLateralControl(OpenpilotTestCase): @parameterized.expand([HONDA.HONDA_CIVIC, TOYOTA.TOYOTA_RAV4, HYUNDAI.HYUNDAI_SANTA_CRUZ_1ST_GEN, GM.CHEVROLET_BOLT_EUV]) def test_saturation(self, car_name): @@ -81,7 +82,7 @@ class TestNeuralNetworkLateralControl: for _ in range(1000): controller.extension.update_model_v2(model_v2) controller.extension.update_lateral_lag(test_lag) - controller.update_live_torque_params(torque_params.latAccelFactor, torque_params.latAccelOffset, torque_params.friction) + controller.update_torque_parameters(torque_params.latAccelFactor, torque_params.latAccelOffset, torque_params.friction) controller.extension.update_limits() _, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, True, 0.2) assert lac_log.saturated @@ -89,7 +90,7 @@ class TestNeuralNetworkLateralControl: for _ in range(1000): controller.extension.update_model_v2(model_v2) controller.extension.update_lateral_lag(test_lag) - controller.update_live_torque_params(torque_params.latAccelFactor, torque_params.latAccelOffset, torque_params.friction) + controller.update_torque_parameters(torque_params.latAccelFactor, torque_params.latAccelOffset, torque_params.friction) controller.extension.update_limits() _, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, False, 0.2) assert not lac_log.saturated @@ -97,7 +98,7 @@ class TestNeuralNetworkLateralControl: for _ in range(1000): controller.extension.update_model_v2(model_v2) controller.extension.update_lateral_lag(test_lag) - controller.update_live_torque_params(torque_params.latAccelFactor, torque_params.latAccelOffset, torque_params.friction) + controller.update_torque_parameters(torque_params.latAccelFactor, torque_params.latAccelOffset, torque_params.friction) controller.extension.update_limits() _, _, lac_log = controller.update(True, CS, VM, params, False, 1, pose, False, 0.2) assert lac_log.saturated diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/tests/test_map_controller.py b/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/tests/test_map_controller.py index dc27447c0b..a9e5cdd992 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/tests/test_map_controller.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/tests/test_map_controller.py @@ -8,18 +8,18 @@ import json import math import platform -import pytest from openpilot.cereal import custom from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET from openpilot.sunnypilot.selfdrive.controls.lib.smart_cruise_control.map_controller import R, SmartCruiseControlMap +from openpilot.common.test import OpenpilotTestCase MapState = VisionState = custom.LongitudinalPlanSP.SmartCruiseControl.MapState -class TestSmartCruiseControlMap: +class TestSmartCruiseControlMap(OpenpilotTestCase): def setup_method(self): self.params = Params() @@ -70,6 +70,6 @@ class TestSmartCruiseControlMap: self.scc_m.update(True, False, 25.0, 0.0, 30.0) - assert self.scc_m.v_target == pytest.approx(24.0) + self.assertAlmostEqual(self.scc_m.v_target, 24.0, delta=24.0 * 1e-6) # TODO-SP: mock data from modelV2 to test other states diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/tests/test_vision_controller.py b/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/tests/test_vision_controller.py index 98b3ffc8e7..610acd47df 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/tests/test_vision_controller.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/smart_cruise_control/tests/test_vision_controller.py @@ -7,7 +7,7 @@ See the LICENSE.md file in the root directory for more details. from typing import Any import numpy as np -import pytest +from openpilot.common.parameterized import parameterized import openpilot.cereal.messaging as messaging from openpilot.cereal import custom, log @@ -17,6 +17,7 @@ from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.sunnypilot.selfdrive.controls.lib.smart_cruise_control import MIN_V from openpilot.sunnypilot.selfdrive.controls.lib.smart_cruise_control.vision_controller import SmartCruiseControlVision, _ENTERING_PRED_LAT_ACC_TH +from openpilot.common.test import OpenpilotTestCase VisionState = custom.LongitudinalPlanSP.SmartCruiseControl.VisionState @@ -105,7 +106,7 @@ def generate_controlsState(): return controls_state -class TestSmartCruiseControlVision: +class TestSmartCruiseControlVision(OpenpilotTestCase): def setup_method(self): self.params = Params() @@ -145,19 +146,11 @@ class TestSmartCruiseControlVision: self.scc_v.update(self.sm, True, False, 0., 0., 0.) assert self.scc_v.state == VisionState.enabled - @pytest.mark.parametrize( - "case, should_enter", - [ + @parameterized.expand([ ("p97_just_above_threshold", True), ("single_spike_filtered", False), ("persistent_high_values", True), - ], - ids=[ - "p97>threshold_enters", - "single_spike_max_large_but_p97_below_threshold", - "high_values_persist_trigger_entering", - ], - ) + ], names=["case", "should_enter"]) def test_max_pred_lat_acc_uses_p97_and_threshold(self, case, should_enter): n = len(ModelConstants.T_IDXS) th = float(_ENTERING_PRED_LAT_ACC_TH) diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py b/openpilot/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py index 1f23fb2299..f3d32a4d5b 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py @@ -7,7 +7,7 @@ See the LICENSE.md file in the root directory for more details. import time -import pytest +from openpilot.common.parameterized import parameterized from openpilot.cereal import custom from opendbc.car.car_helpers import interfaces @@ -27,6 +27,7 @@ from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_assist PRE_ACTIVE_GUARD_PERIOD, ACTIVE_STATES, CRUISE_BUTTON_CONFIRM_HOLD from openpilot.sunnypilot.selfdrive.selfdrived.button_state_tracker import ButtonStateTracker from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP +from openpilot.common.test import OpenpilotTestCase ButtonEvent = car.CarState.ButtonEvent ButtonType = car.CarState.ButtonEvent.Type @@ -45,21 +46,10 @@ SPEED_LIMITS = { DEFAULT_CAR = TOYOTA.TOYOTA_RAV4_TSS2 -@pytest.fixture -def car_name(request): - return getattr(request, "param", DEFAULT_CAR) +class TestSpeedLimitAssist(OpenpilotTestCase): + car_name = DEFAULT_CAR - -@pytest.fixture(autouse=True) -def set_car_name_on_instance(request, car_name): - instance = getattr(request, "instance", None) - if instance: - instance.car_name = car_name - - -class TestSpeedLimitAssist: - - def setup_method(self, method): + def setup_method(self): self.params = Params() self.reset_custom_params() self.events_sp = EventsSP() @@ -69,7 +59,7 @@ class TestSpeedLimitAssist: self.pcm_long_max_set_speed = PCM_LONG_REQUIRED_MAX_SET_SPEED[self.sla.is_metric][1] # use 80 MPH for now self.speed_conv = CV.MS_TO_KPH if self.sla.is_metric else CV.MS_TO_MPH - def teardown_method(self, method): + def teardown_method(self): self.reset_state() def _setup_platform(self, car_name): @@ -112,13 +102,16 @@ class TestSpeedLimitAssist: assert not self.sla.is_active assert V_CRUISE_UNSET == self.sla.get_v_target_from_control() - @pytest.mark.parametrize("car_name", [RIVIAN.RIVIAN_R1, TESLA.TESLA_MODEL_Y], indirect=True) + @parameterized.expand([RIVIAN.RIVIAN_R1, TESLA.TESLA_MODEL_Y], names=["car_name"]) def test_disallowed_brands(self, car_name): """ Speed Limit Assist is disabled for the following brands and conditions: - All Tesla and is a release branch; - All Rivian """ + self.car_name = car_name + self.openpilot_setup_method() # rebuild the platform for this brand + assert not self.sla.enabled # stay disallowed even when the param may have changed from somewhere else @@ -285,9 +278,10 @@ class TestSpeedLimitAssist: assert self.sla.state in ACTIVE_STATES -class TestButtonStateTrackerSLAIntegration: +class TestButtonStateTrackerSLAIntegration(OpenpilotTestCase): + + def setup_method(self): - def setup_method(self, method): self.tracker = ButtonStateTracker() self.params = Params() self.params.put("IsReleaseSpBranch", True, block=True) diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_resolver.py b/openpilot/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_resolver.py index a18880c620..884749c72e 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_resolver.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_resolver.py @@ -7,26 +7,26 @@ See the LICENSE.md file in the root directory for more details. import random import time -import pytest -from pytest_mock import MockerFixture +from openpilot.common.parameterized import parameterized from openpilot.cereal import custom from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import LIMIT_MAX_MAP_DATA_AGE from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_resolver import SpeedLimitResolver, ALL_SOURCES from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Policy +from openpilot.common.test import OpenpilotTestCase SpeedLimitSource = custom.LongitudinalPlanSP.SpeedLimit.Source -def create_mock(properties, mocker: MockerFixture): +def create_mock(properties, mocker): mock = mocker.MagicMock() for _property, value in properties.items(): setattr(mock, _property, value) return mock -def setup_sm_mock(mocker: MockerFixture): +def setup_sm_mock(mocker): cruise_speed_limit = random.uniform(0, 120) live_map_data_limit = random.uniform(0, 120) @@ -58,21 +58,24 @@ def setup_sm_mock(mocker: MockerFixture): return sm_mock -parametrized_policies = pytest.mark.parametrize( - "policy, sm_key, function_key", [ +parametrized_policies = parameterized.expand( + [ (Policy.car_state_only, 'carStateSP', SpeedLimitSource.car), (Policy.car_state_priority, 'carStateSP', SpeedLimitSource.car), (Policy.map_data_only, 'liveMapDataSP', SpeedLimitSource.map), (Policy.map_data_priority, 'liveMapDataSP', SpeedLimitSource.map), ], - ids=lambda val: val.name if hasattr(val, 'name') else str(val) + names=["policy", "sm_key", "function_key"] ) -@pytest.mark.parametrize("resolver_class", [SpeedLimitResolver]) -class TestSpeedLimitResolverValidation: +def resolver_class(): + return SpeedLimitResolver - @pytest.mark.parametrize("policy", list(Policy), ids=lambda policy: policy.name) + +class TestSpeedLimitResolverValidation(OpenpilotTestCase): + + @parameterized.expand(list(Policy), names=["policy"]) def test_initial_state(self, resolver_class, policy): resolver = resolver_class() resolver.policy = policy @@ -82,7 +85,7 @@ class TestSpeedLimitResolverValidation: assert resolver.distance_solutions[source] == 0. @parametrized_policies - def test_resolver(self, resolver_class, policy, sm_key, function_key, mocker: MockerFixture): + def test_resolver(self, resolver_class, policy, sm_key, function_key, mocker): resolver = resolver_class() resolver.policy = policy sm_mock = setup_sm_mock(mocker) @@ -93,7 +96,7 @@ class TestSpeedLimitResolverValidation: assert resolver.speed_limit == source_speed_limit assert resolver.source == ALL_SOURCES[function_key] - def test_resolver_combined(self, resolver_class, mocker: MockerFixture): + def test_resolver_combined(self, resolver_class, mocker): resolver = resolver_class() resolver.policy = Policy.combined sm_mock = setup_sm_mock(mocker) @@ -108,7 +111,7 @@ class TestSpeedLimitResolverValidation: assert resolver.source == socket_to_source[minimum_key] @parametrized_policies - def test_parser(self, resolver_class, policy, sm_key, function_key, mocker: MockerFixture): + def test_parser(self, resolver_class, policy, sm_key, function_key, mocker): resolver = resolver_class() resolver.policy = policy sm_mock = setup_sm_mock(mocker) @@ -119,8 +122,8 @@ class TestSpeedLimitResolverValidation: assert resolver.limit_solutions[ALL_SOURCES[function_key]] == source_speed_limit assert resolver.distance_solutions[ALL_SOURCES[function_key]] == 0. - @pytest.mark.parametrize("policy", list(Policy), ids=lambda policy: policy.name) - def test_resolve_interaction_in_update(self, resolver_class, policy, mocker: MockerFixture): + @parameterized.expand(list(Policy), names=["policy"]) + def test_resolve_interaction_in_update(self, resolver_class, policy, mocker): v_ego = 50 resolver = resolver_class() resolver.policy = policy @@ -133,8 +136,8 @@ class TestSpeedLimitResolverValidation: assert resolver.distance is not None assert resolver.source is not None - @pytest.mark.parametrize("policy", list(Policy), ids=lambda policy: policy.name) - def test_old_map_data_ignored(self, resolver_class, policy, mocker: MockerFixture): + @parameterized.expand(list(Policy), names=["policy"]) + def test_old_map_data_ignored(self, resolver_class, policy, mocker): resolver = resolver_class() resolver.policy = policy sm_mock = mocker.MagicMock() diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_auto_lane_change.py b/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_auto_lane_change.py index a0b78249b3..0d95550412 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_auto_lane_change.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_auto_lane_change.py @@ -9,6 +9,7 @@ from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper, LaneChangeState, LaneChangeDirection from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode, \ AUTO_LANE_CHANGE_TIMER, ONE_SECOND_DELAY +from openpilot.common.test import OpenpilotTestCase AUTO_LANE_CHANGE_TIMER_COMBOS = [ (AutoLaneChangeMode.NUDGELESS, AUTO_LANE_CHANGE_TIMER[AutoLaneChangeMode.NUDGELESS]), @@ -19,7 +20,7 @@ AUTO_LANE_CHANGE_TIMER_COMBOS = [ ] -class TestAutoLaneChangeController: +class TestAutoLaneChangeController(OpenpilotTestCase): def setup_method(self): self.DH = DesireHelper() self.alc = AutoLaneChangeController(self.DH) diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_blinker_pause_lateral.py b/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_blinker_pause_lateral.py index 7a72cfa1f2..4f0e6c03df 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_blinker_pause_lateral.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_blinker_pause_lateral.py @@ -8,9 +8,10 @@ from opendbc.car.structs import car from openpilot.common.constants import CV from openpilot.sunnypilot.selfdrive.controls.lib.blinker_pause_lateral import BlinkerPauseLateral +from openpilot.common.test import OpenpilotTestCase -class TestBlinkerPauseLateral: +class TestBlinkerPauseLateral(OpenpilotTestCase): def setup_method(self): self.blinker_pause_lateral = BlinkerPauseLateral() diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_lane_turn_desire.py b/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_lane_turn_desire.py index 434b12110b..52c6cecbfc 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_lane_turn_desire.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_lane_turn_desire.py @@ -1,6 +1,7 @@ -import pytest from openpilot.cereal import log, custom from openpilot.common.params import Params +from openpilot.common.parameterized import parameterized +from openpilot.common.test import OpenpilotTestCase from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper, LaneChangeState, LaneChangeDirection from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController, LANE_CHANGE_SPEED_MIN @@ -10,65 +11,63 @@ from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLan TurnDirection = custom.ModelDataV2SP.TurnDirection -@pytest.mark.parametrize("left_blinker,right_blinker,v_ego,blindspot_left,blindspot_right,expected", [ - (True, False, 5, False, False, TurnDirection.turnLeft), - (False, True, 6, False, False, TurnDirection.turnRight), - (True, False, 9, False, False, TurnDirection.none), - (True, False, 7, True, False, TurnDirection.none), - (False, True, 6, False, True, TurnDirection.none), - (False, False, 5, False, False, TurnDirection.none), - (True, True, 5, False, False, TurnDirection.none), -]) -def test_lane_turn_desire_conditions(left_blinker, right_blinker, v_ego, blindspot_left, blindspot_right, expected): - dh = DesireHelper() - controller = LaneTurnController(dh) - controller.enabled = True - controller.lane_turn_value = LANE_CHANGE_SPEED_MIN - controller.turn_direction = TurnDirection.none - controller.update_lane_turn(blindspot_left, blindspot_right, left_blinker, right_blinker, v_ego) - assert controller.get_turn_direction() == expected +class TestLaneTurnDesire(OpenpilotTestCase): + @parameterized.expand([ + (True, False, 5, False, False, TurnDirection.turnLeft), + (False, True, 6, False, False, TurnDirection.turnRight), + (True, False, 9, False, False, TurnDirection.none), + (True, False, 7, True, False, TurnDirection.none), + (False, True, 6, False, True, TurnDirection.none), + (False, False, 5, False, False, TurnDirection.none), + (True, True, 5, False, False, TurnDirection.none), + ]) + def test_lane_turn_desire_conditions(self, left_blinker, right_blinker, v_ego, blindspot_left, blindspot_right, expected): + dh = DesireHelper() + controller = LaneTurnController(dh) + controller.enabled = True + controller.lane_turn_value = LANE_CHANGE_SPEED_MIN + controller.turn_direction = TurnDirection.none + controller.update_lane_turn(blindspot_left, blindspot_right, left_blinker, right_blinker, v_ego) + assert controller.get_turn_direction() == expected + def test_lane_turn_desire_disabled(self): + dh = DesireHelper() + controller = LaneTurnController(dh) + controller.enabled = False + controller.lane_turn_value = LANE_CHANGE_SPEED_MIN + controller.turn_direction = TurnDirection.none + controller.update_lane_turn(False, False, True, False, 7) + assert controller.get_turn_direction() == TurnDirection.none -def test_lane_turn_desire_disabled(): - dh = DesireHelper() - controller = LaneTurnController(dh) - controller.enabled = False - controller.lane_turn_value = LANE_CHANGE_SPEED_MIN - controller.turn_direction = TurnDirection.none - controller.update_lane_turn(False, False, True, False, 7) - assert controller.get_turn_direction() == TurnDirection.none + def test_lane_turn_overrides_lane_change(self): + dh = DesireHelper() + controller = LaneTurnController(dh) + controller.enabled = True + controller.lane_turn_value = LANE_CHANGE_SPEED_MIN + controller.turn_direction = TurnDirection.none + # left turn desire + controller.update_lane_turn(False, False, True, False, 5) + assert controller.get_turn_direction() == TurnDirection.turnLeft + # right turn desire + controller.update_lane_turn(False, False, False, True, 6) + assert controller.get_turn_direction() == TurnDirection.turnRight + # no turn + controller.update_lane_turn(False, False, False, False, 7) + assert controller.get_turn_direction() == TurnDirection.none - -def test_lane_turn_overrides_lane_change(): - dh = DesireHelper() - controller = LaneTurnController(dh) - controller.enabled = True - controller.lane_turn_value = LANE_CHANGE_SPEED_MIN - controller.turn_direction = TurnDirection.none - # left turn desire - controller.update_lane_turn(False, False, True, False, 5) - assert controller.get_turn_direction() == TurnDirection.turnLeft - # right turn desire - controller.update_lane_turn(False, False, False, True, 6) - assert controller.get_turn_direction() == TurnDirection.turnRight - # no turn - controller.update_lane_turn(False, False, False, False, 7) - assert controller.get_turn_direction() == TurnDirection.none - - -@pytest.mark.parametrize("v_ego,expected", [ - (8.93, TurnDirection.turnLeft), # just below threshold - (8.96, TurnDirection.none), # above threshold - (8.95, TurnDirection.none), # just above threshold -]) -def test_lane_turn_desire_speed_boundary(v_ego, expected): - dh = DesireHelper() - controller = LaneTurnController(dh) - controller.enabled = True - controller.lane_turn_value = LANE_CHANGE_SPEED_MIN - controller.turn_direction = TurnDirection.none - controller.update_lane_turn(False, True, True, False, v_ego) - assert controller.get_turn_direction() == expected + @parameterized.expand([ + (8.93, TurnDirection.turnLeft), # just below threshold + (8.96, TurnDirection.none), # above threshold + (8.95, TurnDirection.none), # just above threshold + ]) + def test_lane_turn_desire_speed_boundary(self, v_ego, expected): + dh = DesireHelper() + controller = LaneTurnController(dh) + controller.enabled = True + controller.lane_turn_value = LANE_CHANGE_SPEED_MIN + controller.turn_direction = TurnDirection.none + controller.update_lane_turn(False, True, True, False, v_ego) + assert controller.get_turn_direction() == expected class DummyCarState: @@ -84,43 +83,42 @@ class DummyCarState: self.brakePressed = brakePressed -@pytest.fixture def set_lane_turn_params(): params = Params() params.put("LaneTurnDesire", True) params.put("LaneTurnValue", 20.0) -@pytest.mark.parametrize("carstate, lateral_active, lane_change_prob, expected_desire", [ - # Lane turn desire overrides lane change desire - (DummyCarState(vEgo=5, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False), True, 1.0, - log.Desire.turnLeft), - (DummyCarState(vEgo=7, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False), True, 1.0, - log.Desire.turnRight), - # Lane change desire only (no turn desires) - (DummyCarState(vEgo=9, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, - steeringPressed=True, steeringTorque=1), True, 1.0, log.Desire.laneChangeLeft), - (DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False, - steeringPressed=True, steeringTorque=-1), True, 1.0, log.Desire.laneChangeRight), - # No desire (inactive) - (DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=False), False, 1.0, log.Desire.none), - (DummyCarState(vEgo=4, leftBlinker=False, rightBlinker=False), True, 1.0, log.Desire.none), # No blinkers? no desire! -]) -def test_desire_helper_integration(carstate, lateral_active, lane_change_prob, expected_desire, set_lane_turn_params): - dh = DesireHelper() - dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE - for _ in range(10): - dh.update(carstate, lateral_active, lane_change_prob, - left_edge_detected=False, right_edge_detected=False) - assert dh.desire == expected_desire +class TestDesireHelperIntegration(OpenpilotTestCase): + @parameterized.expand([ + # Lane turn desire overrides lane change desire + (DummyCarState(vEgo=5, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False), True, 1.0, + log.Desire.turnLeft), + (DummyCarState(vEgo=7, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False), True, 1.0, + log.Desire.turnRight), + # Lane change desire only (no turn desires) + (DummyCarState(vEgo=9, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, + steeringPressed=True, steeringTorque=1), True, 1.0, log.Desire.laneChangeLeft), + (DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False, + steeringPressed=True, steeringTorque=-1), True, 1.0, log.Desire.laneChangeRight), + # No desire (inactive) + (DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=False), False, 1.0, log.Desire.none), + (DummyCarState(vEgo=4, leftBlinker=False, rightBlinker=False), True, 1.0, log.Desire.none), # No blinkers? no desire! + ], names=["carstate", "lateral_active", "lane_change_prob", "expected_desire"]) + def test_desire_helper_integration(self, carstate, lateral_active, lane_change_prob, expected_desire, set_lane_turn_params): + dh = DesireHelper() + dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE + for _ in range(10): + dh.update(carstate, lateral_active, lane_change_prob, + left_edge_detected=False, right_edge_detected=False) + assert dh.desire == expected_desire - -def test_edge_blocks_lane_change(set_lane_turn_params): - dh = DesireHelper() - dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE - carstate = DummyCarState(vEgo=15, leftBlinker=True, steeringPressed=True, steeringTorque=1) - for _ in range(10): - dh.update(carstate, True, 1.0, left_edge_detected=True, right_edge_detected=False) - assert dh.lane_change_state == LaneChangeState.preLaneChange - assert dh.lane_change_direction == LaneChangeDirection.left - assert dh.desire == log.Desire.none + def test_edge_blocks_lane_change(self, set_lane_turn_params): + dh = DesireHelper() + dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE + carstate = DummyCarState(vEgo=15, leftBlinker=True, steeringPressed=True, steeringTorque=1) + for _ in range(10): + dh.update(carstate, True, 1.0, left_edge_detected=True, right_edge_detected=False) + assert dh.lane_change_state == LaneChangeState.preLaneChange + assert dh.lane_change_direction == LaneChangeDirection.left + assert dh.desire == log.Desire.none 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 index 0df945214d..b4f1137081 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_latcontrol_torque_ext.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_latcontrol_torque_ext.py @@ -19,6 +19,7 @@ from openpilot.selfdrive.locationd.helpers import Pose from openpilot.common.mock.generators import generate_deviceMotion from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces from openpilot.selfdrive.modeld.constants import ModelConstants +from openpilot.common.test import OpenpilotTestCase def _make_controller(enhanced=False, nnlc=False): @@ -71,7 +72,7 @@ def _run_update(controller, VM): return controller.update(True, CS, VM, params, False, 0.5, pose, False, 0.2) -class TestLatControlTorqueExt: +class TestLatControlTorqueExt(OpenpilotTestCase): def test_init_enhanced_only(self): controller, VM, _ = _make_controller(enhanced=True, nnlc=False) assert controller.extension._jerk_aware_enabled diff --git a/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_relc.py b/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_relc.py index 71b153c8b7..8fea65e1a1 100644 --- a/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_relc.py +++ b/openpilot/sunnypilot/selfdrive/controls/lib/tests/test_relc.py @@ -4,7 +4,8 @@ Copyright (c) 2021-, rav4kumar, 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 pytest +from openpilot.common.parameterized import parameterized +from openpilot.common.test import OpenpilotTestCase from openpilot.common.realtime import DT_MDL from openpilot.sunnypilot.selfdrive.controls.lib.relc import ( @@ -29,7 +30,6 @@ CLOSE_EDGES = edges(-2.0, 1.5) FAR_EDGES = edges(-10.0, 10.0) -@pytest.fixture def relc(mocker): mocker.patch("openpilot.sunnypilot.selfdrive.controls.lib.relc.Params") controller = RoadEdgeLaneChangeController() @@ -42,128 +42,129 @@ def drive(controller, road_edge_stds, lane_line_probs, seconds, v_ego=V_HIGH, ro controller.update(road_edge_stds, lane_line_probs, v_ego, road_edges) -@pytest.mark.parametrize("road_edge_stds,lane_line_probs,attr", [ - ([0.0, 0.9], [0.0, 0.8, 0.8, 0.8], "left_edge_detected"), - ([0.9, 0.0], [0.8, 0.8, 0.8, 0.0], "right_edge_detected"), -]) -def test_edge_detection(relc, road_edge_stds, lane_line_probs, attr): - drive(relc, road_edge_stds, lane_line_probs, EDGE_REACTION_TIME + 0.1) - assert getattr(relc, attr) +class TestRELC(OpenpilotTestCase): + @parameterized.expand([ + ([0.0, 0.9], [0.0, 0.8, 0.8, 0.8], "left_edge_detected"), + ([0.9, 0.0], [0.8, 0.8, 0.8, 0.0], "right_edge_detected"), + ], names=["road_edge_stds", "lane_line_probs", "attr"]) + def test_edge_detection(self, relc, road_edge_stds, lane_line_probs, attr): + drive(relc, road_edge_stds, lane_line_probs, EDGE_REACTION_TIME + 0.1) + assert getattr(relc, attr) -def test_edge_detection_requires_time(relc): - drive(relc, [0.0, 0.9], [0.0, 0.8, 0.8, 0.8], EDGE_REACTION_TIME - 0.05) - assert not relc.left_edge_detected + def test_edge_detection_requires_time(self, relc): + drive(relc, [0.0, 0.9], [0.0, 0.8, 0.8, 0.8], EDGE_REACTION_TIME - 0.05) + assert not relc.left_edge_detected -def test_both_edges_detected(relc): - drive(relc, [0.0, 0.0], [0.0, 0.8, 0.8, 0.0], EDGE_REACTION_TIME + 0.1) - assert relc.left_edge_detected - assert relc.right_edge_detected + def test_both_edges_detected(self, relc): + drive(relc, [0.0, 0.0], [0.0, 0.8, 0.8, 0.0], EDGE_REACTION_TIME + 0.1) + assert relc.left_edge_detected + assert relc.right_edge_detected -def test_noise_doesnt_clear(relc): - edge = ([0.0, 0.9], [0.0, 0.8, 0.8, 0.8]) - clear = ([0.9, 0.9], [0.8, 0.8, 0.8, 0.8]) + def test_noise_doesnt_clear(self, relc): + edge = ([0.0, 0.9], [0.0, 0.8, 0.8, 0.8]) + clear = ([0.9, 0.9], [0.8, 0.8, 0.8, 0.8]) - drive(relc, *edge, EDGE_REACTION_TIME + 0.1) - assert relc.left_edge_detected + drive(relc, *edge, EDGE_REACTION_TIME + 0.1) + assert relc.left_edge_detected - relc.update(*clear, V_HIGH, CLOSE_EDGES) - relc.update(*edge, V_HIGH, CLOSE_EDGES) - assert relc.left_edge_detected + relc.update(*clear, V_HIGH, CLOSE_EDGES) + relc.update(*edge, V_HIGH, CLOSE_EDGES) + assert relc.left_edge_detected -def test_clears_after_window(relc): - edge = ([0.0, 0.9], [0.0, 0.8, 0.8, 0.8]) - clear = ([0.9, 0.9], [0.8, 0.8, 0.8, 0.8]) + def test_clears_after_window(self, relc): + edge = ([0.0, 0.9], [0.0, 0.8, 0.8, 0.8]) + clear = ([0.9, 0.9], [0.8, 0.8, 0.8, 0.8]) - drive(relc, *edge, EDGE_REACTION_TIME + 0.1) - assert relc.left_edge_detected + drive(relc, *edge, EDGE_REACTION_TIME + 0.1) + assert relc.left_edge_detected - drive(relc, *clear, EDGE_CLEAR_TIME + 0.05) - assert not relc.left_edge_detected - assert relc.left_edge_timer == 0.0 + drive(relc, *clear, EDGE_CLEAR_TIME + 0.05) + assert not relc.left_edge_detected + assert relc.left_edge_timer == 0.0 -def test_low_speed_skips(relc): - drive(relc, [0.0, 0.9], [0.0, 0.8, 0.8, 0.8], EDGE_REACTION_TIME + 0.1, v_ego=V_LOW) - assert not relc.left_edge_detected - assert relc.left_edge_timer == 0.0 + def test_low_speed_skips(self, relc): + drive(relc, [0.0, 0.9], [0.0, 0.8, 0.8, 0.8], EDGE_REACTION_TIME + 0.1, v_ego=V_LOW) + assert not relc.left_edge_detected + assert relc.left_edge_timer == 0.0 -def test_speed_drop_resets(relc): - drive(relc, [0.0, 0.9], [0.0, 0.8, 0.8, 0.8], EDGE_REACTION_TIME + 0.1) - assert relc.left_edge_detected + def test_speed_drop_resets(self, relc): + drive(relc, [0.0, 0.9], [0.0, 0.8, 0.8, 0.8], EDGE_REACTION_TIME + 0.1) + assert relc.left_edge_detected - relc.update([0.0, 0.9], [0.0, 0.8, 0.8, 0.8], V_LOW, CLOSE_EDGES) - assert not relc.left_edge_detected + relc.update([0.0, 0.9], [0.0, 0.8, 0.8, 0.8], V_LOW, CLOSE_EDGES) + assert not relc.left_edge_detected -def test_param_off_resets(relc): - drive(relc, [0.0, 0.9], [0.0, 0.8, 0.8, 0.8], EDGE_REACTION_TIME + 0.1) - assert relc.left_edge_detected + def test_param_off_resets(self, relc): + drive(relc, [0.0, 0.9], [0.0, 0.8, 0.8, 0.8], EDGE_REACTION_TIME + 0.1) + assert relc.left_edge_detected - relc.params.get_bool.return_value = False - relc.read_params() - relc.update([0.0, 0.9], [0.0, 0.8, 0.8, 0.8], V_HIGH, CLOSE_EDGES) - assert not relc.left_edge_detected - assert not relc.right_edge_detected + relc.params.get_bool.return_value = False + relc.read_params() + relc.update([0.0, 0.9], [0.0, 0.8, 0.8, 0.8], V_HIGH, CLOSE_EDGES) + assert not relc.left_edge_detected + assert not relc.right_edge_detected -def test_lane_line_prevents_detection(relc): - drive(relc, [0.0, 0.9], [0.8, 0.8, 0.8, 0.8], EDGE_REACTION_TIME + 0.1) - assert not relc.left_edge_detected + def test_lane_line_prevents_detection(self, relc): + drive(relc, [0.0, 0.9], [0.8, 0.8, 0.8, 0.8], EDGE_REACTION_TIME + 0.1) + assert not relc.left_edge_detected -def test_one_side_blocks_other_allows(relc): - drive(relc, [0.9, 0.0], [0.8, 0.8, 0.8, 0.0], EDGE_REACTION_TIME + 0.1) - assert relc.right_edge_detected - assert not relc.left_edge_detected + def test_one_side_blocks_other_allows(self, relc): + drive(relc, [0.9, 0.0], [0.8, 0.8, 0.8, 0.0], EDGE_REACTION_TIME + 0.1) + assert relc.right_edge_detected + assert not relc.left_edge_detected -def test_disabled_no_detection(relc): - relc.enabled = False - relc.params.get_bool.return_value = False - drive(relc, [0.0, 0.0], [0.0, 0.8, 0.8, 0.0], EDGE_REACTION_TIME + 0.1) - assert not relc.left_edge_detected - assert not relc.right_edge_detected + def test_disabled_no_detection(self, relc): + relc.enabled = False + relc.params.get_bool.return_value = False + drive(relc, [0.0, 0.0], [0.0, 0.8, 0.8, 0.0], EDGE_REACTION_TIME + 0.1) + assert not relc.left_edge_detected + assert not relc.right_edge_detected -def test_far_edge_no_block(relc): - drive(relc, [0.0, 0.9], [0.05, 0.5, 0.5, 0.08], EDGE_REACTION_TIME + 0.1, road_edges=FAR_EDGES) - assert not relc.left_edge_detected + def test_far_edge_no_block(self, relc): + drive(relc, [0.0, 0.9], [0.05, 0.5, 0.5, 0.08], EDGE_REACTION_TIME + 0.1, road_edges=FAR_EDGES) + assert not relc.left_edge_detected -def test_close_edge_blocks(relc): - drive(relc, [0.9, 0.0], [0.05, 0.8, 0.8, 0.05], EDGE_REACTION_TIME + 0.1, - road_edges=edges(-8.0, 1.5)) - assert relc.right_edge_detected - assert not relc.left_edge_detected + def test_close_edge_blocks(self, relc): + drive(relc, [0.9, 0.0], [0.05, 0.8, 0.8, 0.05], EDGE_REACTION_TIME + 0.1, + road_edges=edges(-8.0, 1.5)) + assert relc.right_edge_detected + assert not relc.left_edge_detected -def test_wide_road_no_lines_no_block(relc): - drive(relc, [0.0, 0.0], [0.05, 0.4, 0.4, 0.05], EDGE_REACTION_TIME + 0.1, - road_edges=edges(-8.0, 8.0)) - assert not relc.left_edge_detected - assert not relc.right_edge_detected + def test_wide_road_no_lines_no_block(self, relc): + drive(relc, [0.0, 0.0], [0.05, 0.4, 0.4, 0.05], EDGE_REACTION_TIME + 0.1, + road_edges=edges(-8.0, 8.0)) + assert not relc.left_edge_detected + assert not relc.right_edge_detected -def test_narrow_road_both_block(relc): - drive(relc, [0.0, 0.0], [0.02, 0.4, 0.4, 0.02], EDGE_REACTION_TIME + 0.1, - road_edges=edges(-2.5, 2.5)) - assert relc.left_edge_detected - assert relc.right_edge_detected + def test_narrow_road_both_block(self, relc): + drive(relc, [0.0, 0.0], [0.02, 0.4, 0.4, 0.02], EDGE_REACTION_TIME + 0.1, + road_edges=edges(-2.5, 2.5)) + assert relc.left_edge_detected + assert relc.right_edge_detected -def test_clearance_boundary(relc): - boundary = VEHICLE_EDGE_MARGIN + EDGE_CLEARANCE # 4.78m - drive(relc, [0.0, 0.9], [0.05, 0.5, 0.5, 0.08], EDGE_REACTION_TIME + 0.1, - road_edges=edges(-(boundary - 0.1), 10.0)) - assert relc.left_edge_detected + def test_clearance_boundary(self, relc): + boundary = VEHICLE_EDGE_MARGIN + EDGE_CLEARANCE # 4.78m + drive(relc, [0.0, 0.9], [0.05, 0.5, 0.5, 0.08], EDGE_REACTION_TIME + 0.1, + road_edges=edges(-(boundary - 0.1), 10.0)) + assert relc.left_edge_detected - relc.reset() + relc.reset() - drive(relc, [0.0, 0.9], [0.05, 0.5, 0.5, 0.08], EDGE_REACTION_TIME + 0.1, - road_edges=edges(-(boundary + 0.1), 10.0)) - assert not relc.left_edge_detected + drive(relc, [0.0, 0.9], [0.05, 0.5, 0.5, 0.08], EDGE_REACTION_TIME + 0.1, + road_edges=edges(-(boundary + 0.1), 10.0)) + assert not relc.left_edge_detected diff --git a/openpilot/sunnypilot/selfdrive/locationd/tests/test_locationd.py b/openpilot/sunnypilot/selfdrive/locationd/tests/test_locationd.py index 748032df3e..ac6ab6aaea 100644 --- a/openpilot/sunnypilot/selfdrive/locationd/tests/test_locationd.py +++ b/openpilot/sunnypilot/selfdrive/locationd/tests/test_locationd.py @@ -1,5 +1,5 @@ -import pytest import platform +import unittest import json import random import time @@ -11,13 +11,11 @@ from openpilot.common.params import Params from openpilot.common.transformations.coordinates import ecef2geodetic from openpilot.system.manager.process_config import managed_processes +from openpilot.common.test import OpenpilotTestCase -if platform.system() == 'Darwin': - pytest.skip("Skipping locationd test on macOS due to unsupported msgq.", allow_module_level=True) - - -class TestLocationdProc: +@unittest.skipIf(platform.system() == 'Darwin', "msgq unsupported on macOS") +class TestLocationdProc(OpenpilotTestCase): LLD_MSGS = ['gpsLocationExternal', 'cameraOdometry', 'carState', 'extrinsicsCalibration', 'accelerometer', 'gyroscope'] @@ -88,6 +86,6 @@ class TestLocationdProc: time.sleep(1) # wait for async params write lastGPS = json.loads(self.params.get('LastGPSPositionLLK')) - assert lastGPS['latitude'] == pytest.approx(self.lat, abs=0.001) - assert lastGPS['longitude'] == pytest.approx(self.lon, abs=0.001) - assert lastGPS['altitude'] == pytest.approx(self.alt, abs=0.001) + self.assertAlmostEqual(lastGPS['latitude'], self.lat, delta=0.001) + self.assertAlmostEqual(lastGPS['longitude'], self.lon, delta=0.001) + self.assertAlmostEqual(lastGPS['altitude'], self.alt, delta=0.001) diff --git a/openpilot/sunnypilot/selfdrive/selfdrived/tests/test_button_state_tracker.py b/openpilot/sunnypilot/selfdrive/selfdrived/tests/test_button_state_tracker.py index 798fab650b..5824f82531 100644 --- a/openpilot/sunnypilot/selfdrive/selfdrived/tests/test_button_state_tracker.py +++ b/openpilot/sunnypilot/selfdrive/selfdrived/tests/test_button_state_tracker.py @@ -6,12 +6,13 @@ See the LICENSE.md file in the root directory for more details. """ from opendbc.car.structs import car from openpilot.sunnypilot.selfdrive.selfdrived.button_state_tracker import ButtonStateTracker +from openpilot.common.test import OpenpilotTestCase ButtonEvent = car.CarState.ButtonEvent ButtonType = car.CarState.ButtonEvent.Type -class TestButtonStateTracker: +class TestButtonStateTracker(OpenpilotTestCase): def setup_method(self) -> None: self.tracker = ButtonStateTracker() diff --git a/openpilot/sunnypilot/sunnylink/athena/tests/test_sunnylinkd.py b/openpilot/sunnypilot/sunnylink/athena/tests/test_sunnylinkd.py index 7b81da5ad4..9540d8329d 100644 --- a/openpilot/sunnypilot/sunnylink/athena/tests/test_sunnylinkd.py +++ b/openpilot/sunnypilot/sunnylink/athena/tests/test_sunnylinkd.py @@ -5,9 +5,10 @@ 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 openpilot.sunnypilot.sunnylink.athena import sunnylinkd +from openpilot.common.test import OpenpilotTestCase -class TestSunnylinkdMethods: +class TestSunnylinkdMethods(OpenpilotTestCase): def setup_method(self): self.saved_params = [] diff --git a/openpilot/sunnypilot/sunnylink/tests/test_capabilities.py b/openpilot/sunnypilot/sunnylink/tests/test_capabilities.py index 4af1462479..7e0272a7c9 100644 --- a/openpilot/sunnypilot/sunnylink/tests/test_capabilities.py +++ b/openpilot/sunnypilot/sunnylink/tests/test_capabilities.py @@ -12,7 +12,6 @@ the same commit so the bump shows up in code review. """ from __future__ import annotations -import pytest from openpilot.sunnypilot.sunnylink.capabilities import ( CAPABILITY_DEFAULTS, @@ -21,18 +20,18 @@ from openpilot.sunnypilot.sunnylink.capabilities import ( PROTOCOL_VERSION, generate_capabilities, ) +from openpilot.common.test import OpenpilotTestCase KNOWN_PROTOCOL_VERSIONS = (1,) LATEST_KNOWN = max(KNOWN_PROTOCOL_VERSIONS) -@pytest.fixture(scope="module") def caps(): return generate_capabilities() -class TestProtocolVersion: +class TestProtocolVersion(OpenpilotTestCase): def test_protocol_version_in_capability_fields(self): assert "protocol_version" in CAPABILITY_FIELDS @@ -63,7 +62,7 @@ class TestProtocolVersion: ) -class TestOpaquePerBrandFlags: +class TestOpaquePerBrandFlags(OpenpilotTestCase): def test_subaru_has_sng_field_present(self): assert "subaru_has_sng" in CAPABILITY_FIELDS @@ -77,7 +76,7 @@ class TestOpaquePerBrandFlags: assert caps["hyundai_alpha_long_available"] is False -class TestCapabilitiesShape: +class TestCapabilitiesShape(OpenpilotTestCase): def test_all_fields_present(self, caps): for field in CAPABILITY_FIELDS: assert field in caps, f"capabilities missing {field}" diff --git a/openpilot/sunnypilot/sunnylink/tests/test_compile_settings_ui.py b/openpilot/sunnypilot/sunnylink/tests/test_compile_settings_ui.py index 2fcd889ec3..797bf42558 100644 --- a/openpilot/sunnypilot/sunnylink/tests/test_compile_settings_ui.py +++ b/openpilot/sunnypilot/sunnylink/tests/test_compile_settings_ui.py @@ -19,7 +19,6 @@ import difflib import json import os -import pytest import yaml from openpilot.sunnypilot.sunnylink.tools.compile_settings_ui import ( @@ -29,20 +28,19 @@ from openpilot.sunnypilot.sunnylink.tools.compile_settings_ui import ( _resolve_refs, compile_schema, ) +from openpilot.common.test import OpenpilotTestCase -@pytest.fixture(scope="module") def compiled() -> dict: return compile_schema(DEFAULT_SRC) -@pytest.fixture(scope="module") def committed() -> dict: with open(DEFAULT_OUT) as f: return json.load(f) -class TestRoundtrip: +class TestRoundtrip(OpenpilotTestCase): def test_compiled_matches_committed(self, compiled, committed): """Compiled output must match the checked-in JSON.""" if compiled == committed: @@ -54,7 +52,7 @@ class TestRoundtrip: tofile="settings_ui.json (freshly compiled)", lineterm="", )) - pytest.fail(f"settings_ui.json schema mismatch — run compile_settings_ui.py\n\n{diff}") + self.fail(f"settings_ui.json schema mismatch — run compile_settings_ui.py\n\n{diff}") def test_committed_file_is_canonical(self): """Compiled output must byte-match the checked-in file (including trailing newline). @@ -72,10 +70,10 @@ class TestRoundtrip: tofile="settings_ui.json (freshly compiled)", lineterm="", )) - pytest.fail(f"settings_ui.json out of sync — run compile_settings_ui.py\n\n{diff}") + self.fail(f"settings_ui.json out of sync — run compile_settings_ui.py\n\n{diff}") -class TestRefResolution: +class TestRefResolution(OpenpilotTestCase): def test_list_context_splices(self): macros = {"a": [{"type": "offroad_only"}], "b": [{"type": "not_engaged"}]} out = _resolve_refs([{"$ref": "#/macros/a"}, {"$ref": "#/macros/b"}], macros) @@ -95,12 +93,12 @@ class TestRefResolution: assert out == [{"type": "offroad_only"}] def test_unknown_macro_raises(self): - with pytest.raises(CompileError, match="unknown macro"): + with self.assertRaisesRegex(CompileError, "unknown macro"): _resolve_refs([{"$ref": "#/macros/missing"}], {}) def test_cycle_raises(self): macros = {"a": [{"$ref": "#/macros/b"}], "b": [{"$ref": "#/macros/a"}]} - with pytest.raises(CompileError, match="cycle"): + with self.assertRaisesRegex(CompileError, "cycle"): _resolve_refs([{"$ref": "#/macros/a"}], macros) def test_depth_limit(self): @@ -111,20 +109,20 @@ class TestRefResolution: "l3": [{"$ref": "#/macros/l4"}], "l4": [{"type": "offroad_only"}], } - with pytest.raises(CompileError, match="depth"): + with self.assertRaisesRegex(CompileError, "depth"): _resolve_refs([{"$ref": "#/macros/l1"}], macros) def test_invalid_ref_scheme(self): - with pytest.raises(CompileError, match="unsupported"): + with self.assertRaisesRegex(CompileError, "unsupported"): _resolve_refs([{"$ref": "https://example.com/x"}], {}) def test_scalar_macro_in_list_context_raises(self): macros = {"x": {"type": "offroad_only"}} # macro is a single rule (dict), not a list - with pytest.raises(CompileError, match="must resolve to a list"): + with self.assertRaisesRegex(CompileError, "must resolve to a list"): _resolve_refs([{"$ref": "#/macros/x"}], macros) -class TestCompiledShape: +class TestCompiledShape(OpenpilotTestCase): def test_panels_present(self, compiled): assert isinstance(compiled["panels"], list) assert len(compiled["panels"]) == 9 @@ -145,7 +143,7 @@ class TestCompiledShape: def walk(node): if isinstance(node, dict): if "$ref" in node: - pytest.fail(f"unresolved $ref: {node}") + self.fail(f"unresolved $ref: {node}") for v in node.values(): walk(v) elif isinstance(node, list): @@ -154,7 +152,7 @@ class TestCompiledShape: walk(compiled) -class TestSourceTreeIntegrity: +class TestSourceTreeIntegrity(OpenpilotTestCase): def test_macros_yaml_well_formed(self): with open(os.path.join(DEFAULT_SRC, "_macros.yaml")) as f: doc = yaml.safe_load(f) diff --git a/openpilot/sunnypilot/sunnylink/tests/test_settings_changes.py b/openpilot/sunnypilot/sunnylink/tests/test_settings_changes.py index 07b05d4ac4..d9b1267a10 100644 --- a/openpilot/sunnypilot/sunnylink/tests/test_settings_changes.py +++ b/openpilot/sunnypilot/sunnylink/tests/test_settings_changes.py @@ -15,7 +15,7 @@ import json import os from typing import Any -import pytest +from openpilot.common.parameterized import parameterized from openpilot.sunnypilot.sunnylink.tools.generate_settings_schema import ( DEFINITION_PATH, @@ -24,6 +24,7 @@ from openpilot.sunnypilot.sunnylink.tools.generate_settings_schema import ( _load_torque_versions, generate_schema, ) +from openpilot.common.test import OpenpilotTestCase SCHEMA_VALIDATOR_PATH = os.path.join(os.path.dirname(DEFINITION_PATH), "settings_ui.schema.json") @@ -105,12 +106,11 @@ def _references_capability_field(rules: list[dict[str, Any]] | None, field: str) return found -@pytest.fixture(scope="module") def schema(): return generate_schema() -class TestMadsBrandGates: +class TestMadsBrandGates(OpenpilotTestCase): def test_mads_main_cruise_has_brand_gate(self, schema): """MadsMainCruiseAllowed must gate on brand and tesla_has_vehicle_bus.""" item = _find_item(schema, "MadsMainCruiseAllowed") @@ -126,7 +126,7 @@ class TestMadsBrandGates: assert _references_capability_field(item.get("enablement"), "tesla_has_vehicle_bus") -class TestTestManeuversSection: +class TestTestManeuversSection(OpenpilotTestCase): def test_lateral_maneuver_mode_in_test_maneuvers(self, schema): section = _find_section(schema, "developer", "test_maneuvers") assert section is not None, "developer.test_maneuvers section missing" @@ -153,10 +153,13 @@ class TestTestManeuversSection: "test_maneuvers must gate ShowAdvancedControls via enablement" -class TestValidator: +class TestValidator(OpenpilotTestCase): def test_validator_accepts_real_json(self): """settings_ui.json validates against settings_ui.schema.json.""" - jsonschema = pytest.importorskip("jsonschema") + try: + import jsonschema + except ImportError: + self.skipTest("jsonschema not installed") with open(DEFINITION_PATH) as f: data = json.load(f) with open(SCHEMA_VALIDATOR_PATH) as f: @@ -164,7 +167,7 @@ class TestValidator: jsonschema.validate(instance=data, schema=validator) -class TestTorqueOptionGeneration: +class TestTorqueOptionGeneration(OpenpilotTestCase): def test_torque_versions_match_generated_options(self, schema): versions = _load_torque_versions() assert versions, "latcontrol_torque_versions.json must have at least one version" @@ -179,11 +182,11 @@ class TestTorqueOptionGeneration: ) -class TestReleaseBranchGates: - @pytest.mark.parametrize("key", [ +class TestReleaseBranchGates(OpenpilotTestCase): + @parameterized.expand([ "EnableGithubRunner", "QuickBootToggle", - ]) + ], names=["key"]) def test_sp_dev_items_gate_on_is_sp_release(self, schema, key): """sunnypilot dev items must hide on sunnypilot release branches (is_sp_release gate).""" item = _find_item(schema, key) @@ -192,7 +195,7 @@ class TestReleaseBranchGates: assert _references_capability_field(rules, "is_sp_release"), f"{key} missing is_sp_release gate" -class TestSpuriousOffroadGatesDropped: +class TestSpuriousOffroadGatesDropped(OpenpilotTestCase): def test_disengage_on_accelerator_has_no_offroad_only(self, schema): item = _find_item(schema, "DisengageOnAccelerator") assert item is not None @@ -204,12 +207,12 @@ class TestSpuriousOffroadGatesDropped: assert "offroad_only" not in _flatten_rule_types(item.get("enablement")) -class TestNotEngagedReplacement: - @pytest.mark.parametrize("key", [ +class TestNotEngagedReplacement(OpenpilotTestCase): + @parameterized.expand([ "AlphaLongitudinalEnabled", "ToyotaEnforceStockLongitudinal", "ToyotaStopAndGoHack", - ]) + ], names=["key"]) def test_offroad_only_replaced_with_not_engaged(self, schema, key): """These items should use not_engaged, not offroad_only.""" item = _find_item(schema, key) diff --git a/openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py b/openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py index 579d72b60b..e60ac000f4 100644 --- a/openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py +++ b/openpilot/sunnypilot/sunnylink/tests/test_settings_schema.py @@ -5,7 +5,6 @@ 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 json -import pytest from openpilot.common.params import Params from openpilot.sunnypilot.sunnylink.tools.generate_settings_schema import ( @@ -16,6 +15,7 @@ from openpilot.sunnypilot.sunnylink.tools.generate_settings_schema import ( collect_capability_refs, ) from openpilot.sunnypilot.sunnylink.capabilities import CAPABILITY_FIELDS +from openpilot.common.test import OpenpilotTestCase VALID_WIDGET_TYPES = {"toggle", "option", "multiple_button", "button", "info"} @@ -55,18 +55,16 @@ def _brand_items(brand_data) -> list[dict]: return [] -@pytest.fixture(scope="module") def schema(): return generate_schema() -@pytest.fixture(scope="module") def all_param_keys(): """All keys registered in the device param store.""" return {k.decode("utf-8") for k in Params().all_keys()} -class TestSchemaStructure: +class TestSchemaStructure(OpenpilotTestCase): def test_schema_is_valid_json(self): """Schema serializes to valid JSON.""" raw = generate_schema_json() @@ -141,16 +139,16 @@ class TestSchemaStructure: for item in _iter_panel_items(panel): key = item["key"] if key in seen: - pytest.fail(f"Key '{key}' appears in both panel '{seen[key]}' and '{panel['id']}'") + self.fail(f"Key '{key}' appears in both panel '{seen[key]}' and '{panel['id']}'") seen[key] = panel["id"] for sub in item.get("sub_items", []): sub_key = sub["key"] if sub_key in seen: - pytest.fail(f"Sub-item key '{sub_key}' appears in both '{seen[sub_key]}' and '{panel['id']}'") + self.fail(f"Sub-item key '{sub_key}' appears in both '{seen[sub_key]}' and '{panel['id']}'") seen[sub_key] = panel["id"] -class TestSchemaCoverage: +class TestSchemaCoverage(OpenpilotTestCase): def test_all_schema_keys_exist_in_params(self, schema, all_param_keys): """Schema keys must exist in Params().all_keys().""" schema_keys = collect_all_keys(schema) @@ -169,7 +167,7 @@ class TestSchemaCoverage: assert set(schema["capability_fields"]) == set(CAPABILITY_FIELDS) -class TestRuleWellFormedness: +class TestRuleWellFormedness(OpenpilotTestCase): def _validate_rule(self, rule: dict, context: str = ""): """Recursively validate a single rule dict.""" assert "type" in rule, f"Rule missing 'type' in {context}" @@ -232,7 +230,7 @@ class TestRuleWellFormedness: key = item.get("key") for rule in item.get(rules_field, []): if rule.get("type") == "param" and rule.get("key") == key: - pytest.fail(f"Item {key} has self-referencing {rules_field} rule") + self.fail(f"Item {key} has self-referencing {rules_field} rule") for panel in schema["panels"]: for item in _iter_panel_items(panel): @@ -245,7 +243,7 @@ class TestRuleWellFormedness: _check_self_ref(item, "enablement") -class TestKnownPanels: +class TestKnownPanels(OpenpilotTestCase): def test_expected_panels_exist(self, schema): panel_ids = {p["id"] for p in schema["panels"]} expected = {"steering", "cruise", "display", "visuals", "device", "software", "developer"} @@ -279,7 +277,7 @@ class TestKnownPanels: assert "NeuralNetworkLateralControl" in enhanced_enable_keys -class TestKnownVehicleSettings: +class TestKnownVehicleSettings(OpenpilotTestCase): def test_hyundai_has_longitudinal_tuning(self, schema): keys = {i["key"] for i in _brand_items(schema["vehicle_settings"].get("hyundai"))} assert "HyundaiLongitudinalTuning" in keys @@ -299,7 +297,7 @@ class TestKnownVehicleSettings: assert "SubaruStopAndGoManualParkingBrake" in keys -class TestItemCompleteness: +class TestItemCompleteness(OpenpilotTestCase): def _collect_all_items(self, schema): """Collect all items and sub_items from panels and vehicle_settings.""" items = [] @@ -319,7 +317,7 @@ class TestItemCompleteness: """All items must have titles.""" missing = [i["key"] for i in self._collect_all_items(schema) if "title" not in i] if len(missing) > MAX_ALLOWED_MISSING_TITLES: - pytest.fail(f"Items without titles ({len(missing)}): {missing[:10]}") + self.fail(f"Items without titles ({len(missing)}): {missing[:10]}") def test_no_default_titles(self, schema): """Item titles must differ from keys.""" diff --git a/openpilot/sunnypilot/system/sensord/tests/test_sensord.py b/openpilot/sunnypilot/system/sensord/tests/test_sensord.py index 53f5d27219..39486fde76 100644 --- a/openpilot/sunnypilot/system/sensord/tests/test_sensord.py +++ b/openpilot/sunnypilot/system/sensord/tests/test_sensord.py @@ -1,6 +1,5 @@ import os import subprocess -import pytest import time import numpy as np from collections import namedtuple, defaultdict @@ -12,6 +11,7 @@ from openpilot.common.gpio import get_irqs_for_action from openpilot.common.timeout import Timeout from openpilot.common.hardware import HARDWARE from openpilot.system.manager.process_config import managed_processes +from openpilot.common.test import OpenpilotTestCase BMX = { ('bmx055', 'acceleration'), @@ -103,8 +103,9 @@ def read_sensor_events(duration_sec): return {k: v for k, v in events.items() if len(v) > 0} -@pytest.mark.tici -class TestSensord: +class TestSensord(OpenpilotTestCase): + COMMA_HARDWARE_TEST = True + @classmethod def setup_class(cls): # enable LSM self test diff --git a/openpilot/sunnypilot/system/updated/tests/test_sp_branch_migrations.py b/openpilot/sunnypilot/system/updated/tests/test_sp_branch_migrations.py index b2742841a0..86f82fd9d2 100644 --- a/openpilot/sunnypilot/system/updated/tests/test_sp_branch_migrations.py +++ b/openpilot/sunnypilot/system/updated/tests/test_sp_branch_migrations.py @@ -4,70 +4,72 @@ 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 pytest +from openpilot.common.parameterized import parameterized +from openpilot.common.test import OpenpilotTestCase from openpilot.common.params import Params from openpilot.system.updated.updated import Updater -@pytest.mark.parametrize(("device_type", "branch", "expected"), [ - ("tici", "staging-c3-new", "staging-tici"), - ("tici", "dev-c3-new", "staging-tici"), - ("tici", "master", "master-tici"), - ("tici", "master-dev-c3-new", "master-tici"), - ("tizi", "staging-c3-new", "staging"), - ("tizi", "dev-c3-new", "dev"), - ("tizi", "master-dev-c3-new", "master-dev"), - ("tizi", "release3", "release-tizi"), - ("tizi", "release3-staging", "release-tizi-staging"), - ("mici", "release3", "release-mici"), - ("mici", "release3-staging", "release-mici-staging"), -]) -def test_sp_branch_migrations_from_current_branch(mocker, device_type, branch, expected): - params = Params() - params.remove("UpdaterTargetBranch") - - mocker.patch("openpilot.system.updated.updated.HARDWARE.get_device_type", return_value=device_type) - mocker.patch.object(Updater, "get_branch", return_value=branch) - - assert Updater().target_branch == expected - - -@pytest.mark.parametrize(("device_type", "branch", "expected"), [ - ("tici", "staging-c3-new", "staging-tici"), - ("tici", "dev-c3-new", "staging-tici"), - ("tici", "master", "master-tici"), - ("tici", "master-dev-c3-new", "master-tici"), - ("tizi", "staging-c3-new", "staging"), - ("tizi", "dev-c3-new", "dev"), - ("tizi", "master-dev-c3-new", "master-dev"), - ("tizi", "release3", "release-tizi"), - ("tizi", "release3-staging", "release-tizi-staging"), - ("mici", "release3", "release-mici"), - ("mici", "release3-staging", "release-mici-staging"), -]) -def test_sp_branch_migrations_from_param(mocker, device_type, branch, expected): - params = Params() - params.put("UpdaterTargetBranch", branch, block=True) - - mocker.patch("openpilot.system.updated.updated.HARDWARE.get_device_type", return_value=device_type) - - try: - assert Updater().target_branch == expected - finally: +class TestBranchMigrations(OpenpilotTestCase): + @parameterized.expand([ + ("tici", "staging-c3-new", "staging-tici"), + ("tici", "dev-c3-new", "staging-tici"), + ("tici", "master", "master-tici"), + ("tici", "master-dev-c3-new", "master-tici"), + ("tizi", "staging-c3-new", "staging"), + ("tizi", "dev-c3-new", "dev"), + ("tizi", "master-dev-c3-new", "master-dev"), + ("tizi", "release3", "release-tizi"), + ("tizi", "release3-staging", "release-tizi-staging"), + ("mici", "release3", "release-mici"), + ("mici", "release3-staging", "release-mici-staging"), + ], names=["device_type", "branch", "expected"]) + def test_sp_branch_migrations_from_current_branch(self, mocker, device_type, branch, expected): + params = Params() params.remove("UpdaterTargetBranch") + mocker.patch("openpilot.system.updated.updated.HARDWARE.get_device_type", return_value=device_type) + mocker.patch.object(Updater, "get_branch", return_value=branch) -@pytest.mark.parametrize(("device_type", "branch"), [ - ("tici", "unknown"), - ("tizi", "unknown"), - ("mici", "unknown"), -]) -def test_sp_branch_migrations_passthrough(mocker, device_type, branch): - params = Params() - params.remove("UpdaterTargetBranch") + assert Updater().target_branch == expected - mocker.patch("openpilot.system.updated.updated.HARDWARE.get_device_type", return_value=device_type) - mocker.patch.object(Updater, "get_branch", return_value=branch) - assert Updater().target_branch == branch + @parameterized.expand([ + ("tici", "staging-c3-new", "staging-tici"), + ("tici", "dev-c3-new", "staging-tici"), + ("tici", "master", "master-tici"), + ("tici", "master-dev-c3-new", "master-tici"), + ("tizi", "staging-c3-new", "staging"), + ("tizi", "dev-c3-new", "dev"), + ("tizi", "master-dev-c3-new", "master-dev"), + ("tizi", "release3", "release-tizi"), + ("tizi", "release3-staging", "release-tizi-staging"), + ("mici", "release3", "release-mici"), + ("mici", "release3-staging", "release-mici-staging"), + ], names=["device_type", "branch", "expected"]) + def test_sp_branch_migrations_from_param(self, mocker, device_type, branch, expected): + params = Params() + params.put("UpdaterTargetBranch", branch, block=True) + + mocker.patch("openpilot.system.updated.updated.HARDWARE.get_device_type", return_value=device_type) + + try: + assert Updater().target_branch == expected + finally: + params.remove("UpdaterTargetBranch") + + + @parameterized.expand([ + ("tici", "unknown"), + ("tizi", "unknown"), + ("mici", "unknown"), + ], names=["device_type", "branch"]) + def test_sp_branch_migrations_passthrough(self, mocker, device_type, branch): + params = Params() + params.remove("UpdaterTargetBranch") + + mocker.patch("openpilot.system.updated.updated.HARDWARE.get_device_type", return_value=device_type) + mocker.patch.object(Updater, "get_branch", return_value=branch) + + assert Updater().target_branch == branch diff --git a/pyproject.toml b/pyproject.toml index 672ac277b2..dcc1024479 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,11 +65,6 @@ dev = [ testing = [ "coverage", "ty", - "pytest", - "pytest-cpp", - "pytest-subtests", - "pytest-xdist @ git+https://github.com/sshane/pytest-xdist@2b4372bd62699fb412c4fe2f95bf9f01bd2018da", - "pytest-mock", "ruff", "codespell", ] @@ -115,24 +110,6 @@ packages = [ [tool.hatch.metadata] allow-direct-references = true -# TODO-SP: upstream replaced pytest with a custom unittest runner (tools/test_runner.py). -# Remove this section and the pytest deps once sunnypilot tests are migrated to unittest. -[tool.pytest.ini_options] -minversion = "6.0" -addopts = "-Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" -python_files = "test_*.py" -markers = [ - "slow: tests that take awhile to run and can be skipped with -m 'not slow'", - "tici: tests that are only meant to run on the C3/C3X", - "skip_tici_setup: mark test to skip tici setup fixture", - "nocapture: don't capture test output", - "shared_download_cache: share download cache between tests", - "xdist_group_class_property: group tests by a property of the class that contains them", -] -testpaths = [ - "openpilot", -] - [tool.codespell] quiet-level = 3 # if you've got a short variable name that's getting flagged, add it here @@ -171,7 +148,6 @@ exclude = [ lint.flake8-implicit-str-concat.allow-multiline = false [tool.ruff.lint.flake8-tidy-imports.banned-api] -"pytest.main".msg = "pytest.main requires special handling that is easy to mess up!" "time.time".msg = "Use time.monotonic. time.time can skip due to its reference clock, you probably want a monotonic clock" "pyray.measure_text_ex".msg = "Use openpilot.system.ui.lib.text_measure" "pyray.is_mouse_button_pressed".msg = "This can miss events. Use Widget._handle_mouse_press" diff --git a/uv.lock b/uv.lock index 102924151d..a86cd55d30 100644 --- a/uv.lock +++ b/uv.lock @@ -18,15 +18,6 @@ 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" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, -] - [[package]] name = "certifi" version = "2026.7.22" @@ -378,15 +369,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/25/2a75b47cb057b1e164c604fb81ab690a6cdb5e2260ce651194eae90f64a3/deepmerge-2.1.0-py3-none-any.whl", hash = "sha256:8f148339a91d680a75ecb74ade235d9e759a93df373a0b04e9d31c8666cfeb75", size = 14345, upload-time = "2026-06-22T05:46:06.742Z" }, ] -[[package]] -name = "execnet" -version = "2.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } -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" @@ -513,15 +495,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/db/55a262f3606bebcae07cc14095338471ad7c0bbcaa37707e6f0ee49725b7/importlib_resources-7.1.0-py3-none-any.whl", hash = "sha256:1bd7b48b4088eddb2cd16382150bb515af0bd2c70128194392725f82ad2c96a1", size = 37232, upload-time = "2026-04-12T16:36:08.219Z" }, ] -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - [[package]] name = "inputs" version = "0.5" @@ -808,11 +781,6 @@ submodules = [ testing = [ { name = "codespell" }, { name = "coverage" }, - { name = "pytest" }, - { name = "pytest-cpp" }, - { name = "pytest-mock" }, - { name = "pytest-subtests" }, - { name = "pytest-xdist" }, { name = "ruff" }, { name = "ty" }, ] @@ -856,11 +824,6 @@ requires-dist = [ { name = "pandacan", marker = "extra == 'submodules'", editable = "panda" }, { name = "pycapnp", specifier = "==2.1.0" }, { name = "pyjwt", extras = ["crypto"] }, - { name = "pytest", marker = "extra == 'testing'" }, - { name = "pytest-cpp", marker = "extra == 'testing'" }, - { name = "pytest-mock", marker = "extra == 'testing'" }, - { name = "pytest-subtests", marker = "extra == 'testing'" }, - { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, { name = "pyzmq" }, { name = "rednose", marker = "extra == 'submodules'", editable = "rednose_repo" }, { name = "requests" }, @@ -939,15 +902,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, ] -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, -] - [[package]] name = "pycapnp" version = "2.1.0" @@ -1041,68 +995,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] -[[package]] -name = "pytest" -version = "9.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, -] - -[[package]] -name = "pytest-cpp" -version = "2.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/a1/c2679d7ff2da20a0f89c7820ae2739cde739eac9b43c192531117b31b5f4/pytest_cpp-2.6.0.tar.gz", hash = "sha256:c2f49d3c038539ac84786a94d852e4f4619c34c95979c2bc69c20b3bdf051d85", size = 465490, upload-time = "2024-09-18T00:08:08.251Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/44/dc2f5d53165264ae5831f361fe7723c45da05718a97015b2eddc452cf503/pytest_cpp-2.6.0-py3-none-any.whl", hash = "sha256:b33de94609450feea2fba9efff3558b8ac8f1fdf40a99e263b395d4798b911bb", size = 15074, upload-time = "2024-09-18T00:08:06.415Z" }, -] - -[[package]] -name = "pytest-mock" -version = "3.15.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, -] - -[[package]] -name = "pytest-subtests" -version = "0.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bb/d9/20097971a8d315e011e055d512fa120fd6be3bdb8f4b3aa3e3c6bf77bebc/pytest_subtests-0.15.0.tar.gz", hash = "sha256:cb495bde05551b784b8f0b8adfaa27edb4131469a27c339b80fd8d6ba33f887c", size = 18525, upload-time = "2025-10-20T16:26:18.358Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/64/bba465299b37448b4c1b84c7a04178399ac22d47b3dc5db1874fe55a2bd3/pytest_subtests-0.15.0-py3-none-any.whl", hash = "sha256:da2d0ce348e1f8d831d5a40d81e3aeac439fec50bd5251cbb7791402696a9493", size = 9185, upload-time = "2025-10-20T16:26:17.239Z" }, -] - -[[package]] -name = "pytest-xdist" -version = "3.7.1.dev24+g2b4372b" -source = { git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da#2b4372bd62699fb412c4fe2f95bf9f01bd2018da" } -dependencies = [ - { name = "execnet" }, - { name = "pytest" }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" From 6405f15d5a58dcb3aaac9a72ba194f8deb933e6c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 14 Aug 2026 01:51:28 -0400 Subject: [PATCH 169/170] models: use requests instead of aiohttp --- openpilot/sunnypilot/models/manager.py | 60 ++-- .../models/tests/test_manager_download.py | 300 ++++++++++++++++++ 2 files changed, 332 insertions(+), 28 deletions(-) create mode 100644 openpilot/sunnypilot/models/tests/test_manager_download.py diff --git a/openpilot/sunnypilot/models/manager.py b/openpilot/sunnypilot/models/manager.py index d742e968a9..7ba805ff95 100644 --- a/openpilot/sunnypilot/models/manager.py +++ b/openpilot/sunnypilot/models/manager.py @@ -9,7 +9,7 @@ import asyncio import os import time -import aiohttp +import requests from openpilot.common.params import Params from openpilot.common.realtime import Ratekeeper from openpilot.common.swaglog import cloudlog @@ -19,6 +19,9 @@ from openpilot.cereal import messaging, custom from openpilot.sunnypilot.models.fetcher import ModelFetcher from openpilot.sunnypilot.models.helpers import get_active_bundle, validate_active_bundle, verify_file +# (connect, read) seconds. read is per-request inactivity, not a total cap +DOWNLOAD_TIMEOUT = (30, 30) + class ModelManagerSP: """Manages model downloads and status reporting""" @@ -63,30 +66,29 @@ class ModelManagerSP: """Downloads a file with progress tracking""" self._download_start_times[model.fileName] = time.monotonic() - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - response.raise_for_status() - total_size = int(response.headers.get("content-length", 0)) - bytes_downloaded = 0 + with requests.get(url, stream=True, timeout=DOWNLOAD_TIMEOUT) as response: # noqa: ASYNC210 + response.raise_for_status() + total_size = int(response.headers.get("content-length", 0)) + bytes_downloaded = 0 - with open(path, 'wb') as f: # noqa: ASYNC230 - async for chunk in response.content.iter_chunked(self._chunk_size): # type: bytes - f.write(chunk) - bytes_downloaded += len(chunk) + with open(path, 'wb') as f: # noqa: ASYNC230 + for chunk in response.iter_content(chunk_size=self._chunk_size): # type: bytes + f.write(chunk) + bytes_downloaded += len(chunk) - if self.params.get("ModelManager_DownloadIndex") is None: - raise Exception("Download cancelled") + if self.params.get("ModelManager_DownloadIndex") is None: + raise Exception("Download cancelled") - if total_size > 0: - progress = (bytes_downloaded / total_size) * 100 - model.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.downloading - model.downloadProgress.progress = progress - model.downloadProgress.eta = self._calculate_eta(model.fileName, progress) - self._sync_artifact_progress(model) - self._report_status() + if total_size > 0: + progress = (bytes_downloaded / total_size) * 100 + model.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.downloading + model.downloadProgress.progress = progress + model.downloadProgress.eta = self._calculate_eta(model.fileName, progress) + self._sync_artifact_progress(model) + self._report_status() - # Clean up start time after download completes - del self._download_start_times[model.fileName] + # Clean up start time after download completes + del self._download_start_times[model.fileName] async def _download_chunked(self, base_url: str, base_path: str, artifact) -> None: from openpilot.common.file_chunker import get_chunk_name, get_manifest_path @@ -98,16 +100,18 @@ class ModelManagerSP: manifest_path = get_manifest_path(base_path) self._download_start_times[artifact.fileName] = time.monotonic() - for i, _ in enumerate(artifact.chunks): - chunk_url = get_chunk_name(base_url, i, num_chunks) - chunk_path = get_chunk_name(base_path, i, num_chunks) - chunk_downloaded = 0 - async with aiohttp.ClientSession() as session: - async with session.get(chunk_url) as response: + # Shared connection saves a TCP+TLS handshake per chunk. + # Keep sequential: the link saturates on one stream and Session is not thread-safe. + with requests.Session() as session: + for i, _ in enumerate(artifact.chunks): + chunk_url = get_chunk_name(base_url, i, num_chunks) + chunk_path = get_chunk_name(base_path, i, num_chunks) + chunk_downloaded = 0 + with session.get(chunk_url, stream=True, timeout=DOWNLOAD_TIMEOUT) as response: response.raise_for_status() chunk_size = int(response.headers.get("content-length", 0)) with open(chunk_path, 'wb') as f: # noqa: ASYNC230 - async for data in response.content.iter_chunked(self._chunk_size): + for data in response.iter_content(chunk_size=self._chunk_size): f.write(data) chunk_downloaded += len(data) if self.params.get("ModelManager_DownloadIndex") is None: diff --git a/openpilot/sunnypilot/models/tests/test_manager_download.py b/openpilot/sunnypilot/models/tests/test_manager_download.py new file mode 100644 index 0000000000..67fb9023af --- /dev/null +++ b/openpilot/sunnypilot/models/tests/test_manager_download.py @@ -0,0 +1,300 @@ +""" +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 asyncio +import hashlib +import http.server +import os +import tempfile +import threading +import unittest +from typing import Any +from unittest import mock + +import requests +from urllib3.connectionpool import HTTPConnectionPool + +from openpilot.cereal import custom +from openpilot.common.test import OpenpilotTestCase +from openpilot.common.file_chunker import get_chunk_name, get_manifest_path +from openpilot.selfdrive.test.helpers import http_server_context +from openpilot.sunnypilot.models import manager as manager_module +from openpilot.sunnypilot.models.manager import ModelManagerSP + +CHUNK_BODIES = [b'A' * 5000, b'B' * 5000, b'C' * 3000] +WHOLE_BODY = b'Z' * 9000 + + +def sha256(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +class DownloadHandler(http.server.BaseHTTPRequestHandler): + """Serves the fixture bodies. Class attributes are reset per test.""" + request_paths: list[str] = [] + fail_paths: dict[str, int] = {} + stall_paths: set[str] = set() + stall_event: threading.Event | None = None + + def log_message(self, format: str, *args: Any) -> None: # noqa: A002 + pass + + def _body_for(self, path): + if path.endswith('.whole'): + return WHOLE_BODY + for i in range(len(CHUNK_BODIES)): + if path.endswith(get_chunk_name('', i, len(CHUNK_BODIES))): + return CHUNK_BODIES[i] + return None + + def do_GET(self): + type(self).request_paths.append(self.path) + + status = type(self).fail_paths.get(self.path) + if status: + self.send_response(status) + self.end_headers() + return + + body = self._body_for(self.path) + if body is None: + self.send_response(404) + self.end_headers() + return + + self.send_response(200) + self.send_header('Content-Length', str(len(body))) + self.end_headers() + + if self.path in type(self).stall_paths: + # write a little, then wait so the test can cancel mid-transfer + self.wfile.write(body[:100]) + self.wfile.flush() + if type(self).stall_event is not None: + type(self).stall_event.wait(timeout=5) + self.wfile.write(body[100:]) + else: + self.wfile.write(body) + + +class ManagerDownloadTestBase(OpenpilotTestCase): + def setUp(self): + super().setUp() + DownloadHandler.request_paths = [] + DownloadHandler.fail_paths = {} + DownloadHandler.stall_paths = set() + DownloadHandler.stall_event = None + + self._tmp = tempfile.TemporaryDirectory() + self.addCleanup(self._tmp.cleanup) + self.dest = self._tmp.name + + self.reported: list[float] = [] + + self.manager = ModelManagerSP.__new__(ModelManagerSP) + self.manager.params = mock.MagicMock() + self.manager.params.get.return_value = b'0' # not cancelled + self.manager.pm = mock.MagicMock() + self.manager.pm.send.side_effect = self._record_progress + self.manager.selected_bundle = None + self.manager.active_bundle = None + self.manager.available_models = [] + self.manager._chunk_size = 1024 + self.manager._download_start_times = {} + + def _record_progress(self, *args) -> None: + """Runs on every real _report_status send.""" + artifact = getattr(self, 'artifact', None) + if artifact is not None: + self.reported.append(float(artifact.downloadProgress.progress)) + + def make_artifact(self, chunked: bool): + bundle = custom.ModelManagerSP.ModelBundle.new_message() + bundle.init('models', 1) + artifact = bundle.models[0].artifact + artifact.fileName = 'driving_test_tinygrad.pkl' + if chunked: + artifact.downloadUri.uri = self.base_url + '/driving_test_tinygrad.pkl' + artifact.downloadUri.sha256 = sha256(b''.join(CHUNK_BODIES)) + artifact.init('chunks', len(CHUNK_BODIES)) + for i, body in enumerate(CHUNK_BODIES): + artifact.chunks[i].sha256 = sha256(body) + else: + artifact.downloadUri.uri = self.base_url + '/driving_test_tinygrad.pkl.whole' + artifact.downloadUri.sha256 = sha256(WHOLE_BODY) + self._bundle = bundle + self.artifact = artifact + return artifact + + def chunk_paths(self, base_path): + return [get_chunk_name(base_path, i, len(CHUNK_BODIES)) for i in range(len(CHUNK_BODIES))] + + def assert_no_partials(self, base_path): + leftovers = [p for p in [base_path, get_manifest_path(base_path)] + self.chunk_paths(base_path) + if os.path.isfile(p)] + assert leftovers == [], f"partial files left behind: {leftovers}" + + +class TestManagerDownload(ManagerDownloadTestBase): + """Exercises the real _download_file / _download_chunked against a local server.""" + + def run_with_server(self, fn): + with http_server_context(handler=DownloadHandler) as (host, port): + self.base_url = f'http://{host}:{port}' + return fn() + + def test_download_file_writes_exact_bytes(self): + def body(): + artifact = self.make_artifact(chunked=False) + path = os.path.join(self.dest, artifact.fileName) + asyncio.run(self.manager._download_file(artifact.downloadUri.uri, path, artifact)) + with open(path, 'rb') as f: + written = f.read() + assert written == WHOLE_BODY + assert sha256(written) == artifact.downloadUri.sha256 + assert artifact.fileName not in self.manager._download_start_times + self.run_with_server(body) + + def test_download_chunked_writes_all_chunks_and_manifest(self): + def body(): + artifact = self.make_artifact(chunked=True) + base_path = os.path.join(self.dest, artifact.fileName) + asyncio.run(self.manager._download_chunked(artifact.downloadUri.uri, base_path, artifact)) + + for i, expected in enumerate(CHUNK_BODIES): + with open(get_chunk_name(base_path, i, len(CHUNK_BODIES)), 'rb') as f: + assert f.read() == expected, f"chunk {i} body mismatch" + + with open(get_manifest_path(base_path)) as f: + assert f.read() == str(len(CHUNK_BODIES)) + + assert not os.path.isfile(base_path), "base file should be removed after chunking" + assert artifact.fileName not in self.manager._download_start_times + self.run_with_server(body) + + def test_progress_is_monotonic_and_bounded(self): + def body(): + artifact = self.make_artifact(chunked=True) + base_path = os.path.join(self.dest, artifact.fileName) + asyncio.run(self.manager._download_chunked(artifact.downloadUri.uri, base_path, artifact)) + + assert self.reported, "expected progress reports" + for a, b in zip(self.reported, self.reported[1:], strict=False): + assert b >= a, f"progress went backwards: {a} -> {b}" + assert max(self.reported) <= 99.0, f"chunked progress must stay <=99 until verify, got {max(self.reported)}" + self.run_with_server(body) + + def test_session_is_reused_across_chunks(self): + """One connection pool shared across every chunk.""" + def body(): + artifact = self.make_artifact(chunked=True) + base_path = os.path.join(self.dest, artifact.fileName) + + pools = [] + original = HTTPConnectionPool.urlopen + + def tracked(pool_self, *args, **kwargs): + pools.append(id(pool_self)) + return original(pool_self, *args, **kwargs) + + with mock.patch.object(HTTPConnectionPool, 'urlopen', tracked): + asyncio.run(self.manager._download_chunked(artifact.downloadUri.uri, base_path, artifact)) + + assert len(pools) == len(CHUNK_BODIES), f"expected one request per chunk, got {len(pools)}" + assert len(set(pools)) == 1, f"connection pool not reused across chunks: {len(set(pools))} pools" + self.run_with_server(body) + + def test_http_error_propagates(self): + def body(): + artifact = self.make_artifact(chunked=True) + base_path = os.path.join(self.dest, artifact.fileName) + failing = '/' + os.path.basename(get_chunk_name(artifact.downloadUri.uri, 1, len(CHUNK_BODIES))) + DownloadHandler.fail_paths = {failing: 404} + + with self.assertRaises(requests.exceptions.HTTPError): + asyncio.run(self.manager._download_chunked(artifact.downloadUri.uri, base_path, artifact)) + + # chunk 1 failed, so its file and the manifest must not exist + assert not os.path.isfile(get_chunk_name(base_path, 1, len(CHUNK_BODIES))) + assert not os.path.isfile(get_manifest_path(base_path)) + self.run_with_server(body) + + def test_cancellation_mid_transfer(self): + """Cancellation is checked inside the byte loop; it must still fire after the port.""" + def body(): + artifact = self.make_artifact(chunked=True) + base_path = os.path.join(self.dest, artifact.fileName) + self.manager.params.get.return_value = None # cancelled + + with self.assertRaises(Exception) as ctx: + asyncio.run(self.manager._download_chunked(artifact.downloadUri.uri, base_path, artifact)) + assert 'cancelled' in str(ctx.exception).lower() + assert not os.path.isfile(get_manifest_path(base_path)) + self.run_with_server(body) + + def test_repeat_downloads_are_stable(self): + """Back-to-back runs must produce identical bytes and leak no start-time state.""" + def body(): + for _ in range(2): + artifact = self.make_artifact(chunked=True) + base_path = os.path.join(self.dest, artifact.fileName) + asyncio.run(self.manager._download_chunked(artifact.downloadUri.uri, base_path, artifact)) + for i, expected in enumerate(CHUNK_BODIES): + with open(get_chunk_name(base_path, i, len(CHUNK_BODIES)), 'rb') as f: + assert f.read() == expected + assert self.manager._download_start_times == {} + self.run_with_server(body) + + +class TestManagerImports(OpenpilotTestCase): + """Catches undeclared dependencies. aiohttp lived only in the AGNOS venv; 19.6 dropped + it and models_manager died on device while CI stayed green.""" + + def test_manager_imports(self): + assert manager_module.ModelManagerSP is not None + + def test_no_undeclared_http_client(self): + with open(manager_module.__file__) as f: + src = f.read() + assert 'import aiohttp' not in src, "aiohttp is not available on AGNOS 19.6; use requests" + + def test_download_timeout_is_explicit(self): + connect, read = manager_module.DOWNLOAD_TIMEOUT + assert connect > 0 and read > 0, "requests defaults to no timeout; downloads would hang forever" + + +@unittest.skipUnless(os.environ.get('RUN_INTEGRATION_TESTS'), 'requires external network') +class TestLiveModelManifest(OpenpilotTestCase): + """Every artifact and chunk URL in the published manifest must resolve.""" + + def test_all_manifest_urls_available(self): + from openpilot.sunnypilot.models.fetcher import ModelFetcher + + manifest = requests.get(ModelFetcher.MODEL_URL, timeout=30).json() + session = requests.Session() + dead = [] + + for bundle in manifest.get('bundles', []): + for model in bundle.get('models', []): + artifact = model['artifact'] + url = artifact['download_uri']['url'] + chunks = artifact.get('chunks', []) + urls = ([url] if not chunks + else [get_chunk_name(url, i, len(chunks)) for i in range(len(chunks))]) + for u in urls: + try: + r = session.head(u, timeout=15, allow_redirects=True) + if r.status_code != 200: + dead.append(f"{bundle.get('short_name')}: HTTP {r.status_code} {u}") + except requests.RequestException as e: + dead.append(f"{bundle.get('short_name')}: {type(e).__name__} {u}") + + assert not dead, "unreachable model URLs:\n" + "\n".join(dead) + + +if __name__ == '__main__': + unittest.main() From d48cfa730bf21593b684efeaf3fd8940695e1dea Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 14 Aug 2026 17:21:34 -0400 Subject: [PATCH 170/170] ui: dismiss screensaver on wake instead of on awake transition (#1907) * fix wake-up behavior * ui: dismiss screensaver on wake instead of on awake transition --------- Co-authored-by: Jason Wen --- openpilot/selfdrive/ui/sunnypilot/ui_state.py | 17 ++++++++++++----- openpilot/selfdrive/ui/ui_state.py | 4 ++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/openpilot/selfdrive/ui/sunnypilot/ui_state.py b/openpilot/selfdrive/ui/sunnypilot/ui_state.py index 510f19383a..602830a4db 100644 --- a/openpilot/selfdrive/ui/sunnypilot/ui_state.py +++ b/openpilot/selfdrive/ui/sunnypilot/ui_state.py @@ -241,19 +241,26 @@ class DeviceSP: def _set_awake(self, on: bool, _ui_state=None): self._blocked_by_screensaver = False - if _ui_state.boot_offroad_mode == 1 and not on: - _ui_state.params.put_bool("OffroadMode", True) - if not on and _ui_state.screensaver_enabled: if _ui_state.screensaver.was_dismissed: - if gui_app.get_active_widget() == _ui_state.screensaver: - gui_app.pop_widget() + self.dismiss_screensaver(_ui_state) elif _ui_state.screensaver.is_active: self._blocked_by_screensaver = True else: _ui_state.screensaver.initialize() gui_app.push_widget(_ui_state.screensaver) self._blocked_by_screensaver = True + else: + self.dismiss_screensaver(_ui_state) + + # blocked runs every frame, so write only when actually sleeping + if _ui_state.boot_offroad_mode == 1 and not on and not self._blocked_by_screensaver: + _ui_state.params.put_bool("OffroadMode", True) + + def dismiss_screensaver(self, _ui_state) -> None: + if gui_app.get_active_widget() == _ui_state.screensaver: + gui_app.pop_widget() + self._blocked_by_screensaver = False @staticmethod def set_onroad_brightness(_ui_state, awake: bool, cur_brightness: float) -> float: diff --git a/openpilot/selfdrive/ui/ui_state.py b/openpilot/selfdrive/ui/ui_state.py index ec5e1a27a4..e0aca74ff2 100644 --- a/openpilot/selfdrive/ui/ui_state.py +++ b/openpilot/selfdrive/ui/ui_state.py @@ -347,6 +347,10 @@ class Device(DeviceSP): self._set_awake(ui_state.ignition or not interaction_timeout or PC) def _set_awake(self, on: bool, _ui_state=None): + # screensaver holds _awake True, so waking is not a state change + if on and self._blocked_by_screensaver: + self.dismiss_screensaver(_ui_state or ui_state) + if on != self._awake: super()._set_awake(on, _ui_state or ui_state) if self._blocked_by_screensaver: