diff --git a/opendbc_repo/opendbc/car/hyundai/ioniq5pe_startup.py b/opendbc_repo/opendbc/car/hyundai/ioniq5pe_startup.py index 1fe02deccb..d7b19db6ce 100644 --- a/opendbc_repo/opendbc/car/hyundai/ioniq5pe_startup.py +++ b/opendbc_repo/opendbc/car/hyundai/ioniq5pe_startup.py @@ -244,7 +244,13 @@ class PEPendingStartup: self._tester() self.check() self.thread = self.thread_factory(target=self._loop, name='pe-startup-tester', daemon=True) - self.thread.start() + try: + self.thread.start() + except BaseException: + self.stop_event.set() + if getattr(self.thread, 'ident', None) is None: + self.thread = None + raise except BaseException: self.owner.phase = Phase.ABORT self._restore() diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_sibling_continuity.py b/opendbc_repo/opendbc/car/hyundai/tests/test_sibling_continuity.py index 890a39137e..280c693a65 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_sibling_continuity.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_sibling_continuity.py @@ -183,11 +183,28 @@ def test_actual_card_publisher_keeps_sibling_diagnostics_until_sent(identity, pa @pytest.mark.parametrize('identity', [CAR.KIA_EV9, CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_KONA_EV_2ND_GEN]) def test_existing_keeper_owners_close_after_real_thread_launch_failure(identity): - from opendbc.car.hyundai.tests.test_ioniq5pe_stock import params as ev9_params from openpilot.starpilot.vehicle_startup import VehicleStartupOwner if identity == CAR.KIA_EV9: - cp = ev9_params(candidate=identity) + import threading + from opendbc.car.hyundai.tests.test_ev9_pending_startup import pending_startup_fixture + + with pending_startup_fixture() as (fixture, owner, ci, packer): + fixture.warm(owner, ci, packer) + published = ci.CP.to_dict() + owner.thread_factory = threading.Thread + with patch('threading.Thread.start', side_effect=OSError('launch failed')): + with pytest.raises(OSError, match='launch failed'): + try: + fixture.commit(owner, ci) + finally: + owner.close() + assert owner.closed and owner.restored and owner.close_completed + assert owner.thread is None and owner.stop_event.is_set() + assert owner.fault and not owner.owner.active() and not owner.can_emit(ci.CC) + assert owner.owner.published and ci.CP.to_dict() == published + assert [request for request, _ in fixture.queries[-3:]] == [b'\x10\x03', b'\x28\x00\x01', b'\x10\x01'] + return else: fp = gen_empty_fingerprint() fp[2].update({0x50: 16, 0x2A4: 24}) @@ -197,8 +214,6 @@ def test_existing_keeper_owners_close_after_real_thread_launch_failure(identity) sent = [] owner = CarInterface.startup_owner(cp, (list, sent.extend), requested=True) assert owner is not None - if identity == CAR.KIA_EV9: - owner._ready = lambda: False requests = [] def query(send, recv, bus, addresses, commands, responses): diff --git a/opendbc_repo/opendbc/safety/tests/test_hyundai.py b/opendbc_repo/opendbc/safety/tests/test_hyundai.py index 91014b6cb6..351addc4c8 100755 --- a/opendbc_repo/opendbc/safety/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/safety/tests/test_hyundai.py @@ -507,9 +507,16 @@ class TestHyundaiLongitudinalSafetyCameraSCCRefreshHybrid(HyundaiRefreshHybridGa self.packer = CANPackerSafety("hyundai_can_refresh_generated") self.safety = libsafety_py.libsafety flags = HyundaiSafetyFlags.CAMERA_SCC | HyundaiSafetyFlags.CAN_REFRESH_MSGS | HyundaiSafetyFlags.HYBRID_GAS | HyundaiSafetyFlags.LONG + self.safety.set_alternative_experience(0) self.safety.set_safety_hooks(CarParams.SafetyModel.hyundai, flags) self.safety.init_tests() + def test_set_resume_buttons(self): + self.assertFalse(self.safety.get_controls_allowed()) + self.assertTrue(self._rx(self._button_msg(0, main_button=1))) + self.assertTrue(self._rx(self._button_msg(0, main_button=0))) + super().test_set_resume_buttons() + def test_hybrid_long_refresh_length_and_acc_allowlist(self): self.assertTrue(self._tx(common.make_msg(0, 0x485, 8))) self.assertFalse(self._tx(common.make_msg(0, 0x485, 4))) diff --git a/opendbc_repo/opendbc/safety/tests/test_hyundai_blended_cruise_ack.py b/opendbc_repo/opendbc/safety/tests/test_hyundai_blended_cruise_ack.py index 9d70b0afe3..b5e016db1a 100644 --- a/opendbc_repo/opendbc/safety/tests/test_hyundai_blended_cruise_ack.py +++ b/opendbc_repo/opendbc/safety/tests/test_hyundai_blended_cruise_ack.py @@ -37,7 +37,8 @@ class TestBlendedCruiseAcknowledgment(unittest.TestCase): helper.v_cruise_kph = helper.v_cruise_kph_last = 90. instance = SimpleNamespace(sm={'carControl': SimpleNamespace(enabled=True)}, CC_prev=SimpleNamespace(enabled=False), CS_prev=previous, - experimental_mode=False, is_metric=True, vehicle_startup=holder, v_cruise_helper=helper) + experimental_mode=False, is_metric=True, initial_cruise_speed=V_CRUISE_INITIAL, + vehicle_startup=holder, v_cruise_helper=helper) Car.update_cruise_speed(cast(Car, instance), previous) self.assertIsNone(holder.consume_cruise_resume()) return helper.v_cruise_kph diff --git a/opendbc_repo/opendbc/safety/tests/test_ioniq5pe_paired_native.py b/opendbc_repo/opendbc/safety/tests/test_ioniq5pe_paired_native.py index eecc131776..43efb9eef7 100644 --- a/opendbc_repo/opendbc/safety/tests/test_ioniq5pe_paired_native.py +++ b/opendbc_repo/opendbc/safety/tests/test_ioniq5pe_paired_native.py @@ -1,4 +1,5 @@ import unittest +from functools import cache from opendbc.can import CANPacker from opendbc.car.structs import CarParams @@ -8,7 +9,16 @@ from opendbc.safety.tests.libsafety import libsafety_py from opendbc.safety.tests.test_hyundai_ev9_long import Ev9LongFixture +@cache +def release_safety(): + default = libsafety_py.libsafety + library = libsafety_py.ffi.dlopen(libsafety_py._build_libsafety(release=True)) + assert libsafety_py.libsafety is default + return library + + class PEFixture: + RELEASE_NATIVE = False packet = Ev9LongFixture.packet feed = Ev9LongFixture.feed @@ -16,7 +26,7 @@ class PEFixture: ready = Ev9LongFixture.ready def setUp(self): - self.safety = libsafety_py.libsafety + self.safety = release_safety() if self.RELEASE_NATIVE else libsafety_py.libsafety self.packer = CANPacker('hyundai_canfd_generated') self.safety.init_tests() self.safety.set_alternative_experience(0) @@ -177,6 +187,8 @@ class TestPELongDebug(PEFixture, unittest.TestCase): class TestPELongReleaseDenied(PEFixture, unittest.TestCase): + RELEASE_NATIVE = True + def test_release_never_admits_proposed_pe_long_profile(self): for _ in range(6): diff --git a/opendbc_repo/opendbc/safety/tests/test_pe_combined_aol.py b/opendbc_repo/opendbc/safety/tests/test_pe_combined_aol.py index 0f06cc0c2e..b5392dfeae 100644 --- a/opendbc_repo/opendbc/safety/tests/test_pe_combined_aol.py +++ b/opendbc_repo/opendbc/safety/tests/test_pe_combined_aol.py @@ -8,6 +8,8 @@ from opendbc.safety.tests.test_hyundai_ev9_long import Ev9LongFixture class TestPECombinedReleaseDenied(PEFixture, unittest.TestCase): + RELEASE_NATIVE = True + def test_combined_pe_profile_has_no_transmit_authority_in_release(self): self.safety.set_alternative_experience(32) self.assertEqual(self.safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, 0x5495), 0) diff --git a/openpilot/starpilot/aol/tests/test_sonata_stock_transition.py b/openpilot/starpilot/aol/tests/test_sonata_stock_transition.py index e2ee4a421a..7ea222ad14 100644 --- a/openpilot/starpilot/aol/tests/test_sonata_stock_transition.py +++ b/openpilot/starpilot/aol/tests/test_sonata_stock_transition.py @@ -29,7 +29,7 @@ from openpilot.cereal import log disable_ecu_module = importlib.import_module('opendbc.car.disable_ecu') -def classic_frames(ci, tick, *, main=0, lkas=0, button=0, engaged=False): +def classic_frames(ci, tick, *, main=0, main_button=0, lkas=0, button=0, engaged=False): assert ci.CP.carFingerprint == CAR.HYUNDAI_SONATA_HYBRID packer = CANPacker('hyundai_can_generated') @@ -52,7 +52,7 @@ def classic_frames(ci, tick, *, main=0, lkas=0, button=0, engaged=False): counter = tick // period values = {} if name == 'CLU11': - values.update(CF_Clu_AliveCnt1=counter%16, CF_Clu_CruiseSwState=button, CF_Clu_CruiseSwMain=main) + values.update(CF_Clu_AliveCnt1=counter%16, CF_Clu_CruiseSwState=button, CF_Clu_CruiseSwMain=main_button) elif name == 'E_EMS11': values.update(CR_Vcu_AccPedDep_Pos=0) elif name == 'WHL_SPD11': @@ -204,7 +204,7 @@ def test_full_engagement_arms_aol_through_stock_off_with_main_on(raw_fault, with return send(service, raw) local.setattr(card.pm, 'send', capture) - def step(*, main=1, lkas=0, button=0, engaged=False, withdraw_request=False, bad_checksum=False, + def step(*, main=1, main_button=0, lkas=0, button=0, engaged=False, withdraw_request=False, bad_checksum=False, heartbeat_override=None, inject_relay=False, drop_wheel=False): nonlocal tick tick += 1 @@ -219,7 +219,7 @@ def test_full_engagement_arms_aol_through_stock_off_with_main_on(raw_fault, with native.set_timer(source_ns // 1000) native.set_aol_test_heartbeat(heartbeat if heartbeat_override is None else heartbeat_override) native.aol_set_host_request(request) - frames = classic_frames(card.CI, tick, main=main, lkas=lkas, button=button, engaged=engaged) + frames = classic_frames(card.CI, tick, main=main, main_button=main_button, lkas=lkas, button=button, engaged=engaged) if drop_wheel: frames = [frame for frame in frames if frame[0] != 0x386] if bad_checksum: @@ -352,6 +352,10 @@ def test_full_engagement_arms_aol_through_stock_off_with_main_on(raw_fault, with assert card.aol_card_intent.allowed_latch and not card.aol_card_intent.pause_lateral assert not state.cruiseState.enabled and state.cruiseState.available assert native_counts['lateral_only_nonzero'] > 0 + assert not card.aol_card_intent._main_held + if withdrawal == 'main-off': + for _ in range(2): + step(main=0, main_button=1, engaged=False) if withdrawal == 'lkas-off': for _ in range(2): step(main=1, lkas=1, engaged=False) diff --git a/openpilot/starpilot/flm/steerlearnerd.py b/openpilot/starpilot/flm/steerlearnerd.py index d592e1ccd6..98ac85f19b 100644 --- a/openpilot/starpilot/flm/steerlearnerd.py +++ b/openpilot/starpilot/flm/steerlearnerd.py @@ -571,7 +571,14 @@ def run( drain(control_socket, control, messaging.recv_one_or_none, sink, now) drain(applied_socket, output, messaging.recv_one_or_none, sink, now) - source_valid, now = source_current() + if collector is not None: + # Decoding queued frames may outlast the sampled shared inputs. Consume + # real pending deliveries before pose/native checks; keep their original + # headers and receipts for the next iteration instead of polling twice. + source_delivery.refresh(sm) + source_valid, now = source_current(read=collector is not None) + if tracking_pose_header < source_delivery.pose_header <= now: + tracking_pose_header = source_delivery.pose_header current_native = False if source_valid and collector is not None and collector.ready: current_native = current_native_health(sm, CP, collector.ready[-1][0], now) diff --git a/openpilot/starpilot/flm/tests/test_postdrain_sources.py b/openpilot/starpilot/flm/tests/test_postdrain_sources.py new file mode 100644 index 0000000000..56687e84b1 --- /dev/null +++ b/openpilot/starpilot/flm/tests/test_postdrain_sources.py @@ -0,0 +1,57 @@ +"""Actual daemon scheduling only; synthetic collector is not fit/native proof.""" +from types import SimpleNamespace as S +import pytest +from openpilot.starpilot.flm import steerlearnerd +from openpilot.starpilot.flm.tests.test_live_tracking_daemon import BASE, source + + +@pytest.mark.parametrize('fresh_allowed', [True, False]) +def test_pose_observer_consumes_delivery_that_arrived_during_decoding(monkeypatch, fresh_allowed): + now = [BASE] + class Stop(Exception): + pass + class SM: + updated = {'deviceMotion': False} + logMonoTime = {'deviceMotion': BASE} + marker = 'old' + pending = False + def update(self, timeout=None): + if timeout == 0 and self.pending: + self.marker = 'new' + self.updated['deviceMotion'] = True + self.logMonoTime['deviceMotion'] = now[0] + self.pending = False + sm = SM() + evidence = S(source_epoch='b'*64, source_receipt_ns=BASE, control_ns=BASE, + identity='a'*64, generation=1, domain='normalized_torque', stage_revision='stage', + response_revision='response', controller_mode='torque', policy='policy', + baseline_revision='d'*64, controller_revision='controller', turn_assist=False) + adapter = S(event_collection=True, source_plant_protocol='fixture', + bind_source=lambda value: None, select_objective=lambda key: None, prepare_control=lambda value: value) + collector = S(adapter=adapter, ready=[(evidence, object())], control=lambda *args: None) + monkeypatch.setattr('openpilot.starpilot.flm.event_collector.EventFullrateCollector', lambda value: collector) + monkeypatch.setattr('openpilot.starpilot.flm.source_plant_runtime.start_original_capture', lambda value: True) + monkeypatch.setattr('openpilot.starpilot.flm.native_health.current_native_health', + lambda *args: sm.marker == 'new' and fresh_allowed) + monkeypatch.setattr(steerlearnerd, 'read_control', lambda event: evidence) + seen = [] + def observe(_sm, cp, current, stamp, native): + seen.append((_sm.marker, stamp, native, _sm.logMonoTime['deviceMotion'])) + raise Stop + monkeypatch.setattr('openpilot.starpilot.flm.source_plant_runtime.observe', observe) + control_socket, applied_socket = object(), object() + def drain(sock, callback, *args): + if sock is control_socket: + callback(S(which=lambda:'starpilotLateralState', starpilotLateralState=S(learningEvidence=S(version=1))), None, BASE) + else: + now[0] += 80_000_000 + sm.pending = True + monkeypatch.setattr(steerlearnerd, 'drain', drain) + actor=S(ingress=None, interrupt=lambda *a,**k: None, close=lambda *a: True) + fitter=S(cancel=lambda *a: None, close=lambda *a: True) + with pytest.raises(Stop): + steerlearnerd.run(S(),sm,control_socket,applied_socket,S(),lambda *a:adapter,None, + lambda stamp:source(stamp),lambda value:('objective',),lambda value:'objective', + None,None,fitter,None,clock=lambda:now[0],live_actor=actor) + assert seen == [('new', BASE+80_000_000, fresh_allowed, BASE+80_000_000)] + assert sm.logMonoTime['deviceMotion'] == BASE+80_000_000 diff --git a/prebuilt.json b/prebuilt.json index 5b4974a8c0..a4a1a46e48 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "fd982ffeecd96a97883067e2a73d2d794c594ea548cc5c7bd82ad98e076f88d7", "schema_version": 1} +{"protected_inventory_sha256": "fd1aca7f7dace9d37ff77f6dbd3da45e95cf674ac48b3bb1a0a26ddad8f3435f", "schema_version": 1}