From 5f0bda110af5192bbe14ccfcff3a14d7694648a6 Mon Sep 17 00:00:00 2001 From: Dom <168790843+firestar5683@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:30:15 -0500 Subject: [PATCH] Fix Ford qualification coverage, MISRA declarations and test fixtures Preserve geometry, steering limits, defaults and fault checks. --- .../car/ford/tests/test_blend_settings.py | 5 ++- .../car/ford/tests/test_pinion_measurement.py | 20 ++++++++++ opendbc_repo/opendbc/safety/modes/ford.h | 39 +++++++++++-------- .../safety/modes/ford_pinion_measurement.h | 36 +++++++++-------- .../opendbc/safety/tests/libsafety/safety.c | 16 ++++++-- .../galaxy/tests/test_ford_pinion_settings.py | 8 ++-- prebuilt.json | 2 +- tools/ci/safety_pytest_coverage.py | 3 +- tools/ci/tests/test_safety_qualification.py | 3 +- 9 files changed, 87 insertions(+), 45 deletions(-) diff --git a/opendbc_repo/opendbc/car/ford/tests/test_blend_settings.py b/opendbc_repo/opendbc/car/ford/tests/test_blend_settings.py index 1ee28572c3..a039847775 100644 --- a/opendbc_repo/opendbc/car/ford/tests/test_blend_settings.py +++ b/opendbc_repo/opendbc/car/ford/tests/test_blend_settings.py @@ -3,6 +3,7 @@ from types import SimpleNamespace import pytest +from opendbc.car.ford.interface import CarInterface from opendbc.car.ford.lateral_strategy import FordLateralController from opendbc.car.ford.values import CAR @@ -36,7 +37,7 @@ def test_typed_saved_blends_reach_owner_without_model(values, expected, monkeypa for key, value in zip(keys, values, strict=True): saved.put(key, value, block=True) inputs = controller_extensions.ManualTurnInputs(saved) - owner = FordLateralController(SimpleNamespace(carFingerprint=CAR.FORD_RANGER_MK2)) + owner = FordLateralController(CarInterface.get_non_essential_params(CAR.FORD_RANGER_MK2)) calls = [] setter = owner.set_blend_settings @@ -66,7 +67,7 @@ def test_typed_saved_blends_reach_owner_without_model(values, expected, monkeypa @pytest.mark.parametrize("desired,predicted,direction", ((-0.0005, -0.002, 1), (0.0005, 0.002, 2), (-0.001, -0.003, 2), (0.001, 0.003, 1))) def test_saved_blends_preserve_source_units_and_lane_direction(desired, predicted, direction): - owner = FordLateralController(SimpleNamespace(carFingerprint=CAR.FORD_RANGER_MK2)) + owner = FordLateralController(CarInterface.get_non_essential_params(CAR.FORD_RANGER_MK2)) owner.set_blend_settings(0.25, 0.65, 1.1) owner.model = SimpleNamespace(meta=SimpleNamespace(laneChangeState=2, laneChangeDirection=direction)) blend = 0.25 + min(abs(desired) / 0.001, 1.0) * 0.4 diff --git a/opendbc_repo/opendbc/car/ford/tests/test_pinion_measurement.py b/opendbc_repo/opendbc/car/ford/tests/test_pinion_measurement.py index 7e07fa4dbb..43ed71e242 100644 --- a/opendbc_repo/opendbc/car/ford/tests/test_pinion_measurement.py +++ b/opendbc_repo/opendbc/car/ford/tests/test_pinion_measurement.py @@ -155,3 +155,23 @@ def test_angle_selection_preserves_pinion_and_aol_exact_profile(car, long): cp.alternativeExperience = 32 assert cp.safetyConfigs[-1].safetyParam in AOL_WORDS assert configured(cp) and angle_qualified(cp) and qualified(cp, marked_only=True) + + +@pytest.mark.parametrize('word', [32 | ENABLE, 32 | (1 << SHIFT), 32 | ENABLE | (13 << SHIFT), + 66 | ENABLE | (1 << SHIFT), 32 | ENABLE | (1 << SHIFT) | 0x4000, + 32 | ENABLE | (1 << SHIFT) | 0x8000]) +def test_invalid_pinion_native_profile_denies_all_commands(word): + n = libsafety_py.libsafety + n.set_alternative_experience(32) + n.set_safety_hooks(structs.CarParams.SafetyModel.ford, word) + n.set_controls_allowed(True) + assert not n.get_ford_pinion_enabled() + for address in (0x3D3, 0x3D6, 0x3CA, 0x186, 0x18A, 0x3D8): + assert not n.safety_tx_hook(libsafety_py.make_CANPacket(address, 0, bytes(8))) + + +def test_pinion_rx_capacity_guard_withdraws_authority_without_overwrite(): + n = libsafety_py.libsafety + n.set_alternative_experience(32) + n.set_safety_hooks(structs.CarParams.SafetyModel.ford, 66 | ENABLE | (7 << SHIFT)) + assert n.test_ford_pinion_rx_overflow() diff --git a/opendbc_repo/opendbc/safety/modes/ford.h b/opendbc_repo/opendbc/safety/modes/ford.h index 7bc38c332c..67f2402ca7 100644 --- a/opendbc_repo/opendbc/safety/modes/ford.h +++ b/opendbc_repo/opendbc/safety/modes/ford.h @@ -114,11 +114,12 @@ static const CurvatureSteeringLimits FORD_STEERING_LIMITS = { }; static CurvatureSteeringLimits ford_measurement_limits(CurvatureSteeringLimits limits) { + const int max_curvature_error = (ford_pinion_enabled && (limits.max_curvature_error < 150)) ? 150 : limits.max_curvature_error; return (CurvatureSteeringLimits){ .max_curvature = limits.max_curvature, .curvature_to_can = limits.curvature_to_can, .frequency = limits.frequency, - .max_curvature_error = ford_pinion_enabled ? SAFETY_MAX(limits.max_curvature_error, 150) : limits.max_curvature_error, + .max_curvature_error = max_curvature_error, .curvature_error_min_speed = limits.curvature_error_min_speed, .max_steer_power = limits.max_steer_power, }; @@ -532,14 +533,31 @@ static void ford_aol_bind_tx(safety_config *ret, CanMsg workspace[7]) { } } -static RxCheck ford_rx_workspace[11]; + +static void ford_append_pinion_rx(safety_config *config, RxCheck workspace[11]) { + if (config->rx_checks_len <= 10) { + const int count = config->rx_checks_len; + for (int i = 0; i < count; i++) { + workspace[i] = config->rx_checks[i]; + } + workspace[count] = (RxCheck){.msg = {{FORD_SteeringPinion_Data, 0, 8, 100U, + .max_counter = 15U, .ignore_checksum = true}, {0}, {0}}}; + config->rx_checks = workspace; + config->rx_checks_len = count + 1; + } else { + config->tx_msgs = NULL; + config->tx_msgs_len = 0; + ford_aol_reset(); + } +} static safety_config ford_init(uint16_t configured_param) { + static RxCheck ford_rx_workspace[11]; const bool pinion_valid = ford_pinion_word_valid(configured_param); const uint16_t profile_param = ford_pinion_base_word(configured_param); const uint16_t geometry = (configured_param >> 10U) & 15U; ford_pinion_enabled = pinion_valid && ((configured_param & FORD_PINION_ENABLE) != 0U); - ford_pinion_params = ford_pinion_enabled ? &ford_pinion_geometry[geometry] : &ford_pinion_geometry[0]; + ford_pinion_params = ford_pinion_geometry_at(ford_pinion_enabled ? geometry : 0U); const uint16_t bp_param = profile_param & ~256U; const bool joint_angle = ((unsigned int)alternative_experience == 32U) && ((bp_param == 146U) || (bp_param == 147U) || (bp_param == 160U) || (bp_param == 161U) || (bp_param == 194U) || (bp_param == 195U)); @@ -746,20 +764,7 @@ static safety_config ford_init(uint16_t configured_param) { } if (ford_pinion_enabled) { // Append after profile-specific RX selection so AOL retains every existing check. - if (ret.rx_checks_len <= 10) { - const int count = ret.rx_checks_len; - for (int i = 0; i < count; i++) { - ford_rx_workspace[i] = ret.rx_checks[i]; - } - ford_rx_workspace[count] = (RxCheck){.msg = {{FORD_SteeringPinion_Data, 0, 8, 100U, - .max_counter = 15U, .ignore_checksum = true}, {0}, {0}}}; - ret.rx_checks = ford_rx_workspace; - ret.rx_checks_len = count + 1; - } else { - ret.tx_msgs = NULL; - ret.tx_msgs_len = 0; - ford_aol_reset(); - } + ford_append_pinion_rx(&ret, ford_rx_workspace); } if (!pinion_valid) { ret.tx_msgs = NULL; diff --git a/opendbc_repo/opendbc/safety/modes/ford_pinion_measurement.h b/opendbc_repo/opendbc/safety/modes/ford_pinion_measurement.h index 376a24fd60..53ad721d84 100644 --- a/opendbc_repo/opendbc/safety/modes/ford_pinion_measurement.h +++ b/opendbc_repo/opendbc/safety/modes/ford_pinion_measurement.h @@ -1,21 +1,23 @@ #pragma once #define FORD_PINION_GEOMETRY_COUNT 12U -static const AngleSteeringParams ford_pinion_geometry[FORD_PINION_GEOMETRY_COUNT + 1U] = { - {.slip_factor = 0.0f, .steer_ratio = 1.0f, .wheelbase = 1.0f}, // 0: invalid - {.slip_factor = -0.00062819555f, .steer_ratio = 17.7f, .wheelbase = 2.670f}, // 1: FORD_BRONCO_SPORT_MK1 - {.slip_factor = -0.00061892325f, .steer_ratio = 16.7f, .wheelbase = 2.710f}, // 2: FORD_ESCAPE_MK4 - {.slip_factor = -0.00061892325f, .steer_ratio = 16.7f, .wheelbase = 2.710f}, // 3: FORD_ESCAPE_MK4_5 - {.slip_factor = -0.00045454798f, .steer_ratio = 17.0f, .wheelbase = 3.690f}, // 4: FORD_EXPEDITION_MK4 - {.slip_factor = -0.00055447339f, .steer_ratio = 16.8f, .wheelbase = 3.025f}, // 5: FORD_EXPLORER_MK6 - {.slip_factor = -0.00062121569f, .steer_ratio = 15.0f, .wheelbase = 2.700f}, // 6: FORD_FOCUS_MK4 - {.slip_factor = -0.00045331952f, .steer_ratio = 16.9f, .wheelbase = 3.700f}, // 7: FORD_F_150_LIGHTNING_MK1 - {.slip_factor = -0.00042037149f, .steer_ratio = 17.0f, .wheelbase = 3.990f}, // 8: FORD_F_150_MK14 - {.slip_factor = -0.00054528036f, .steer_ratio = 17.0f, .wheelbase = 3.076f}, // 9: FORD_MAVERICK_MK1 - {.slip_factor = -0.00058852001f, .steer_ratio = 14.8f, .wheelbase = 2.850f}, // 10: FORD_MONDEO_MK5 - {.slip_factor = -0.00056209187f, .steer_ratio = 17.0f, .wheelbase = 2.984f}, // 11: FORD_MUSTANG_MACH_E_MK1 - {.slip_factor = -0.00051293030f, .steer_ratio = 17.0f, .wheelbase = 3.270f}, // 12: FORD_RANGER_MK2 -}; - +static const AngleSteeringParams *ford_pinion_geometry_at(uint16_t index) { + static const AngleSteeringParams ford_pinion_geometry[FORD_PINION_GEOMETRY_COUNT + 1U] = { + {.slip_factor = 0.0f, .steer_ratio = 1.0f, .wheelbase = 1.0f}, // 0: invalid + {.slip_factor = -0.00062819555f, .steer_ratio = 17.7f, .wheelbase = 2.670f}, // 1: FORD_BRONCO_SPORT_MK1 + {.slip_factor = -0.00061892325f, .steer_ratio = 16.7f, .wheelbase = 2.710f}, // 2: FORD_ESCAPE_MK4 + {.slip_factor = -0.00061892325f, .steer_ratio = 16.7f, .wheelbase = 2.710f}, // 3: FORD_ESCAPE_MK4_5 + {.slip_factor = -0.00045454798f, .steer_ratio = 17.0f, .wheelbase = 3.690f}, // 4: FORD_EXPEDITION_MK4 + {.slip_factor = -0.00055447339f, .steer_ratio = 16.8f, .wheelbase = 3.025f}, // 5: FORD_EXPLORER_MK6 + {.slip_factor = -0.00062121569f, .steer_ratio = 15.0f, .wheelbase = 2.700f}, // 6: FORD_FOCUS_MK4 + {.slip_factor = -0.00045331952f, .steer_ratio = 16.9f, .wheelbase = 3.700f}, // 7: FORD_F_150_LIGHTNING_MK1 + {.slip_factor = -0.00042037149f, .steer_ratio = 17.0f, .wheelbase = 3.990f}, // 8: FORD_F_150_MK14 + {.slip_factor = -0.00054528036f, .steer_ratio = 17.0f, .wheelbase = 3.076f}, // 9: FORD_MAVERICK_MK1 + {.slip_factor = -0.00058852001f, .steer_ratio = 14.8f, .wheelbase = 2.850f}, // 10: FORD_MONDEO_MK5 + {.slip_factor = -0.00056209187f, .steer_ratio = 17.0f, .wheelbase = 2.984f}, // 11: FORD_MUSTANG_MACH_E_MK1 + {.slip_factor = -0.00051293030f, .steer_ratio = 17.0f, .wheelbase = 3.270f}, // 12: FORD_RANGER_MK2 + }; + return &ford_pinion_geometry[index]; +} static bool ford_pinion_enabled = false; -static const AngleSteeringParams *ford_pinion_params = &ford_pinion_geometry[0]; +static const AngleSteeringParams *ford_pinion_params = NULL; diff --git a/opendbc_repo/opendbc/safety/tests/libsafety/safety.c b/opendbc_repo/opendbc/safety/tests/libsafety/safety.c index c01a2c55f3..066cff9216 100644 --- a/opendbc_repo/opendbc/safety/tests/libsafety/safety.c +++ b/opendbc_repo/opendbc/safety/tests/libsafety/safety.c @@ -460,7 +460,17 @@ bool safety_test_hyundai_gv60_catalog_rejection(bool alternate) { // Pinion protocol introspection for exact host/native geometry and namespace tests. uint16_t get_ford_pinion_base(uint16_t word) { return ford_pinion_base_word(word); } bool get_ford_pinion_enabled(void) { return ford_pinion_enabled; } -float get_ford_pinion_slip(int index) { return ford_pinion_geometry[index].slip_factor; } -float get_ford_pinion_ratio(int index) { return ford_pinion_geometry[index].steer_ratio; } -float get_ford_pinion_wheelbase(int index) { return ford_pinion_geometry[index].wheelbase; } +float get_ford_pinion_slip(int index) { return ford_pinion_geometry_at((uint16_t)index)->slip_factor; } +float get_ford_pinion_ratio(int index) { return ford_pinion_geometry_at((uint16_t)index)->steer_ratio; } +float get_ford_pinion_wheelbase(int index) { return ford_pinion_geometry_at((uint16_t)index)->wheelbase; } int get_ford_pinion_rx_count(void) { return current_safety_config.rx_checks_len; } + +bool test_ford_pinion_rx_overflow(void) { + RxCheck workspace[11] = {0}; + safety_config config = current_safety_config; + config.rx_checks = workspace; + config.rx_checks_len = 11; + ford_append_pinion_rx(&config, workspace); + return (config.tx_msgs == NULL) && (config.tx_msgs_len == 0) && + (config.rx_checks_len == 11) && !ford_aol_enabled; +} diff --git a/openpilot/starpilot/galaxy/tests/test_ford_pinion_settings.py b/openpilot/starpilot/galaxy/tests/test_ford_pinion_settings.py index 3d581ee785..e7ef851d4e 100644 --- a/openpilot/starpilot/galaxy/tests/test_ford_pinion_settings.py +++ b/openpilot/starpilot/galaxy/tests/test_ford_pinion_settings.py @@ -1,7 +1,6 @@ """Actual typed Params, Galaxy parked write, and startup configuration for pinion.""" from pathlib import Path import tempfile -from types import SimpleNamespace import pytest from opendbc.car import gen_empty_fingerprint @@ -20,8 +19,11 @@ def test_real_galaxy_typed_parked_selection_and_stale_dependency(): params.put_bool('AlwaysOnLateral', False, block=True) cp = ford_params(CAR.FORD_EXPLORER_MK6) parked = True - context = SimpleNamespace(sample=lambda: AuthorityContext(parked, cp, cp.as_reader().as_builder().to_bytes())) - gateway = SettingsGateway(params, context) + class TestContext: + def sample(self) -> AuthorityContext: + return AuthorityContext(parked, cp, cp.as_reader().as_builder().to_bytes()) + + gateway = SettingsGateway(params, TestContext()) try: page = gateway.page('torque', 'pinion-test', b'generation') index, row = next((i, row) for i, row in enumerate(page['rows']) if row['label'] == 'Use Pinion Yaw Sensor') diff --git a/prebuilt.json b/prebuilt.json index ded3e010cc..dff281177b 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "bac00362c2a71e8b613fc38a6654dca4dfdf46b270f57f267da490c04e7cebf0", "schema_version": 1} +{"protected_inventory_sha256": "c6bef01a6bedfafd1c7e9cd62762c0fd4f5a8eeaa16018d6d5fc369c37ff56b9", "schema_version": 1} diff --git a/tools/ci/safety_pytest_coverage.py b/tools/ci/safety_pytest_coverage.py index 60cf6914d7..72521d0e8f 100644 --- a/tools/ci/safety_pytest_coverage.py +++ b/tools/ci/safety_pytest_coverage.py @@ -31,7 +31,8 @@ def targets_for_coverage(root=ROOT): selected.add(f'{relative}::{node.name}') # These existing end-to-end callers bind the supplied library. Other vehicle suites # deliberately compile distinct DEBUG/RELEASE artifacts and retain their own gates. - for relative in ('openpilot/starpilot/car/nissan/tests/test_aol.py', + for relative in ('opendbc_repo/opendbc/car/ford/tests/test_pinion_measurement.py', + 'openpilot/starpilot/car/nissan/tests/test_aol.py', 'openpilot/starpilot/car/rivian/tests/test_aol_native.py'): if (root / relative).is_file(): selected.add(relative) diff --git a/tools/ci/tests/test_safety_qualification.py b/tools/ci/tests/test_safety_qualification.py index 8bd22caaf1..b0e3ad30a1 100644 --- a/tools/ci/tests/test_safety_qualification.py +++ b/tools/ci/tests/test_safety_qualification.py @@ -95,7 +95,8 @@ class TestSafetyQualificationRunner(TestCase): def test_coverage_catalog_includes_native_pytest_and_external_callers(self): from tools.ci import safety_pytest_coverage as coverage selected = coverage.targets_for_coverage(qualification.ROOT) - for name in ('opendbc_repo/opendbc/safety/tests/test_tesla_preap_pedal.py::test_actual_registered_priming_rate_absolute_bus_and_counter_bounds', + for name in ('opendbc_repo/opendbc/car/ford/tests/test_pinion_measurement.py', + 'opendbc_repo/opendbc/safety/tests/test_tesla_preap_pedal.py::test_actual_registered_priming_rate_absolute_bus_and_counter_bounds', 'openpilot/starpilot/car/nissan/tests/test_aol.py', 'openpilot/starpilot/car/rivian/tests/test_aol_native.py'): self.assertIn(name, selected)