Fix Ford qualification coverage, MISRA declarations and test fixtures

Preserve geometry, steering limits, defaults and fault checks.
This commit is contained in:
Dom
2026-10-27 18:30:15 -05:00
parent a1d8204b25
commit 5f0bda110a
9 changed files with 87 additions and 45 deletions
@@ -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
@@ -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()
+22 -17
View File
@@ -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;
@@ -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;
@@ -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;
}
@@ -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')
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "bac00362c2a71e8b613fc38a6654dca4dfdf46b270f57f267da490c04e7cebf0", "schema_version": 1}
{"protected_inventory_sha256": "c6bef01a6bedfafd1c7e9cd62762c0fd4f5a8eeaa16018d6d5fc369c37ff56b9", "schema_version": 1}
+2 -1
View File
@@ -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)
+2 -1
View File
@@ -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)