diff --git a/opendbc_repo/opendbc/safety/modes/ford_aol.h b/opendbc_repo/opendbc/safety/modes/ford_aol.h index d25f0c6709..aafbb7fa28 100644 --- a/opendbc_repo/opendbc/safety/modes/ford_aol.h +++ b/opendbc_repo/opendbc/safety/modes/ford_aol.h @@ -68,6 +68,8 @@ static bool ford_aol_neutral_packet(const CANPacket_t *msg) { } else if (msg->addr == 0x3D8U) { const unsigned int lines = msg->data[6] >> 3U; neutral = ((lines == 0U) || (lines == 30U)) && (((msg->data[6] >> 1U) & 3U) == 0U); + } else { + // Other packet types do not supply neutral display state. } return neutral; } @@ -331,6 +333,8 @@ static void ford_aol_tx(const CANPacket_t *msg, bool accepted) { ford_aol_neutral_display_mask |= (msg->addr == 0x18AU) ? 1U : 2U; } else if (!accepted) { ford_aol_neutral_reset(); + } else { + // No fresh neutral proof; retain the existing lease without renewing it. } } } diff --git a/opendbc_repo/opendbc/safety/modes/hyundai_canfd_aol.h b/opendbc_repo/opendbc/safety/modes/hyundai_canfd_aol.h index 83626206f4..f04f3d6572 100644 --- a/opendbc_repo/opendbc/safety/modes/hyundai_canfd_aol.h +++ b/opendbc_repo/opendbc/safety/modes/hyundai_canfd_aol.h @@ -21,7 +21,7 @@ static inline bool hyundai_canfd_torque_long_aol_param(uint16_t param) { const uint16_t base = param & (uint16_t)~0x0800U; const uint16_t fuel = base & 3U; const uint16_t topology = base & 0x00B8U; - const bool sportage = ((param & 0x0800U) != 0U) && ((base & (uint16_t)~0x00BEU) == 0U) && + const bool sportage = ((param & 0x0800U) != 0U) && ((base & 0xFF41U) == 0U) && ((base & 4U) != 0U) && ((fuel == 0U) || (fuel == 2U)) && ((topology == 0U) || (topology == 8U) || (topology == 16U) || (topology == 144U) || (topology == 32U) || (topology == 40U)); diff --git a/openpilot/starpilot/controllers/wheel_actions.py b/openpilot/starpilot/controllers/wheel_actions.py index e94cf711b0..6396cf48e7 100644 --- a/openpilot/starpilot/controllers/wheel_actions.py +++ b/openpilot/starpilot/controllers/wheel_actions.py @@ -82,7 +82,7 @@ def button_reported(cp, key): if brand == 'ford': return bool(cp.openpilotLongitudinalControl) if brand == 'mazda': - from opendbc.car.mazda.aol import qualified + from opendbc.car.mazda.stock_aol import qualified return qualified(cp, marked_only=True) return True diff --git a/openpilot/starpilot/ui/tests/test_wheel_effective_aol_roles.py b/openpilot/starpilot/ui/tests/test_wheel_effective_aol_roles.py index 14de75573d..a2ed27c62e 100644 --- a/openpilot/starpilot/ui/tests/test_wheel_effective_aol_roles.py +++ b/openpilot/starpilot/ui/tests/test_wheel_effective_aol_roles.py @@ -49,6 +49,11 @@ def test_hkg_default_toggle_and_explicit_mapping_are_displayed_truthfully(tmp_pa assert rows['wheel:MainCruiseButtonControl'].value == 'Off' assert params.get('LKASButtonControl') == 9 assert params.get('MainCruiseButtonControl') == 0 + params.put('LKASButtonControl', 8, block=True) + rows = {row.key: row for row in wheel.rows()} + assert rows['wheel:LKASButtonControl'].value == 'Bookmark' + assert rows['wheel:MainCruiseButtonControl'].value == 'Toggle AOL' + assert params.get('LKASButtonControl') == 8 def test_mach_e_fixed_lkas_role_matches_actual_intent_and_cannot_claim_off(tmp_path): @@ -81,3 +86,41 @@ def test_ioniq6_actual_settings_owner_exposes_both_rows(tmp_path): rows = {row.key: row for row in page.rows} for key in ('LKASButtonControl', 'MainCruiseButtonControl'): assert 'Toggle AOL' in rows['wheel:' + key].choices + + +@pytest.mark.parametrize('brand', ['chrysler', 'mazda', 'nissan', 'volkswagen']) +def test_factory_main_aol_role_is_visible_without_inventing_a_button_mapping(tmp_path, brand): + from openpilot.starpilot.aol.vehicle import policy_for + + candidates = (interface.get_non_essential_params(identity) for identity, interface in interfaces.items() + if interface.__module__.split('.')[-2] == brand) + cp = next((candidate for candidate in candidates if policy_for(candidate).runtime_supported), None) + assert cp is not None, brand + params = Params(str(tmp_path)) + wheel = WheelFeature(owner(params, cp)) + rows = {row.key: row for row in wheel.rows()} + main = rows['wheel:MainCruiseButtonControl'] + assert main.value == 'Factory cruise / AOL' + assert not main.available and not main.choices + assert params.get('MainCruiseButtonControl') is None + assert not wheel.choices('MainCruiseButtonControl') + + +def test_mazda_cancel_assignment_reaches_wheel_publisher_without_missing_import(tmp_path): + from opendbc.car.mazda.values import CAR as MAZDA + from openpilot.starpilot.controllers.wheel_actions import WheelPublisher, button_reported, capture, eligible + + cp = interfaces[MAZDA.MAZDA_CX5_2022].get_non_essential_params(MAZDA.MAZDA_CX5_2022) + cp.alternativeExperience = 32 + params = Params(str(tmp_path)) + params.put('CancelButtonControl', 8, block=True) + assert eligible(cp) + assert button_reported(cp, 'CancelButtonControl') + publisher = WheelPublisher(params) + state = structs.CarState(canValid=True, gearShifter='drive') + publisher.observe(params, cp, state, now_ns=10_000_000, drive_id=1) + state.buttonEvents = [structs.CarState.ButtonEvent(type='cancel', pressed=True)] + publisher.observe(params, cp, state, now_ns=20_000_000, drive_id=1) + state.buttonEvents = [structs.CarState.ButtonEvent(type='cancel', pressed=False)] + commands = publisher.observe(params, cp, state, now_ns=30_000_000, drive_id=1) + assert commands == (('CancelButtonControl', 8),), (capture(params), publisher.mapping, publisher.cancel_tracker.__dict__, publisher.axis_keys) diff --git a/openpilot/starpilot/ui/wheel_feature.py b/openpilot/starpilot/ui/wheel_feature.py index 98dee9e47a..440ec02225 100644 --- a/openpilot/starpilot/ui/wheel_feature.py +++ b/openpilot/starpilot/ui/wheel_feature.py @@ -44,6 +44,9 @@ class WheelFeature: return None if policy.fixed_cruise_buttons and not (key == KEYS[0] and policy.auxiliary_lkas_supported): return "Toggle AOL" if cp.brand == 'hyundai' else "Factory cruise / AOL" + if (key == KEYS[1] and policy.runtime_supported and cp.brand in ('chrysler', 'mazda', 'nissan', 'volkswagen') and + not button_reported(cp, key)): + return "Factory cruise / AOL" if cp.brand == 'ford' and policy.explicit_latch and policy.runtime_supported and key == KEYS[1]: sources = [read_saved(self.owner.params, name, 8) for name in KEYS[:2]] return "Factory cruise / AOL" if all(ok and value != b'9' for value, ok in sources) else "Factory cruise" @@ -122,7 +125,7 @@ class WheelFeature: implicit_toggle = independent_cruise_buttons_supported(cp) and all(ok and value in ordinary_actions for value, ok in sources) ford_role = cp.brand == 'ford' and policy.explicit_latch and policy.runtime_supported and key == KEYS[0] - if implicit_toggle or ford_role and selected == "Off": + if (implicit_toggle or ford_role) and selected == "Off": selected = "Toggle AOL" default = next(label for label, code in ACTIONS.items() if code == DEFAULTS.get(key, 0)) rows.append(FeatureRow(PREFIX + key, label, selected or "Invalid saved action", raw, choices, @@ -131,6 +134,7 @@ class WheelFeature: reason=("LKAS also toggles AOL independently of its saved extra action." if ford_role else "Neither LKAS nor MAIN is assigned Toggle AOL, so both retain the default AOL toggle." if implicit_toggle else "Bookmark on release. Available while Left Paddle Longitudinal Pause is Off." if key == PADDLE_KEY else + "Off adds no action to the vehicle's existing button behavior. " + "Axis pause and AOL assignments apply after restarting. Other actions can apply this drive. " + "Long actions fire while held."), capability=capability, repair_value="Off" if selected is None else "")) diff --git a/prebuilt.json b/prebuilt.json index 38b72c1453..63e0475cd2 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "1270220cf581ab9d7b6da883bb4a9197967b27f4634345151bfbf0cb49048736", "schema_version": 1} +{"protected_inventory_sha256": "32675d7ccca0fc6a03db2b06173dd65ffe7e7bc760b80e4cfb91c698943609b0", "schema_version": 1}