diff --git a/cereal/SConscript b/cereal/SConscript index a58a9490c..1c0a53d7c 100644 --- a/cereal/SConscript +++ b/cereal/SConscript @@ -1,3 +1,5 @@ +import subprocess + Import('env', 'common', 'msgq') cereal_dir = Dir('.') @@ -5,9 +7,15 @@ gen_dir = Dir('gen') # Build cereal schema_files = ['log.capnp', 'car.capnp', 'legacy.capnp', 'custom.capnp'] -env.Command([f'gen/cpp/{s}.c++' for s in schema_files] + [f'gen/cpp/{s}.h' for s in schema_files], - schema_files, - f"capnpc --src-prefix={cereal_dir.path} $SOURCES -o c++:{gen_dir.path}/cpp/") +schema_outputs = env.Command([f'gen/cpp/{s}.c++' for s in schema_files] + [f'gen/cpp/{s}.h' for s in schema_files], + schema_files, + f"capnpc --src-prefix={cereal_dir.path} $SOURCES -o c++:{gen_dir.path}/cpp/") + +# capnpc embeds an exact compiler-version guard in every generated C++ header. +# Include the tool version in the dependency signature so SCons cannot reuse +# generated headers from a cache populated by another AGNOS/toolchain version. +capnp_compiler_version = subprocess.check_output(['capnpc', '--version'], encoding='utf-8').strip() +env.Depends(schema_outputs, env.Value(capnp_compiler_version)) cereal = env.Library('cereal', [f'gen/cpp/{s}.c++' for s in schema_files]) diff --git a/cereal/libcereal.a b/cereal/libcereal.a index e24b78e71..31c256564 100644 Binary files a/cereal/libcereal.a and b/cereal/libcereal.a differ diff --git a/opendbc_repo/opendbc/car/toyota/interface.py b/opendbc_repo/opendbc/car/toyota/interface.py index f0db903bc..4788020ab 100644 --- a/opendbc_repo/opendbc/car/toyota/interface.py +++ b/opendbc_repo/opendbc/car/toyota/interface.py @@ -70,8 +70,9 @@ class CarInterface(CarInterfaceBase): late_prius_camera = candidate == CAR.TOYOTA_PRIUS and any( fw.ecu == Ecu.fwdCamera and bytes(fw.fwVersion).startswith(b'8646F4705') for fw in car_fw ) - if candidate == CAR.LEXUS_IS or late_prius_camera: - has_dsu_bypass = ((0x343 in camera_fingerprint and 0x343 not in fingerprint.get(1, {})) or + if candidate in (CAR.LEXUS_IS, CAR.TOYOTA_CAMRY) or late_prius_camera: + native_acc_fingerprints = (fingerprint.get(0, {}), fingerprint.get(1, {})) + has_dsu_bypass = ((0x343 in camera_fingerprint and not any(0x343 in native_bus for native_bus in native_acc_fingerprints)) or (0x4CB in camera_fingerprint and 0x4CB not in fingerprint.get(0, {}))) if not use_sdsu and candidate not in TSS2_CAR and has_dsu_bypass: ret.flags |= ToyotaFlags.DSU_BYPASS.value diff --git a/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py b/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py index 2ab6f54b2..bd9a64590 100644 --- a/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py +++ b/opendbc_repo/opendbc/car/toyota/tests/test_toyota.py @@ -284,6 +284,36 @@ class TestToyotaInterfaces: assert car_params.safetyConfigs[0].safetyParam & ToyotaSafetyFlags.STOCK_LONGITUDINAL.value assert car_params.safetyConfigs[0].safetyParam & ToyotaSafetyFlags.ALT_CRUISE.value + def test_camry_ignores_startup_acc_bus_mirror(self): + fingerprint = {bus: {} for bus in range(8)} + fingerprint[0][0x343] = 8 + fingerprint[2][0x343] = 8 + + car_params = CarInterface.get_params( + CAR.TOYOTA_CAMRY, + fingerprint, + [CarParams.CarFw(ecu=Ecu.hybrid, address=0x7D2, fwVersion=b"test")], + alpha_long=False, + is_release=False, + docs=False, + starpilot_toggles=SimpleNamespace(), + ) + + assert car_params.flags & ToyotaFlags.HYBRID.value + assert not car_params.flags & ToyotaFlags.DSU_BYPASS.value + assert not car_params.openpilotLongitudinalControl + assert car_params.safetyConfigs[0].safetyParam & ToyotaSafetyFlags.STOCK_LONGITUDINAL.value + + starpilot_params = CarInterface.get_starpilot_params( + CAR.TOYOTA_CAMRY, fingerprint, [], car_params, SimpleNamespace(), + ) + car_state = CarState(car_params, starpilot_params) + can_parsers = car_state.get_can_parsers(car_params) + car_state.update(can_parsers, SimpleNamespace(cluster_offset=1.0)) + assert "PRE_COLLISION" in can_parsers[Bus.pt].vl + for message in ("ACC_CONTROL", "PRE_COLLISION"): + assert message not in can_parsers[Bus.cam].vl + @pytest.mark.parametrize(("native_bus", "message"), [(1, 0x343), (0, 0x4CB)]) def test_prius_dsu_bypass_allows_native_bus_message(self, native_bus, message): fingerprint = {bus: {} for bus in range(8)} diff --git a/selfdrive/controls/tests/test_starpilot_vcruise.py b/selfdrive/controls/tests/test_starpilot_vcruise.py index fc48e72c8..09f578b8d 100644 --- a/selfdrive/controls/tests/test_starpilot_vcruise.py +++ b/selfdrive/controls/tests/test_starpilot_vcruise.py @@ -261,6 +261,21 @@ def test_curve_speed_controller_persists_data_after_leaving_curve(): assert any(key == "CurvatureData" for key, _ in planner.params.writes) +def test_curve_speed_controller_publishes_live_values_to_memory_params(): + planner, vcruise = make_vcruise(road_curvature=0.02) + sm = make_sm(standstill=False) + sm["carControl"].longActive = False + planner.driving_in_curve = True + planner.lateral_acceleration = 2.4 + vcruise.csc.training_timer = PLANNER_TIME + + vcruise.csc.log_data(20.0, sm) + + assert any(key == "CalibratedLateralAcceleration" for key, _ in planner.params_memory.writes) + assert any(key == "CalibrationProgress" for key, _ in planner.params_memory.writes) + assert planner.params_memory.values["CalibrationProgress"] > 0.0 + + def test_curve_speed_controller_ramps_toward_curve_speed_at_bounded_rate(): planner = SimpleNamespace( params=FakeParams(), diff --git a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py index b273ee7f2..a3c2ee7c0 100644 --- a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py +++ b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py @@ -1022,6 +1022,8 @@ class StarPilotLongitudinalLayout(_SettingsPage): self._params.put_float("CalibratedLateralAcceleration", 2.00) self._params.remove("CalibrationProgress") self._params.remove("CurvatureData") + self._params_memory.put_float("CalibratedLateralAcceleration", 2.00) + self._params_memory.put_float("CalibrationProgress", 0.0) gui_app.push_widget(ConfirmDialog(tr_noop("Reset Curve Data?"), tr_noop("Confirm"), callback=on_close)) diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 6028ffafa..ac5e69508 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -257,6 +257,9 @@ class Soundd: @retry(attempts=10, delay=3) def get_stream(self, sd): + # reload sounddevice to reinitialize portaudio + sd._terminate() + sd._initialize() return sd.OutputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback, blocksize=SAMPLE_BUFFER) def start_stream(self, sd): diff --git a/starpilot/controls/lib/curve_speed_controller.py b/starpilot/controls/lib/curve_speed_controller.py index 7d21780be..4d4bf4480 100644 --- a/starpilot/controls/lib/curve_speed_controller.py +++ b/starpilot/controls/lib/curve_speed_controller.py @@ -54,6 +54,7 @@ class CurveSpeedController: self.required_curvatures = [str(round(road_curvature, ROUNDING_PRECISION)) for road_curvature in np.arange(MIN_CURVATURE, MAX_CURVATURE + STEP, STEP)] self.update_lateral_acceleration() + self._publish_calibration_progress() @staticmethod def _bucket_curvature(road_curvature): @@ -102,15 +103,27 @@ class CurveSpeedController: if not self.data_dirty: return + progress = self._calibration_progress() + self.starpilot_planner.params.put_nonblocking("CalibrationProgress", progress) + self.starpilot_planner.params.put_nonblocking("CurvatureData", self.curvature_data) + self._put_memory_param("CalibrationProgress", progress) + self.data_dirty = False + self.persistence_timer = 0.0 + + def _calibration_progress(self): progress = 0.0 for key in self.required_curvatures: if key in self.curvature_data: progress += min(self.curvature_data[key]["count"] / CALIBRATION_PROGRESS_THRESHOLD, 1.0) + return (progress / len(self.required_curvatures)) * 100 - self.starpilot_planner.params.put_nonblocking("CalibrationProgress", (progress / len(self.required_curvatures)) * 100) - self.starpilot_planner.params.put_nonblocking("CurvatureData", self.curvature_data) - self.data_dirty = False - self.persistence_timer = 0.0 + def _publish_calibration_progress(self): + self._put_memory_param("CalibrationProgress", self._calibration_progress()) + + def _put_memory_param(self, key, value): + params_memory = getattr(self.starpilot_planner, "params_memory", None) + if params_memory is not None: + params_memory.put_nonblocking(key, value) def flush_data(self): self._persist_data() @@ -158,6 +171,7 @@ class CurveSpeedController: self.data_dirty = True self.update_lateral_acceleration() + self._publish_calibration_progress() self.enable_training = True if self.persistence_timer >= PLANNER_TIME: @@ -173,6 +187,7 @@ class CurveSpeedController: self.lateral_acceleration = DEFAULT_LATERAL_ACCELERATION self.starpilot_planner.params.put_nonblocking("CalibratedLateralAcceleration", self.lateral_acceleration) + self._put_memory_param("CalibratedLateralAcceleration", self.lateral_acceleration) def update_target(self, v_ego): lateral_acceleration = self.lateral_acceleration diff --git a/starpilot/system/the_galaxy/tests/test_dashboard_stats.py b/starpilot/system/the_galaxy/tests/test_dashboard_stats.py index 3a6a3ca61..485332dc9 100644 --- a/starpilot/system/the_galaxy/tests/test_dashboard_stats.py +++ b/starpilot/system/the_galaxy/tests/test_dashboard_stats.py @@ -42,6 +42,10 @@ sys.modules.setdefault("openpilot.system.loggerd.uploader", loggerd_uploader) model_manager = ModuleType("openpilot.starpilot.assets.model_manager") model_manager.canonical_model_key = lambda value: str(value or "").strip().lower().replace(" ", "-") +model_manager.external_gpu_available = lambda: False +model_manager.is_builtin_model_key = lambda key: False +model_manager.model_key_aliases = lambda key: () +model_manager.model_uses_external_gpu = lambda key: False sys.modules.setdefault("openpilot.starpilot.assets.model_manager", model_manager) starpilot_variables = ModuleType("openpilot.starpilot.common.starpilot_variables") @@ -220,6 +224,7 @@ def _install_server_import_stubs(): ) for name, value in { "ACTIVE_THEME_PATH": Path("/tmp/dashboard-test-active-theme"), + "BUTTON_FUNCTIONS": {}, "ERROR_LOGS_PATH": "/tmp/dashboard-test-errors", "EXCLUDED_KEYS": set(), "LEGACY_STARPILOT_PARAM_RENAMES": {}, @@ -1613,6 +1618,24 @@ def _load_server_module(): return module +def test_clear_generated_build_state_preserves_prebuilts_and_user_data(tmp_path): + server = _load_server_module() + sconsign = tmp_path / ".sconsign.dblite" + generated = tmp_path / "cereal" / "gen" / "cpp" / "log.capnp.h" + prebuilt = tmp_path / "prebuilt" + user_model = tmp_path / "uncompiledmodels" / "custom.onnx" + for path in (sconsign, generated, prebuilt, user_model): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("test") + + server._clear_generated_build_state(tmp_path) + + assert not sconsign.exists() + assert not (tmp_path / "cereal" / "gen").exists() + assert prebuilt.read_text() == "test" + assert user_model.read_text() == "test" + + def test_troubleshoot_steer_delay_normalizes_vehicle_delay_for_display(): server = _load_server_module() diff --git a/starpilot/system/the_galaxy/the_galaxy.py b/starpilot/system/the_galaxy/the_galaxy.py index 8a885f2f3..f2ca383b0 100644 --- a/starpilot/system/the_galaxy/the_galaxy.py +++ b/starpilot/system/the_galaxy/the_galaxy.py @@ -2161,6 +2161,18 @@ def _git_stdout(repo_path, args, timeout=15): raise RuntimeError(stderr) return (result.stdout or "").strip() +def _clear_generated_build_state(repo_path): + """Drop ignored build metadata that is unsafe to carry across revisions.""" + root = Path(repo_path) + for path in (root / ".sconsign.dblite", root / "cereal" / "gen"): + try: + if path.is_dir() and not path.is_symlink(): + shutil.rmtree(path) + else: + path.unlink(missing_ok=True) + except OSError as exception: + raise RuntimeError(f"Unable to clear stale build state at {path}: {exception}") from exception + def _git_config_get(repo_path, key): try: return _git_stdout(repo_path, ["config", "--local", "--get", key], timeout=10) @@ -2497,6 +2509,7 @@ def _fast_update_worker(): reset = _run_git(repo_path, ["reset", "--hard", "FETCH_HEAD"], timeout=120) if reset.returncode != 0: raise RuntimeError((reset.stderr or reset.stdout or "git reset failed").strip()) + _clear_generated_build_state(repo_path) _set_fast_update_progress(3, "Applying fetched commit", 100.0, "Repository reset complete.") _run_submodule_update_if_needed(repo_path, step=4) @@ -2549,6 +2562,7 @@ def _branch_switch_worker(target_branch): reset = _run_git(repo_path, ["reset", "--hard", "FETCH_HEAD"], timeout=120) if reset.returncode != 0: raise RuntimeError((reset.stderr or reset.stdout or "git reset failed").strip()) + _clear_generated_build_state(repo_path) _run_git(repo_path, ["branch", "--set-upstream-to", f"origin/{target_branch}", target_branch], timeout=30) _set_fast_update_progress(3, "Switching branch", 100.0, f"Now on '{target_branch}'.") @@ -2617,6 +2631,7 @@ def _rollback_worker(): reset = _run_git(repo_path, ["reset", "--hard", target_commit], timeout=120) if reset.returncode != 0: raise RuntimeError((reset.stderr or reset.stdout or "git reset failed").strip()) + _clear_generated_build_state(repo_path) _run_git(repo_path, ["branch", "--set-upstream-to", f"origin/{target_branch}", target_branch], timeout=30) _set_fast_update_progress(3, "Applying rollback target", 100.0, f"Now on {target_branch} @ {short_commit}.") diff --git a/system/loggerd/encoderd b/system/loggerd/encoderd index a1c139ba0..0513b40c0 100755 Binary files a/system/loggerd/encoderd and b/system/loggerd/encoderd differ diff --git a/system/micd.py b/system/micd.py index bfbc27227..580ba39d6 100755 --- a/system/micd.py +++ b/system/micd.py @@ -97,6 +97,9 @@ class Mic: @retry(attempts=10, delay=3) def get_stream(self, sd, device=None): + # reload sounddevice to reinitialize portaudio + sd._terminate() + sd._initialize() kwargs = { "channels": 1, "samplerate": SAMPLE_RATE, diff --git a/tools/laptop_device_build/Dockerfile b/tools/laptop_device_build/Dockerfile index 0cf253289..0bd540826 100644 --- a/tools/laptop_device_build/Dockerfile +++ b/tools/laptop_device_build/Dockerfile @@ -1,6 +1,6 @@ FROM ghcr.io/commaai/openpilot-base-aarch64:latest -ARG CAPNP_VERSION=1.3.0 +ARG CAPNP_VERSION=1.0.2 ENV PYTHONUNBUFFERED=1 ENV UV_LINK_MODE=copy