mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-04 19:43:49 +08:00
Keep radar failures from disabling independent lateral steering
This commit is contained in:
Executable → Regular
+16
-10
@@ -57,7 +57,7 @@ from openpilot.starpilot.conditional_mode.projection import paired_clocks_ns
|
||||
from openpilot.starpilot.conditional_mode.runtime_settings import (ConditionalSettingsOwner, experimental_requested, read_mode_sources)
|
||||
from openpilot.starpilot.conditional_mode.status import settings_fingerprint
|
||||
from openpilot.starpilot.feature_runtime import enabled as feature_enabled
|
||||
from openpilot.starpilot.nostalgia import aol_no_entry, paddle_cancel, physical_cancel, saved_enabled as nostalgia_saved_enabled
|
||||
from openpilot.starpilot.nostalgia import LONGITUDINAL_ONLY_EVENTS, aol_no_entry, paddle_cancel, physical_cancel, saved_enabled as nostalgia_saved_enabled
|
||||
from openpilot.starpilot.lateral.low_speed_advisory import LowSpeedAdvisory, advisory_eligible
|
||||
from openpilot.starpilot.lateral.lane_change_status_wire import alert_wording, decode as decode_lane_status, fresh_for_model
|
||||
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroad_alert
|
||||
@@ -265,7 +265,7 @@ class SelfdriveD:
|
||||
if not_running != self.not_running_prev:
|
||||
cloudlog.event("process_not_running", not_running=not_running, error=True)
|
||||
self.not_running_prev = not_running
|
||||
self.events.add(EventName.processNotRunning)
|
||||
self.events.add(EventName.radarFault if set(not_running) == {"radard"} else EventName.processNotRunning)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -506,7 +506,7 @@ class SelfdriveD:
|
||||
self.events.add(EventName.selfdrivedLagging)
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
if self.sm['radarState'].radarErrors.canError:
|
||||
self.events.add(EventName.canError)
|
||||
self.events.add(EventName.radarFault)
|
||||
elif self.sm['radarState'].radarErrors.radarUnavailableTemporary:
|
||||
self.events.add(EventName.radarTempUnavailable)
|
||||
elif any(self.sm['radarState'].radarErrors.to_dict().values()):
|
||||
@@ -621,8 +621,11 @@ class SelfdriveD:
|
||||
no_system_errors = (not has_disable_events) or (hardware_events_added == 0 and len(self.events) == num_can_events)
|
||||
warmup_sec = 5.
|
||||
big_model_settling = self.big_model_loading or time.monotonic() < self.big_model_ready_t + warmup_sec
|
||||
if not self.sm.all_checks() and no_system_errors and not big_model_settling: # the load holds modelV2 and friends back on purpose
|
||||
if not self.sm.all_alive():
|
||||
if (not self.sm.all_checks() and not big_model_settling and
|
||||
(no_system_errors or any(event in LONGITUDINAL_ONLY_EVENTS for event in self.events.names))): # the load holds modelV2 and friends back on purpose
|
||||
if self.sm.all_checks([service for service in self.sm.services if service != 'radarState']):
|
||||
self.events.add(EventName.radarFault)
|
||||
elif not self.sm.all_alive():
|
||||
self.events.add(EventName.commIssue)
|
||||
elif not self.sm.all_freq_ok():
|
||||
self.events.add(EventName.commIssueAvgFreq)
|
||||
@@ -1707,6 +1710,7 @@ class SelfdriveD:
|
||||
lateral_events = [event for event in lateral_events if event not in (EventName.wrongGear, EventName.reverseGear)]
|
||||
return decide_axes(
|
||||
standard_lateral=self.active, standard_longitudinal=bool(self.enabled and self.CP.openpilotLongitudinalControl and
|
||||
not any(event in LONGITUDINAL_ONLY_EVENTS for event in self.events.names) and
|
||||
not any(sample.seatbeltUnlatched or sample.doorOpen for sample, _ in getattr(self, 'car_state_samples', ()))),
|
||||
intent=intent, native=native, car_state=CS, initialized=self.initialized,
|
||||
model_ready=bool(self.sm.all_checks(['modelV2', 'extrinsicsCalibration']) and
|
||||
@@ -1989,7 +1993,7 @@ class SelfdriveD:
|
||||
expired_healthy_source=getattr(self, 'aol_expired_healthy_source', False),
|
||||
previously_active=previous.lateral_active, hard_fault=bool(
|
||||
self.aol_selected_pair_future or self.aol_authority_lost or lost_active_aol or lost_permission or self.events.contains(ET.IMMEDIATE_DISABLE) or
|
||||
self.events.contains(ET.SOFT_DISABLE)),
|
||||
any(event.softDisable and event.name not in LONGITUDINAL_ONLY_EVENTS for event in self.events.to_msg())),
|
||||
model_ready=bool(self.sm.all_checks(['modelV2', 'extrinsicsCalibration']) and
|
||||
self.sm['extrinsicsCalibration'].calStatus == log.ExtrinsicsCalibration.Status.calibrated),
|
||||
user_disable=self.events.contains(ET.USER_DISABLE))
|
||||
@@ -2057,7 +2061,7 @@ class SelfdriveD:
|
||||
from openpilot.starpilot.aol.seatbelt import seatbelt_lateral_soft_event_allowed
|
||||
reverse_pause = known_reverse_pause(CS, getattr(self, 'car_state_samples', ()))
|
||||
aol_hard_soft_fault = any(event.softDisable and not (
|
||||
event.name == EventName.doorOpen or
|
||||
event.name == EventName.doorOpen or event.name in LONGITUDINAL_ONLY_EVENTS or
|
||||
event.name == EventName.wrongGear and reverse_pause or
|
||||
event.name == EventName.steerTempUnavailable and CS.steerFaultTemporary and not CS.steerFaultPermanent or
|
||||
seatbelt_lateral_soft_event_allowed(self.CP, CS, intent, native, event,
|
||||
@@ -2075,7 +2079,8 @@ class SelfdriveD:
|
||||
self.events.add(EventName.controlsMismatch)
|
||||
lost_active_aol |= pause
|
||||
was_enabled = self.enabled
|
||||
if self.aol_replay and source_current and CS.doorOpen and was_enabled:
|
||||
if self.aol_replay and source_current and was_enabled and (
|
||||
CS.doorOpen or any(event in LONGITUDINAL_ONLY_EVENTS for event in self.events.names)):
|
||||
# Ordinary cruise is withdrawn; its legacy permission is no longer expected.
|
||||
self.aol_availability_long_inhibit = True
|
||||
if not self.CP.passive and self.initialized:
|
||||
@@ -2146,9 +2151,10 @@ class SelfdriveD:
|
||||
previous.lateral_active and previous.native_acknowledged and not self.aol_axis_decision.lateral_active and
|
||||
self.aol_fault_reason in ('critical', 'availabilityPause') and not self.aol_authority_lost and
|
||||
intent is not None and intent.allowedLatch and
|
||||
any(event.softDisable and event.name != EventName.doorOpen for event in self.events.to_msg()) and
|
||||
any(event.softDisable and event.name not in (EventName.doorOpen, *LONGITUDINAL_ONLY_EVENTS)
|
||||
for event in self.events.to_msg()) and
|
||||
not self.events.contains(ET.IMMEDIATE_DISABLE)):
|
||||
# Door-open does not inhibit independent steering; ordinary LONG keeps its alert.
|
||||
# Lateral-only operation keeps ordinary longitudinal faults visible.
|
||||
self.aol_soft_takeover_alert = True
|
||||
if (self.aol_replay and intent is not None and intent.lateralArmed and
|
||||
CS.steerFaultTemporary and not CS.steerFaultPermanent and not CS.steeringPressed):
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
"""Production shared event/axis/Controls callers with controlled native peers."""
|
||||
from contextlib import ExitStack
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from openpilot.cereal import messaging
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.selfdrived.events import EventName
|
||||
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
|
||||
from openpilot.starpilot.aol.tests.test_vehicle_policy_cache import profile
|
||||
|
||||
FAMILIES = ('sdgm', 'ascm', 'flashed', 'bolt2017', 'bolt2018', 'bolt2022', 'boltacc',
|
||||
'honda', 'hyundai', 'toyota', 'ford', 'mazda', 'tesla')
|
||||
|
||||
|
||||
def params(family):
|
||||
from opendbc.car.gm.values import CAR
|
||||
if family == 'sdgm':
|
||||
from opendbc.car.gm.tests.test_volt_sdgm_control import sdgm_params
|
||||
return sdgm_params(alpha=True, sascm=True, radar=True)
|
||||
if family == 'ascm':
|
||||
from opendbc.car.gm.tests.test_bolt_volt_configurations import ordinary_params
|
||||
return ordinary_params(CAR.CHEVROLET_VOLT_ASCM, alpha=True, sascm=True, radar=True)
|
||||
if family == 'flashed':
|
||||
from opendbc.car.gm.tests.test_volt_flashed_obd import params as flashed
|
||||
return flashed()
|
||||
if family.startswith('bolt'):
|
||||
from opendbc.car.gm.tests.test_bolt_pedal import params as bolt
|
||||
candidate = {'bolt2017': CAR.CHEVROLET_BOLT_CC_2017, 'bolt2018': CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
'bolt2022': CAR.CHEVROLET_BOLT_CC_2022_2023, 'boltacc': CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL}[family]
|
||||
return bolt(candidate, pedal=True, alpha_long=True, camera=True)
|
||||
return profile(family)
|
||||
|
||||
|
||||
def callers(cleanup, family, full_op=False):
|
||||
case = fixture.IoniqAvailabilityTests(methodName='runTest')
|
||||
cleanup.callback(case.doCleanups)
|
||||
cleanup.enter_context(OpenpilotPrefix())
|
||||
cleanup.enter_context(mock.patch.dict(os.environ, {'SIMULATION': '0', 'AOL_REPLAY_RUNTIME': '1'}))
|
||||
return case._authority_callers(full_op, cp=params(family), genuine_events=True)
|
||||
|
||||
|
||||
def radar_fault(sd, error, *, other=None):
|
||||
original = sd.update_events
|
||||
def update(cs):
|
||||
radar = messaging.new_message('radarState', valid=True)
|
||||
setattr(radar.radarState.radarErrors, error, True)
|
||||
sd.sm.data['radarState'] = radar.radarState.as_reader()
|
||||
original(cs)
|
||||
# Stock-long ports do not consume radarState, but share lateral event policy.
|
||||
if not sd.CP.openpilotLongitudinalControl:
|
||||
sd.events.add(EventName.radarTempUnavailable if error == 'radarUnavailableTemporary' else EventName.radarFault)
|
||||
if other is not None:
|
||||
sd.events.add(other)
|
||||
return mock.patch.object(sd, 'update_events', side_effect=update)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('family', FAMILIES)
|
||||
@pytest.mark.parametrize('error', ('radarFault', 'radarUnavailableTemporary', 'canError'))
|
||||
def test_radar_only_fault_keeps_lateral_for_every_port(family, error):
|
||||
with ExitStack() as cleanup:
|
||||
sd, _, step = callers(cleanup, family)
|
||||
for tick in (0, 1):
|
||||
axis, _, command = step(tick, native_request_long=False, panda_controls_allowed=False)
|
||||
assert axis.lateralActive and command.latActive
|
||||
expected = EventName.radarTempUnavailable if error == 'radarUnavailableTemporary' else EventName.radarFault
|
||||
with radar_fault(sd, error):
|
||||
for tick in range(2, 22):
|
||||
axis, host, command = step(tick, native_request_long=False, panda_controls_allowed=False)
|
||||
assert expected in sd.events.names
|
||||
assert axis.lateralActive and command.latActive
|
||||
assert not host.enabled and not command.longActive and not axis.longitudinalActive
|
||||
assert not sd.aol_authority_lost and str(axis.faultReason) == 'none'
|
||||
assert EventName.controlsMismatch not in sd.events.names
|
||||
|
||||
|
||||
@pytest.mark.parametrize('family', ('sdgm', 'ascm', 'bolt2018', 'boltacc', 'honda'))
|
||||
def test_radar_withdraws_long_immediately_without_lateral_loss_or_auto_resume(family):
|
||||
with ExitStack() as cleanup:
|
||||
sd, _, step = callers(cleanup, family, full_op=True)
|
||||
assert sd.CP.openpilotLongitudinalControl
|
||||
cs = fixture.car_state()
|
||||
cs.cruiseState.enabled = True
|
||||
for tick in (0, 1):
|
||||
axis, host, command = step(tick, observed_state=cs)
|
||||
assert host.enabled and command.longActive and command.latActive
|
||||
with radar_fault(sd, 'canError'):
|
||||
for tick in range(2, 322):
|
||||
axis, host, command = step(tick, observed_state=cs, native_request_long=False,
|
||||
long_allowed=False, panda_controls_allowed=False)
|
||||
assert EventName.radarFault in sd.events.names and EventName.canError not in sd.events.names
|
||||
assert not host.enabled and not command.longActive and not axis.longitudinalActive
|
||||
assert axis.lateralActive and command.latActive
|
||||
assert EventName.controlsMismatch not in sd.events.names
|
||||
for tick in range(322, 330):
|
||||
axis, host, command = step(tick, native_request_long=False, long_allowed=False, panda_controls_allowed=False)
|
||||
assert command.latActive and not command.longActive and not host.enabled
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fault', ('canError', 'canBusMissing', 'lowMemory', 'steerUnavailable', 'modeldLagging'))
|
||||
def test_radar_never_hides_other_faults(fault):
|
||||
with ExitStack() as cleanup:
|
||||
sd, _, step = callers(cleanup, 'sdgm')
|
||||
step(0, native_request_long=False)
|
||||
with radar_fault(sd, 'canError', other=getattr(EventName, fault)):
|
||||
axis, _, command = step(1, native_request_long=False)
|
||||
assert not command.latActive and not axis.lateralActive
|
||||
assert EventName.radarFault in sd.events.names and getattr(EventName, fault) in sd.events.names
|
||||
|
||||
|
||||
@pytest.mark.parametrize('negative', ({'allowed': False}, {'native_valid': False}, {'panda_fault': True}, {'calibrated': False}))
|
||||
def test_radar_cannot_bypass_native_or_model_health(negative):
|
||||
with ExitStack() as cleanup:
|
||||
sd, _, step = callers(cleanup, 'sdgm')
|
||||
step(0, native_request_long=False)
|
||||
with radar_fault(sd, 'canError'):
|
||||
for tick in range(1, 31):
|
||||
axis, _, command = step(tick, native_request_long=False, **negative)
|
||||
assert not axis.lateralActive and not command.latActive
|
||||
|
||||
|
||||
@pytest.mark.parametrize('service', ('radarState', 'modelV2'))
|
||||
@pytest.mark.parametrize('health', ('valid', 'alive', 'freq_ok'))
|
||||
def test_only_radar_service_failure_is_longitudinal(service, health):
|
||||
with ExitStack() as cleanup:
|
||||
sd, _, step = callers(cleanup, 'sdgm')
|
||||
step(0, native_request_long=False)
|
||||
original = sd.update_events
|
||||
def update(cs):
|
||||
getattr(sd.sm, health)[service] = False
|
||||
original(cs)
|
||||
with mock.patch.object(sd, 'update_events', side_effect=update):
|
||||
axis, _, command = step(1, native_request_long=False)
|
||||
if service == 'radarState':
|
||||
assert EventName.radarFault in sd.events.names
|
||||
assert axis.lateralActive and command.latActive
|
||||
else:
|
||||
assert not axis.lateralActive and not command.latActive
|
||||
|
||||
|
||||
@pytest.mark.parametrize('names,expected', ((['radard'], EventName.radarFault),
|
||||
(['radard', 'modeld'], EventName.processNotRunning)))
|
||||
def test_dead_radard_does_not_hide_other_processes(names, expected):
|
||||
with ExitStack() as cleanup:
|
||||
sd, _, step = callers(cleanup, 'sdgm')
|
||||
step(0, native_request_long=False)
|
||||
with mock.patch('openpilot.selfdrive.selfdrived.selfdrived.driving_process_failures', return_value=names):
|
||||
axis, _, command = step(1, native_request_long=False)
|
||||
assert expected in sd.events.names
|
||||
assert bool(axis.lateralActive and command.latActive) == (names == ['radard'])
|
||||
|
||||
|
||||
@pytest.mark.parametrize('family', ('sdgm', 'honda'))
|
||||
def test_radar_fault_present_at_start_does_not_require_prior_engagement(family):
|
||||
with ExitStack() as cleanup:
|
||||
sd, _, step = callers(cleanup, family)
|
||||
with radar_fault(sd, 'radarFault'):
|
||||
for tick in range(20):
|
||||
axis, host, command = step(tick, native_request_long=False, panda_controls_allowed=False)
|
||||
assert axis.lateralActive and command.latActive and not host.enabled and not command.longActive
|
||||
|
||||
|
||||
def test_radar_exception_does_not_arm_unrequested_aol():
|
||||
with ExitStack() as cleanup:
|
||||
sd, _, step = callers(cleanup, 'sdgm', full_op=True)
|
||||
step(0, latched=False)
|
||||
with radar_fault(sd, 'radarFault'):
|
||||
axis, host, command = step(1, latched=False, native_request_long=False, panda_controls_allowed=False)
|
||||
assert not axis.lateralActive and not command.latActive and not host.enabled and not command.longActive
|
||||
@@ -7,6 +7,9 @@ from openpilot.starpilot.longitudinal.ioniq6_start import eligible as ioniq6_lon
|
||||
from openpilot.starpilot.saved_source import read_saved
|
||||
|
||||
|
||||
LONGITUDINAL_ONLY_EVENTS = (EventName.radarFault, EventName.radarTempUnavailable)
|
||||
|
||||
|
||||
ButtonType = car.CarState.ButtonEvent.Type
|
||||
|
||||
|
||||
@@ -40,7 +43,8 @@ def physical_cancel(cs, cp=None) -> bool:
|
||||
def aol_no_entry(event_names, cs, *, paddle_only_cancel: bool, cruise_main_required: bool = True,
|
||||
allow_below_engage_speed: bool = False, allow_lateral_cancel: bool = False) -> bool:
|
||||
"""Apply vehicle-owned lateral exceptions without changing ordinary cruise events."""
|
||||
return any(event not in (EventName.pedalPressed, EventName.seatbeltNotLatched, EventName.resumeBlocked, EventName.doorOpen) and
|
||||
return any(event not in (EventName.pedalPressed, EventName.seatbeltNotLatched, EventName.resumeBlocked, EventName.doorOpen,
|
||||
*LONGITUDINAL_ONLY_EVENTS) and
|
||||
ET.NO_ENTRY in EVENTS.get(event, {}) and
|
||||
(event != EventName.belowEngageSpeed or not allow_below_engage_speed) and
|
||||
(event != EventName.wrongCarMode or cruise_main_required) and
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "59cca55bbc8f9f8d42bee954161022f1014f9087f480d4b0b77ff6b27116542a", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "fbedd0567ba8be7ec8af9fb677658ab0c903b46a4a4975b74ebcefd02289d021", "schema_version": 1}
|
||||
|
||||
Reference in New Issue
Block a user