mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
Preserve G90 diagnostic continuity through controller startup
This commit is contained in:
@@ -2,10 +2,11 @@
|
||||
from copy import deepcopy
|
||||
import time
|
||||
|
||||
from opendbc.car import Bus
|
||||
from opendbc.car import Bus, make_tester_present_msg
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
from opendbc.car.isotp_parallel_query import IsoTpParallelQuery
|
||||
from opendbc.can.parser import MAX_BAD_COUNTER
|
||||
from opendbc.car.hyundai.ev9_keeper import EV9Keeper as StartupKeeper
|
||||
from opendbc.car.hyundai.ecu_startup import HyundaiECUStartup, Outcome, stock_copy as stock_copy
|
||||
|
||||
|
||||
@@ -16,8 +17,55 @@ def required(cp):
|
||||
|
||||
|
||||
class G90Startup(HyundaiECUStartup):
|
||||
def __init__(self, cp, callbacks):
|
||||
def __init__(self, cp, callbacks, *, clock=time.monotonic, keeper_factory=StartupKeeper):
|
||||
super().__init__(cp, callbacks, address=0x7d0, bus=0, label='G90')
|
||||
self.clock = clock
|
||||
self.keeper_factory = keeper_factory
|
||||
self.keeper = None
|
||||
self.handed_off = False
|
||||
|
||||
def prepare(self, *, admission):
|
||||
result = super().prepare(admission=admission)
|
||||
if required(self.cp) and self.outcome is Outcome.SENT_UNCONFIRMED:
|
||||
self.keeper = self.keeper_factory(
|
||||
lambda: self.callbacks[1]([make_tester_present_msg(self.address, self.bus, suppress_response=True)]), clock=self.clock)
|
||||
try:
|
||||
self.keeper.start()
|
||||
except Exception:
|
||||
if self.keeper.thread is not None and self.keeper.thread.ident is None:
|
||||
self.keeper.event.set()
|
||||
self.keeper.thread = None
|
||||
self.close()
|
||||
raise
|
||||
return result
|
||||
|
||||
def check(self):
|
||||
super().check()
|
||||
keeper = getattr(self, 'keeper', None)
|
||||
if keeper is not None and keeper.abort_reason is not None:
|
||||
keeper.stop()
|
||||
self.outcome = Outcome.ABORT_UNCERTAIN
|
||||
raise RuntimeError('G90 startup diagnostic continuity failed')
|
||||
|
||||
def maintain(self, *, configured):
|
||||
self.check()
|
||||
|
||||
def sent(self, frames, *, valid):
|
||||
keeper = getattr(self, 'keeper', None)
|
||||
if keeper is None or self.handed_off or not valid or self.closed or not self.published:
|
||||
return
|
||||
tester = make_tester_present_msg(self.address, self.bus, suppress_response=True)
|
||||
if tester not in frames:
|
||||
return
|
||||
keeper.stop()
|
||||
self.check()
|
||||
self.handed_off = True
|
||||
|
||||
def close(self):
|
||||
keeper = getattr(self, 'keeper', None)
|
||||
if keeper is not None:
|
||||
keeper.stop()
|
||||
super().close()
|
||||
|
||||
def _query(self, *args, **kwargs):
|
||||
return IsoTpParallelQuery(*args, **kwargs)
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car import gen_empty_fingerprint, make_tester_present_msg
|
||||
from opendbc.car.hyundai import g90_startup
|
||||
from opendbc.car.hyundai.ecu_startup import Outcome
|
||||
from opendbc.car.hyundai.ev9_keeper import EV9Keeper
|
||||
from opendbc.car.hyundai.g90_startup import G90Startup
|
||||
from opendbc.car.hyundai.interface import CarInterface
|
||||
from opendbc.car.hyundai.values import CAR
|
||||
|
||||
|
||||
class ManualKeeper(EV9Keeper):
|
||||
def start(self):
|
||||
self.tick()
|
||||
|
||||
|
||||
def prepared(*, aol=False, send_error=False, keeper_factory=ManualKeeper, holders=None):
|
||||
now = [8.637416955]
|
||||
sent = []
|
||||
cp = CarInterface.get_params(CAR.GENESIS_G90, gen_empty_fingerprint(), [], alpha_long=True, is_release=False, docs=False)
|
||||
assert cp.openpilotLongitudinalControl
|
||||
if aol:
|
||||
cp.safetyConfigs[0].safetyParam |= 0x400
|
||||
cp.alternativeExperience = 32
|
||||
|
||||
def send(frames):
|
||||
if send_error and make_tester_present_msg(0x7d0, 0, suppress_response=True) in frames:
|
||||
raise OSError('Actual sender rejected tester')
|
||||
sent.extend((now[0], frame) for frame in frames)
|
||||
|
||||
def query(can_send, can_recv, bus, addresses, requests, responses):
|
||||
request = requests[0]
|
||||
|
||||
class Query:
|
||||
def get_data(self, timeout):
|
||||
can_send([(0x7d0, bytes([len(request)]) + request + bytes(7 - len(request)), 0)])
|
||||
return {} if request == b"\x28\x83\x01" else {(0x7d0, None): b''}
|
||||
return Query()
|
||||
|
||||
owner = G90Startup(cp, (list, send))
|
||||
if holders is not None:
|
||||
holders.append(owner)
|
||||
owner.clock = lambda: now[0]
|
||||
owner.keeper_factory = keeper_factory
|
||||
with patch.object(g90_startup, 'IsoTpParallelQuery', side_effect=query), patch.object(g90_startup.time, 'sleep'):
|
||||
owner.prepare(admission=lambda: True)
|
||||
return owner, now, sent
|
||||
|
||||
|
||||
@pytest.mark.parametrize('aol', [False, True])
|
||||
def test_recorded_disable_to_first_controller_interval_has_continuous_tester(aol):
|
||||
owner, now, sent = prepared(aol=aol)
|
||||
tester = make_tester_present_msg(0x7d0, 0, suppress_response=True)
|
||||
try:
|
||||
assert any(frame == tester for _, frame in sent), 'No tester starts after successful ECU disable'
|
||||
for second in range(1, 7):
|
||||
now[0] = 8.637416955 + second
|
||||
owner.keeper.tick()
|
||||
now[0] = 15.046147807
|
||||
times = [stamp for stamp, frame in sent if frame == tester]
|
||||
assert len(times) == 7
|
||||
assert max(right - left for left, right in zip(times, times[1:], strict=False)) <= 1.000001
|
||||
assert now[0] - times[-1] < 1.0
|
||||
assert owner.outcome is Outcome.SENT_UNCONFIRMED
|
||||
assert all(frame == tester or frame[1][:3] == b"\x02\x10\x03" or frame[1][:4] == b"\x03\x28\x83\x01" for _, frame in sent)
|
||||
finally:
|
||||
owner.published = True
|
||||
owner.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('kind', ['invalid', 'empty', 'wrong_bus', 'wrong_payload', 'unpublished', 'valid'])
|
||||
def test_only_actual_valid_controller_tester_receipt_ends_continuity(kind):
|
||||
owner, now, sent = prepared()
|
||||
try:
|
||||
tester = make_tester_present_msg(0x7d0, 0, suppress_response=True)
|
||||
owner.published = kind != 'unpublished'
|
||||
frames = [tester]
|
||||
if kind == 'empty':
|
||||
frames = []
|
||||
elif kind == 'wrong_bus':
|
||||
frames = [(tester[0], tester[1], 1)]
|
||||
elif kind == 'wrong_payload':
|
||||
frames = [(tester[0], b"\x02\x3e\x00" + bytes(5), tester[2])]
|
||||
owner.sent(frames, valid=kind != 'invalid')
|
||||
assert owner.handed_off == (kind == 'valid')
|
||||
assert owner.keeper.event.is_set() == (kind == 'valid')
|
||||
count = len(sent)
|
||||
now[0] += 1.0
|
||||
owner.keeper.tick()
|
||||
assert len(sent) == count + (kind != 'valid')
|
||||
finally:
|
||||
owner.published = True
|
||||
owner.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fault', ['deadline', 'clock_reversal', 'sender'])
|
||||
def test_failed_continuity_cannot_be_acknowledged_or_rearmed(fault):
|
||||
owner, now, sent = prepared(send_error=fault == 'sender')
|
||||
owner.published = True
|
||||
try:
|
||||
if fault == 'deadline':
|
||||
now[0] += 60.0
|
||||
elif fault == 'clock_reversal':
|
||||
now[0] -= 1.0
|
||||
owner.keeper.tick()
|
||||
with pytest.raises(RuntimeError, match='diagnostic continuity failed'):
|
||||
owner.maintain(configured=True)
|
||||
assert owner.outcome is Outcome.ABORT_UNCERTAIN
|
||||
assert not owner.handed_off
|
||||
count = len(sent)
|
||||
now[0] += 1.0
|
||||
assert not owner.keeper.tick()
|
||||
assert len(sent) == count
|
||||
finally:
|
||||
owner.close()
|
||||
|
||||
|
||||
def test_close_stops_tester_before_unpublished_stock_restoration():
|
||||
owner, now, sent = prepared()
|
||||
requests = []
|
||||
|
||||
def query(*args, **kwargs):
|
||||
assert owner.keeper.event.is_set()
|
||||
request = args[4][0]
|
||||
requests.append(request)
|
||||
|
||||
class Query:
|
||||
def get_data(self, timeout):
|
||||
return {(0x7d0, None): b''}
|
||||
return Query()
|
||||
|
||||
with patch.object(g90_startup, 'IsoTpParallelQuery', side_effect=query):
|
||||
owner.close()
|
||||
assert requests == [b"\x10\x03", b"\x28\x00\x01", b"\x10\x01"]
|
||||
assert owner.closed and owner.restored
|
||||
count = len(sent)
|
||||
now[0] += 1.0
|
||||
assert not owner.keeper.tick()
|
||||
assert len(sent) == count
|
||||
|
||||
|
||||
def test_actual_thread_start_failure_restores_stock_and_preserves_original_error():
|
||||
holders = []
|
||||
with patch('threading.Thread.start', side_effect=OSError('thread launch failed')):
|
||||
with pytest.raises(OSError, match='thread launch failed'):
|
||||
prepared(keeper_factory=EV9Keeper, holders=holders)
|
||||
owner = holders[0]
|
||||
assert owner.closed and owner.restored
|
||||
assert not owner.published
|
||||
assert owner.keeper.event.is_set()
|
||||
assert owner.keeper.thread is None
|
||||
@@ -0,0 +1,80 @@
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car import Bus, make_tester_present_msg
|
||||
from opendbc.car.hyundai.tests.test_g90_continuity import prepared
|
||||
from opendbc.car.hyundai.interface import CarInterface
|
||||
from opendbc.car.hyundai.values import CAR, DBC
|
||||
from opendbc.safety.tests.common import CANPackerSafety
|
||||
from opendbc.safety.tests.test_hyundai import checksum
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
from openpilot.selfdrive.car.card import Car
|
||||
from openpilot.starpilot.vehicle_startup import VehicleStartupOwner
|
||||
|
||||
|
||||
@pytest.mark.parametrize('address,data', [(0x420, '501420c8b84b6a00'), (0x421, '000000ffe37f2db1')])
|
||||
@pytest.mark.parametrize('bus', [0, 2])
|
||||
def test_recorded_returned_stock_scc_keeps_native_relay_protection(address, data, bus):
|
||||
safety = libsafety_py.libsafety
|
||||
safety.set_alternative_experience(32)
|
||||
assert safety.set_safety_hooks(8, 0xC04) == 0
|
||||
safety.init_tests()
|
||||
safety.set_alternative_experience(32)
|
||||
packer = CANPackerSafety(DBC[CAR.GENESIS_G90][Bus.pt])
|
||||
for name, values, fix in (
|
||||
('WHL_SPD11', {'WHL_SPD_AliveCounter_LSB': 0, 'WHL_SPD_AliveCounter_MSB': 0}, checksum),
|
||||
('TCS13', {'AliveCounterTCS': 0}, checksum),
|
||||
('MDPS12', {}, None),
|
||||
('CLU11', {'CF_Clu_AliveCnt1': 0}, None),
|
||||
('EMS16', {'CRUISE_LAMP_S': 0, 'AliveCounter': 0}, checksum),
|
||||
('BCM_PO_11', {'LDA_BTN': 0}, None),
|
||||
):
|
||||
assert safety.safety_rx_hook(packer.make_can_msg_safety(name, 0, values, fix_checksum=fix)), name
|
||||
safety.set_timer(1000)
|
||||
safety.safety_tick()
|
||||
assert safety.safety_config_valid()
|
||||
assert not safety.get_relay_malfunction()
|
||||
safety.safety_rx_hook(libsafety_py.make_CANPacket(address, bus, bytes.fromhex(data)))
|
||||
assert bool(safety.get_relay_malfunction()) == (bus == 0)
|
||||
assert not safety.get_controls_allowed()
|
||||
assert safety.aol_get_permission_mask() == 0
|
||||
if bus == 0:
|
||||
tester = make_tester_present_msg(0x7d0, 0, suppress_response=True)
|
||||
assert not safety.safety_tx_hook(libsafety_py.make_CANPacket(tester[0], tester[2], tester[1]))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('receipt', ['valid', 'invalid', 'exception'])
|
||||
def test_current_card_publisher_transfers_continuity_only_after_success(receipt):
|
||||
owner, now, sent = prepared()
|
||||
owner.published = True
|
||||
delivered = []
|
||||
card = Car.__new__(Car)
|
||||
card.CI = CarInterface(owner.cp)
|
||||
card.CP = owner.cp
|
||||
card.vehicle_startup = VehicleStartupOwner(owner)
|
||||
|
||||
def send(service, packet):
|
||||
assert service == 'sendcan'
|
||||
assert not owner.handed_off
|
||||
if receipt == 'exception':
|
||||
raise OSError('Actual publication failed')
|
||||
delivered.append(packet)
|
||||
|
||||
try:
|
||||
with patch.object(card, 'pm', SimpleNamespace(send=send), create=True), patch.object(card, 'volt_startup_keepalive', return_value=False):
|
||||
frames = [make_tester_present_msg(0x7d0, 0, suppress_response=True)]
|
||||
if receipt == 'exception':
|
||||
with pytest.raises(OSError, match='publication failed'):
|
||||
card.publish_sendcan(frames)
|
||||
else:
|
||||
card.publish_sendcan(frames, valid=receipt == 'valid')
|
||||
assert bool(delivered) == (receipt != 'exception')
|
||||
assert owner.handed_off == (receipt == 'valid')
|
||||
count = len(sent)
|
||||
now[0] += 1.0
|
||||
owner.keeper.tick()
|
||||
assert len(sent) == count + (receipt != 'valid')
|
||||
finally:
|
||||
owner.close()
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "c319992bbe9280bf5904b8e8eb6a87d0f2d16d1a5a21482238b9d7e021ad06ff", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "226a3f00d9020c539ae74d575cbc3fb3525405d370335ae84327401130bce1b7", "schema_version": 1}
|
||||
|
||||
@@ -33,6 +33,7 @@ DEBUG_SAFETY_CLASS_TARGETS = {
|
||||
|
||||
|
||||
RELEASE_TARGETS = (
|
||||
"opendbc_repo/opendbc/car/hyundai/tests/test_g90_continuity.py",
|
||||
"opendbc_repo/opendbc/car/hyundai/tests/test_disable_response.py",
|
||||
"openpilot/starpilot/aol/tests/test_angle_stop_native_feedback.py",
|
||||
"openpilot/starpilot/aol/tests/test_angle_stop_fault_guards.py",
|
||||
|
||||
Reference in New Issue
Block a user