mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
Honda: Keep longitudinal through temporary EPS faults and ignore camera CAN interrupt-rate faults
This commit is contained in:
@@ -29,37 +29,48 @@ NIDEC = frozenset((
|
||||
BOSCH_WORDS = frozenset((0, 1, 8, 9, 10, 11))
|
||||
NIDEC_WORDS = frozenset((0, 4, 260))
|
||||
|
||||
# qualified() runs several times per 100 Hz frame in card and selfdrived, so
|
||||
# everything that depends only on this module is computed once, and its flag
|
||||
# tests use plain ints: IntFlag operators cost microseconds each in Python.
|
||||
_DETECTED = int(HondaFlags.BOSCH_EXT_HUD | HondaFlags.BOSCH_ALT_BRAKE | HondaFlags.HAS_BSM | HondaFlags.EPS_MODIFIED | HondaFlags.HYBRID)
|
||||
_ALLOWED_FLAGS = {str(car): int(car.config.flags) | _DETECTED for car in CLASSIC_BOSCH | NIDEC | RADARLESS}
|
||||
_BOSCH, _BOSCH_RADARLESS, _BOSCH_ALT_BRAKE = int(HondaFlags.BOSCH), int(HondaFlags.BOSCH_RADARLESS), int(HondaFlags.BOSCH_ALT_BRAKE)
|
||||
_NIDEC, _NIDEC_ALT_SCM_MESSAGES = int(HondaFlags.NIDEC), int(HondaFlags.NIDEC_ALT_SCM_MESSAGES)
|
||||
_UNSUPPORTED = int(HondaFlags.BOSCH_CANFD | HondaFlags.BOSCH_ALT_RADAR)
|
||||
_FIXED = structs.CarParams.FingerprintSource.fixed
|
||||
_TRANSMISSIONS = (structs.CarParams.TransmissionType.automatic, structs.CarParams.TransmissionType.cvt)
|
||||
_HONDA_NIDEC, _HONDA_BOSCH = structs.CarParams.SafetyModel.hondaNidec, structs.CarParams.SafetyModel.hondaBosch
|
||||
|
||||
|
||||
def qualified(cp, *, marked_only=False):
|
||||
if (cp.brand != 'honda' or cp.passive or cp.notCar or cp.dashcamOnly or len(cp.safetyConfigs) != 1 or
|
||||
cp.fingerprintSource == structs.CarParams.FingerprintSource.fixed or
|
||||
cp.transmissionType not in (structs.CarParams.TransmissionType.automatic, structs.CarParams.TransmissionType.cvt) or
|
||||
cp.fingerprintSource == _FIXED or cp.transmissionType not in _TRANSMISSIONS or
|
||||
cp.alternativeExperience not in ((32,) if marked_only else (0, 32))):
|
||||
return False
|
||||
safety = cp.safetyConfigs[0]
|
||||
identity = next((candidate for candidate in CLASSIC_BOSCH | NIDEC | RADARLESS if candidate == cp.carFingerprint), None)
|
||||
if identity is None:
|
||||
fingerprint = str(cp.carFingerprint)
|
||||
allowed = _ALLOWED_FLAGS.get(fingerprint)
|
||||
if allowed is None:
|
||||
return False
|
||||
detected = int(HondaFlags.BOSCH_EXT_HUD | HondaFlags.BOSCH_ALT_BRAKE | HondaFlags.HAS_BSM | HondaFlags.EPS_MODIFIED | HondaFlags.HYBRID)
|
||||
if int(cp.flags) & ~(int(identity.config.flags) | detected):
|
||||
flags = int(cp.flags)
|
||||
if flags & ~allowed:
|
||||
return False
|
||||
flags = HondaFlags(cp.flags)
|
||||
if flags & (HondaFlags.BOSCH_CANFD | HondaFlags.BOSCH_ALT_RADAR):
|
||||
if flags & _UNSUPPORTED:
|
||||
return False
|
||||
if cp.carFingerprint in NIDEC and flags & HondaFlags.NIDEC and not flags & HondaFlags.BOSCH:
|
||||
expected = 4 if flags & HondaFlags.NIDEC_ALT_SCM_MESSAGES else 0
|
||||
if cp.carFingerprint == CAR.HONDA_ODYSSEY_TWN and cp.alternativeExperience == 32:
|
||||
if fingerprint in NIDEC and flags & _NIDEC and not flags & _BOSCH:
|
||||
expected = 4 if flags & _NIDEC_ALT_SCM_MESSAGES else 0
|
||||
if fingerprint == CAR.HONDA_ODYSSEY_TWN and cp.alternativeExperience == 32:
|
||||
expected |= 256
|
||||
return (cp.pcmCruise and cp.openpilotLongitudinalControl and
|
||||
safety.safetyModel == structs.CarParams.SafetyModel.hondaNidec and safety.safetyParam == expected)
|
||||
if cp.carFingerprint in CLASSIC_BOSCH and flags & HondaFlags.BOSCH and not flags & HondaFlags.BOSCH_RADARLESS:
|
||||
expected = int(bool(flags & HondaFlags.BOSCH_ALT_BRAKE))
|
||||
safety.safetyModel == _HONDA_NIDEC and safety.safetyParam == expected)
|
||||
if fingerprint in CLASSIC_BOSCH and flags & _BOSCH and not flags & _BOSCH_RADARLESS:
|
||||
expected = int(bool(flags & _BOSCH_ALT_BRAKE))
|
||||
return (cp.pcmCruise and not cp.openpilotLongitudinalControl and
|
||||
safety.safetyModel == structs.CarParams.SafetyModel.hondaBosch and safety.safetyParam == expected)
|
||||
if cp.carFingerprint in RADARLESS and flags & HondaFlags.BOSCH_RADARLESS:
|
||||
expected = 8 | int(bool(flags & HondaFlags.BOSCH_ALT_BRAKE)) | (2 if cp.openpilotLongitudinalControl else 0)
|
||||
safety.safetyModel == _HONDA_BOSCH and safety.safetyParam == expected)
|
||||
if fingerprint in RADARLESS and flags & _BOSCH_RADARLESS:
|
||||
expected = 8 | int(bool(flags & _BOSCH_ALT_BRAKE)) | (2 if cp.openpilotLongitudinalControl else 0)
|
||||
return (bool(cp.pcmCruise) == (not cp.openpilotLongitudinalControl) and
|
||||
safety.safetyModel == structs.CarParams.SafetyModel.hondaBosch and safety.safetyParam == expected)
|
||||
safety.safetyModel == _HONDA_BOSCH and safety.safetyParam == expected)
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -94,9 +94,12 @@ static uint8_t honda_stock_aol_request_mask(void) {
|
||||
static uint8_t honda_stock_aol_permission_mask(void) {
|
||||
const uint8_t request = honda_stock_aol_request_mask();
|
||||
uint8_t permission = 0U;
|
||||
if (acc_main_on && honda_stock_aol_drive &&
|
||||
((honda_stock_aol_eps == 0U) || (honda_stock_aol_eps == 3U) || (honda_stock_aol_eps == 4U))) {
|
||||
if (controls_allowed || honda_stock_aol_token) {
|
||||
// A temporary EPS fault (2/6, e.g. a hard driver override) withdraws lateral only;
|
||||
// longitudinal stays with controls_allowed as in stock openpilot.
|
||||
const bool eps_lateral = (honda_stock_aol_eps == 0U) || (honda_stock_aol_eps == 3U) || (honda_stock_aol_eps == 4U);
|
||||
const bool eps_known = eps_lateral || (honda_stock_aol_eps == 2U) || (honda_stock_aol_eps == 6U);
|
||||
if (acc_main_on && honda_stock_aol_drive && eps_known) {
|
||||
if (eps_lateral && (controls_allowed || honda_stock_aol_token)) {
|
||||
permission = request & 1U;
|
||||
}
|
||||
if (controls_allowed) {
|
||||
|
||||
@@ -144,6 +144,38 @@ class TestHondaStockAol(unittest.TestCase):
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 1 if self.scoped else 0)
|
||||
|
||||
def test_temporary_eps_during_engagement_keeps_longitudinal(self):
|
||||
# Hard driver override: EPS reports 2/6 while engaged; only lateral may drop.
|
||||
for case in CASES:
|
||||
for eps in (2, 6):
|
||||
with self.subTest(case=case[0], eps=eps):
|
||||
self.configure(case)
|
||||
self.stream()
|
||||
self.normal_engage()
|
||||
self.safety.aol_set_host_request(3)
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 3 if self.scoped else 0)
|
||||
self.stream(eps=eps, cruise=1, request=False)
|
||||
self.assertTrue(self.safety.get_controls_allowed())
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 2 if self.scoped else 0)
|
||||
self.stream(cruise=1, request=False)
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 3 if self.scoped else 0)
|
||||
self.stream(eps=7, cruise=1, request=False)
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
|
||||
|
||||
def test_aol_only_temporary_eps_recovers_without_restart(self):
|
||||
for case in CASES:
|
||||
with self.subTest(case=case[0]):
|
||||
self.configure(case)
|
||||
self.arm()
|
||||
for eps in (2, 6, 2):
|
||||
self.stream(eps=eps)
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 0)
|
||||
self.safety.aol_set_host_request(0)
|
||||
self.stream()
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
self.assertEqual(self.safety.aol_get_permission_mask(), 1 if self.scoped else 0)
|
||||
|
||||
def test_normal_grant_session_loss_before_first_permission_query(self):
|
||||
for case in CASES:
|
||||
for loss in ("heartbeat", "sources"):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Compatibility policy for two recorded GM configurations; native authority stays separate.
|
||||
"""Compatibility policy for qualified Bosch Honda AOL and two recorded GM configurations; native authority stays separate.
|
||||
|
||||
Register divergence remains a protected-register diagnostic, not a benign-fault
|
||||
claim. Raw fault reports are retained. Every unlisted configuration/bit blocks.
|
||||
@@ -9,6 +9,13 @@ from opendbc.car.gm.cancel_remap import aol_enabled
|
||||
|
||||
def _diagnostic_mask(cp):
|
||||
try:
|
||||
if cp.brand == 'honda':
|
||||
from opendbc.car.honda.stock_aol import qualified
|
||||
from opendbc.car.honda.values import HondaFlags
|
||||
# Only the reported CAN2 interrupt-rate diagnostic is exempt. Qualification
|
||||
# requires a marked, supported, single-panda Bosch AOL configuration.
|
||||
# Other interrupt rates and mixed faults remain blocking.
|
||||
return (1 << 3) if int(cp.flags) & int(HondaFlags.BOSCH) and qualified(cp, marked_only=True) else 0
|
||||
from opendbc.car.gm.aol import qualified_gm
|
||||
from opendbc.car.gm.values import CAR, is_volt_sdgm_profile, is_bolt_present_no_acc_pedal_profile
|
||||
if cp.brand != 'gm' or len(cp.safetyConfigs) != 1:
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Only reported CAN2 load diagnostics on current qualified Bosch AOL profiles."""
|
||||
from dataclasses import replace
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car import gen_empty_fingerprint, structs
|
||||
from opendbc.car.honda.interface import CarInterface
|
||||
from opendbc.car.honda.stock_aol import qualified
|
||||
from opendbc.car.honda.values import CAR
|
||||
from openpilot.cereal import log
|
||||
from openpilot.starpilot.aol.panda_faults import blocking_fault_mask, blocking_fault_list
|
||||
from openpilot.starpilot.aol.runtime import current_native, decide_axes
|
||||
from openpilot.starpilot.aol.tests.test_panda_inventory import bound_state, sm_for, NOW
|
||||
from openpilot.starpilot.aol.tests.test_runtime import car_state
|
||||
|
||||
|
||||
PROFILES = ((CAR.HONDA_CIVIC_BOSCH, False), (CAR.HONDA_ACCORD, False),
|
||||
(CAR.HONDA_CIVIC_2022, False), (CAR.HONDA_CIVIC_2022, True))
|
||||
|
||||
|
||||
def honda(identity=CAR.HONDA_CIVIC_BOSCH, alpha=False):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[0][0x1A3] = 8
|
||||
fingerprint[1][0x1A3] = 8
|
||||
cp = CarInterface.get_params(identity, fingerprint, [], alpha, False, False)
|
||||
cp.fingerprintSource = structs.CarParams.FingerprintSource.fw
|
||||
cp.alternativeExperience = 32
|
||||
assert qualified(cp, marked_only=True)
|
||||
return cp
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity,alpha', PROFILES)
|
||||
def test_reported_can2_fault_on_qualified_bosch_is_diagnostic(identity, alpha):
|
||||
cp = honda(identity, alpha)
|
||||
assert not blocking_fault_mask(1 << 3, cp)
|
||||
assert not blocking_fault_list([log.PandaState.FaultType.interruptRateCan2], cp)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity,alpha', PROFILES)
|
||||
@pytest.mark.parametrize('fault,value', tuple(log.PandaState.FaultType.schema.enumerants.items()))
|
||||
def test_all_other_known_and_mixed_faults_still_block(identity, alpha, fault, value):
|
||||
cp = honda(identity, alpha)
|
||||
blocks = fault != 'interruptRateCan2'
|
||||
assert blocking_fault_mask(1 << value, cp) == blocks
|
||||
assert blocking_fault_mask((1 << value) | (1 << 3), cp) == blocks
|
||||
assert blocking_fault_list([value], cp) == blocks
|
||||
assert blocking_fault_list([log.PandaState.FaultType.interruptRateCan2, value], cp) == blocks
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mutation', ('unmarked', 'fixed', 'passive', 'dashcam', 'notcar', 'extra_panda',
|
||||
'wrong_model', 'wrong_word', 'unknown_flags', 'manual', 'nidec', 'unknown_identity'))
|
||||
def test_other_honda_configurations_cannot_borrow_exception(mutation):
|
||||
cp = honda()
|
||||
if mutation == 'unmarked':
|
||||
cp.alternativeExperience = 0
|
||||
elif mutation == 'fixed':
|
||||
cp.fingerprintSource = 'fixed'
|
||||
elif mutation == 'passive':
|
||||
cp.passive = True
|
||||
elif mutation == 'dashcam':
|
||||
cp.dashcamOnly = True
|
||||
elif mutation == 'notcar':
|
||||
cp.notCar = True
|
||||
elif mutation == 'extra_panda':
|
||||
cp.safetyConfigs = [cp.safetyConfigs[0], cp.safetyConfigs[0]]
|
||||
elif mutation == 'wrong_model':
|
||||
cp.safetyConfigs[0].safetyModel = 'hondaNidec'
|
||||
elif mutation == 'wrong_word':
|
||||
cp.safetyConfigs[0].safetyParam = 16
|
||||
elif mutation == 'unknown_flags':
|
||||
cp.flags |= 1 << 31
|
||||
elif mutation == 'manual':
|
||||
cp.transmissionType = 'manual'
|
||||
elif mutation == 'nidec':
|
||||
cp = honda(CAR.HONDA_CIVIC)
|
||||
else:
|
||||
cp.carFingerprint = 'UNKNOWN HONDA'
|
||||
assert blocking_fault_mask(1 << 3, cp)
|
||||
assert blocking_fault_list([log.PandaState.FaultType.interruptRateCan2], cp)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mask', (-1, 1 << 27, 1 << 31, 1 << 32))
|
||||
def test_unknown_or_malformed_faults_are_not_diagnostic(mask):
|
||||
cp = honda()
|
||||
assert blocking_fault_mask(mask, cp)
|
||||
assert blocking_fault_list([31], cp)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mutation', ('none', 'rx', 'heartbeat', 'relay', 'expired', 'wrong_session', 'denied'))
|
||||
def test_real_inventory_exception_does_not_create_authority_or_bypass_vetoes(mutation):
|
||||
cp = honda()
|
||||
state = bound_state(cp)
|
||||
slot = replace(state.pandaInventory[0], faults=1 << 3)
|
||||
if mutation == 'rx':
|
||||
slot = replace(slot, safetyRxChecksInvalid=True)
|
||||
elif mutation == 'heartbeat':
|
||||
slot = replace(slot, heartbeatLost=True)
|
||||
elif mutation == 'relay':
|
||||
slot = replace(slot, faults=(1 << 3) | 1)
|
||||
state = replace(state, pandaInventory=(slot,))
|
||||
if mutation == 'denied':
|
||||
state = replace(state, lateralAllowed=False, longitudinalAllowed=False)
|
||||
native = current_native(sm_for(cp, state), cp,
|
||||
now_ns=NOW + (200_000_001 if mutation == 'expired' else 0),
|
||||
axis_session_id='wrong' if mutation == 'wrong_session' else state.axisSessionId)
|
||||
if mutation in ('rx', 'heartbeat', 'relay', 'expired', 'wrong_session'):
|
||||
assert native is None
|
||||
return
|
||||
assert native == state
|
||||
decision = decide_axes(standard_lateral=True, standard_longitudinal=False,
|
||||
intent=SimpleNamespace(allowedLatch=True, pauseLateral=False, pauseLongitudinal=False),
|
||||
native=native, car_state=car_state(), initialized=True, model_ready=True, no_entry=False,
|
||||
immediate_disable=False, dm_lockout=False, pause_brake_mps=0.)
|
||||
assert decision.lateral_active == (mutation == 'none')
|
||||
assert not decision.longitudinal_active
|
||||
@@ -176,3 +176,28 @@ def test_gm_saved_profiles_require_existing_longitudinal_owner():
|
||||
(root / 'CustomPersonalities').write_bytes(b'1')
|
||||
(root / 'LongitudinalPersonalityProfiles').write_text('{broken')
|
||||
assert not enabled(params, ascm_params(CAR.CHEVROLET_VOLT, radar=True), 'profile', {})
|
||||
|
||||
|
||||
def test_honda_slc_controls_only_with_openpilot_long():
|
||||
from types import SimpleNamespace
|
||||
from openpilot.starpilot.feature_runtime import slc_runtime_settings, slc_transport_capable, vision_control_enabled
|
||||
|
||||
def honda(**overrides):
|
||||
return SimpleNamespace(**{'brand': 'honda', 'carFingerprint': 'HONDA_CIVIC_2022', 'openpilotLongitudinalControl': True,
|
||||
'pcmCruise': False, 'passive': False, 'dashcamOnly': False, 'notCar': False, **overrides})
|
||||
|
||||
with TemporaryDirectory() as temporary:
|
||||
params = Files(Path(temporary))
|
||||
(params.root / 'SpeedLimitController').write_bytes(b'1')
|
||||
(params.root / 'SLCPriority1').write_bytes(b'Dashboard')
|
||||
(params.root / 'SLCPriority2').write_bytes(b'Map Data')
|
||||
assert slc_transport_capable(honda(), {})
|
||||
assert enabled(params, honda(), 'slc', {})
|
||||
assert slc_runtime_settings(params, honda(), {}).enabled
|
||||
assert not enabled(params, honda(), 'vision', {})
|
||||
assert not vision_control_enabled(params, honda())
|
||||
stock = honda(openpilotLongitudinalControl=False, pcmCruise=True)
|
||||
assert enabled(params, stock, 'slc', {})
|
||||
assert not slc_runtime_settings(params, stock, {}).enabled
|
||||
assert not slc_transport_capable(honda(dashcamOnly=True), {})
|
||||
assert not enabled(params, honda(dashcamOnly=True), 'slc', {})
|
||||
|
||||
Reference in New Issue
Block a user