Preserve fresh GM lateral authority while braking

This commit is contained in:
firestar5683
2026-10-21 10:54:26 -05:00
parent 223c4f1322
commit 1319c60fda
5 changed files with 523 additions and 18 deletions
+24 -17
View File
@@ -803,12 +803,12 @@ class SelfdriveD:
return CS
def held_axis_override_wait(self, CS, proof, now_ns):
def held_axis_override_wait(self, CS, proof, now_ns, *, fresh_successor=False):
"""Recognize an already applied override; never admit a new physical edge."""
if (proof is None or not (qualified_ioniq6(self.CP) or qualified_gm(self.CP)) or not self.sm.seen['carControl'] or
not self.sm.valid['carControl'] or self.sm['carControl'].longActive or
not 0 < int(self.sm.logMonoTime['carControl']) <= now_ns or
now_ns - int(self.sm.logMonoTime['carControl']) > INTENT_MAX_AGE_NS):
not fresh_successor and now_ns - int(self.sm.logMonoTime['carControl']) > INTENT_MAX_AGE_NS):
return False, False
previous = self.CS_prev
samples = getattr(self, 'car_state_samples', ()) or ((CS, self.conditional_car_state_valid),)
@@ -1273,6 +1273,17 @@ class SelfdriveD:
source_ns = self.aol_car_state_log_ns
intent = getattr(self, 'aol_last_intent', None)
companion = getattr(self, 'aol_intent_companion', None)
fresh_successor = bool(proof[1] < source_ns <= now_ns and now_ns - source_ns <= INTENT_MAX_AGE_NS and
self.conditional_car_state_valid and CS.canValid and not CS.canTimeout and
intent is not None and intent.carStateLogMonoTime == source_ns and
intent.producerSessionId == proof[4] and intent.settingsQualified and
0 < intent.observedMonoTime <= now_ns <= intent.validUntilMonoTime and
now_ns - intent.observedMonoTime <= INTENT_MAX_AGE_NS and
companion is not None and companion.integrity_epoch == proof[5] and
decision.native_acknowledged and not self.aol_authority_lost and
getattr(self, 'aol_fault_reason', 'none') == 'none' and
not self.events.contains(ET.IMMEDIATE_DISABLE) and
(not decision.longitudinal_active or self.enabled and self.active))
user_disable = self.events.contains(ET.USER_DISABLE)
if user_disable:
from opendbc.car.hyundai.canfd_angle_aol import qualified as angle_qualified
@@ -1287,21 +1298,17 @@ class SelfdriveD:
if stock_lateral_role(self, CS, native, intent, allow_independent_cancel=True,
allow_independent_brake=True, now_ns=now_ns):
user_disable = False
if user_disable and self.held_axis_override_wait(CS, proof, now_ns)[0]:
# A moving held brake repeats pedalPressed while ordinary control is
# already disabled. Its existing lateral-only override is not a new edge.
user_disable = any(event.userDisable and event.name != EventName.pedalPressed for event in self.events.to_msg())
if (proof[1] < source_ns <= now_ns and now_ns - source_ns <= INTENT_MAX_AGE_NS and
self.conditional_car_state_valid and CS.canValid and not CS.canTimeout and
intent is not None and intent.carStateLogMonoTime == source_ns and
intent.producerSessionId == proof[4] and intent.settingsQualified and
0 < intent.observedMonoTime <= now_ns <= intent.validUntilMonoTime and
now_ns - intent.observedMonoTime <= INTENT_MAX_AGE_NS and
companion is not None and companion.integrity_epoch == proof[5] and
decision.native_acknowledged and not self.aol_authority_lost and
getattr(self, 'aol_fault_reason', 'none') == 'none' and
not user_disable and not self.events.contains(ET.IMMEDIATE_DISABLE) and
(not decision.longitudinal_active or self.enabled and self.active)):
if user_disable and self.held_axis_override_wait(CS, proof, now_ns, fresh_successor=fresh_successor and qualified_gm(self.CP))[0]:
# A fresh same-owner source can establish its own LAT permission after
# an old output expires. Held-brake continuity does not renew that output;
# the current native proof below still has to grant the new decision.
stock_already_off = bool(fresh_successor and qualified_gm(self.CP) and self.CP.pcmCruise and
not self.CP.openpilotLongitudinalControl and not CS.cruiseState.enabled and
not self.CS_prev.cruiseState.enabled and all(valid and not sample.cruiseState.enabled
for sample, valid in getattr(self, 'car_state_samples', ())))
user_disable = any(event.userDisable and event.name != EventName.pedalPressed and not
(stock_already_off and event.name == EventName.pcmDisable) for event in self.events.to_msg())
if fresh_successor and not user_disable:
native = current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id)
prior_native = getattr(self, 'aol_published_input_native', None)
if (native is not None and prior_native is not None and native.pandaSerial == prior_native.pandaSerial and
@@ -54,6 +54,10 @@ def test_final_handoff_full_owner(mode, condition):
assert command.longActive == (mode == 'ordinary')
deadline = original.validUntilMonoTime
now = [BASE + 29_900_000]
def empty_control_receive(timeout):
assert timeout == 0
actual_update_msgs(now[0] / 1e9, [])
cleanup.enter_context(mock.patch.object(controls.sm, 'update', side_effect=empty_control_receive))
queued, wires = deque(), deque()
decisions, samples, states, deferred_proofs = [], [], [], []
original_receive = messaging.recv_one_or_none
@@ -0,0 +1,446 @@
"""SDGM fresh held-brake successor across the recorded source/decision timing pair.
Model/readiness and receive transport are bench fixtures; ECU I/O is skipped.
Neither ordinary controls_allowed nor an active steering command is seeded.
"""
from contextlib import ExitStack
from unittest.mock import patch
import pytest
from opendbc.can import CANPacker
from opendbc.car import Bus, structs
from opendbc.car.gm.tests.test_volt_sdgm_control import sdgm_params
from opendbc.car.gm.values import DBC
from opendbc.safety.tests.libsafety import libsafety_py as native
from openpilot.cereal import messaging
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.aol.intent import read_settings
from openpilot.starpilot.aol.tests import test_gm as gm
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
from openpilot.starpilot.aol.tests.test_gm_startup_main import parsed_packets
from openpilot.starpilot.aol.wire import PandaSlot, SafetyState, encode_safety
from openpilot.starpilot.lateral.tests.test_lane_runtime import feed
def sdgm_packets(cp, packer, now, tick, speed, main, gesture):
speed = (5.858360767 if tick < 390 else 5.836455345) if speed > 0 else 0.0
frames = parsed_packets(cp, packer, now, tick, speed, main)
engine = packer.make_can_msg(
"ECMEngineStatus",
0,
{"CruiseMainOn": int(main), "BrakePressed": int(tick > 300)},
)
brake = packer.make_can_msg("ECMAcceleratorPos", 0, {"BrakePedalPos": 8 if tick > 300 else 0})
frames = [f for f in frames if f[0] not in (engine[0], brake[0])]
return frames + [engine, brake]
@pytest.mark.parametrize("alpha", [False, True])
@pytest.mark.parametrize("c9", [False, True])
def test_actual_sdgm_held_brake_fresh_successor_after_expired_output(c9, alpha):
exercise_sdgm(c9=c9, alpha=alpha)
def exercise_sdgm(c9, alpha, condition="healthy"):
main_action = 0
gesture = "held_brake"
cp = sdgm_params(alpha=alpha, brake_c9=c9)
packet_factory = sdgm_packets
below_engage = False
case = fixture.IoniqAvailabilityTests(methodName="runTest")
with OpenpilotPrefix(), ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
sd, controls, step = case._authority_callers(False, cp=cp, genuine_events=True)
sd.params.put("MainCruiseButtonControl", main_action, block=True)
sd.aol_settings = read_settings(sd.params)
# The initial real Controls result is disabled because no authority wire exists.
# This boot-neutral fixture establishes no Panda grant or active output.
feed(controls, 10_000_000_000, 0, active=False, enabled=False)
boot, boot_log = controls.state_control()
assert not boot.latActive and not boot.longActive
controls.publish(boot, boot_log)
card = gm.TestGmAol.card(cp, sd.params)
cleanup.callback(card.vehicle_startup.close)
card.ci_initialized = True
card.car_params_published = True
card.sm.frame = 1
assert card.CP.to_dict() == sd.CP.to_dict() == controls.CP.to_dict()
original_pm = card.pm
captured = {}
intent_queue = []
companion_queue = []
original_optional = messaging.recv_one_or_none
def deliver(sock):
if sd.steering_companion is not None and sock is sd.steering_companion.sock:
return companion_queue.pop(0) if companion_queue else None
if sd.aol_intent_companion is not None and sock is sd.aol_intent_companion.sock:
return intent_queue.pop(0) if intent_queue else None
return original_optional(sock)
cleanup.enter_context(
patch(
"openpilot.selfdrive.selfdrived.selfdrived.messaging.recv_one_or_none",
side_effect=deliver,
)
)
class Tee:
def send(self, service, message):
original_pm.send(service, message)
event = messaging.log_from_bytes(message) if isinstance(message, bytes) else message.as_reader()
captured[service] = event.as_builder()
if service == "aolIntentWire":
intent_queue.append(event)
if service == "starpilotCarState":
companion_queue.append(event)
card.pm = Tee()
sd_receive = sd.sm.update_msgs
last_sd_control_stamp = 0
def receive_sd(stamp, rows):
nonlocal last_sd_control_stamp
# Deliver the original last published Controls envelope without renewing its source.
published = controls.pm.messages.get("carControl")
rows = [r for r in rows if r.which() != "carControl"]
if published is not None and published.logMonoTime != last_sd_control_stamp:
rows.append(published)
last_sd_control_stamp = int(published.logMonoTime)
return sd_receive(stamp, rows)
cleanup.enter_context(patch.object(sd.sm, "update_msgs", side_effect=receive_sd))
safety = native.libsafety
safety.init_tests()
safety.set_alternative_experience(cp.alternativeExperience)
assert safety.set_safety_hooks(4, cp.safetyConfigs[0].safetyParam) == 0
# Establish actual neutral producer cadence before Card publishes carOutput.
card_receive = card.sm.update_msgs
card_receive(10.0, [controls.pm.messages["carControl"]])
boot_rows = []
with patch.object(
controls.sm,
"update_msgs",
side_effect=lambda stamp, rows: boot_rows.extend(rows),
):
feed(controls, 10_010_000_000, 1, active=False, enabled=False)
controls.sm.update_msgs(10.01, boot_rows)
clocked = messaging.new_message
with patch(
"openpilot.selfdrive.controls.controlsd.messaging.new_message",
side_effect=lambda *args, **kwargs: clocked(*args, **dict(kwargs, logMonoTime=10_010_000_000)),
):
neutral, neutral_log = controls.state_control()
assert not neutral.latActive and not neutral.longActive
controls.publish(neutral, neutral_log)
card_receive(10.01, [controls.pm.messages["carControl"]])
last_control_stamp = int(controls.pm.messages["carControl"].logMonoTime)
delivered = {}
if card.volt_cc_selected:
# Establish the real clock floor before any fresh physical/control input.
with (
patch(
"openpilot.selfdrive.car.card.time.monotonic_ns",
return_value=10_010_000_000,
),
patch(
"openpilot.selfdrive.car.card.time.clock_gettime_ns",
return_value=10_010_000_000,
),
):
assert not card.volt_cc_control_current()
def wire_batch(now):
mask = safety.aol_get_permission_mask()
request = safety.aol_get_request_mask()
invalid = not safety.safety_config_valid()
relay = safety.get_relay_malfunction()
p = messaging.new_message("pandaStates", 1, valid=True, logMonoTime=now)
slot = p.pandaStates[0]
slot.safetyModel = 4
slot.safetyParam = cp.safetyConfigs[0].safetyParam
slot.alternativeExperience = cp.alternativeExperience
slot.controlsAllowed = safety.get_controls_allowed()
slot.safetyRxChecksInvalid = invalid
slot.faults = ["relayMalfunction"] if relay else []
w = messaging.new_message("aolSafetyWire", 0, valid=True, logMonoTime=now)
w.aolSafetyWire = encode_safety(
SafetyState(
1,
True,
now,
now + 200_000_000,
4,
cp.safetyConfigs[0].safetyParam,
bool(mask & 1),
bool(mask & 2),
bool(request & 1),
bool(request & 2),
"authority-panda",
sd.aol_session_id,
now,
(
PandaSlot(
0,
"authority-panda",
4,
cp.safetyConfigs[0].safetyParam,
cp.alternativeExperience,
safety.get_controls_allowed(),
invalid,
False,
1 if relay else 0,
),
),
)
)
return p.as_reader(), w.as_reader()
def receive_card(now, *, control=True):
nonlocal last_control_stamp
rows = list(wire_batch(now))
if card.volt_cc_selected:
device = messaging.new_message("deviceState", valid=True, logMonoTime=now)
device.deviceState.started, device.deviceState.startedMonoTime = (
True,
9_000_000_000,
)
rows.append(device.as_reader())
rows += [sd.pm.messages[k] for k in ("aolAxisState", "selfdriveState", "onroadEvents") if k in sd.pm.messages]
published = controls.pm.messages["carControl"]
if control and published.logMonoTime != last_control_stamp:
rows.append(published)
last_control_stamp = int(published.logMonoTime)
unique = []
for event in rows:
name = event.which()
key = (int(event.logMonoTime), event.as_builder().to_bytes())
if delivered.get(name) != key:
unique.append(event)
delivered[name] = key
card_receive(now / 1e9, unique)
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
def physical(tick, stamp, driving):
speed = max(4.0, cp.minSteerSpeed + 2.0)
if below_engage:
assert speed < cp.minEnableSpeed
else:
speed = max(speed, cp.minEnableSpeed + 2.0)
packets = packet_factory(cp, packer, stamp, tick - 100, speed if driving else 0.0, True, gesture)
packets = [f for f in packets if f[0] not in (0x1F5, 0x12A, 0x184, 0x17D)]
if cp.pcmCruise and cp.networkLocation == structs.CarParams.NetworkLocation.fwdCamera:
address = packer.make_can_msg("ASCMActiveCruiseControlStatus", 2, {})[0]
packets = [f for f in packets if not (f[0] == address and f[2] == 2)]
# Factory camera reports adaptive standby, not non-adaptive cruise mode.
packets.append(packer.make_can_msg("ASCMActiveCruiseControlStatus", 2, {"ACCCruiseState": 2}))
packets += [
packer.make_can_msg("ESPStatus", 0, {"TractionControlOn": 1}),
packer.make_can_msg("ECMPRDNL2", 0, {"PRNDL2": 4 if driving else 1}),
packer.make_can_msg("BCMDoorBeltStatus", 0, {"LeftSeatBelt": int(driving)}),
packer.make_can_msg("PSCMStatus", 0, {"LKATorqueDeliveredStatus": int(driving)}),
]
return packets
for warm in range(100):
stamp = 9_000_000_000 + warm * 10_000_000
safety.set_timer(stamp // 1000)
safety.set_aol_test_heartbeat(True)
safety.aol_set_host_request(0)
packets = physical(warm, stamp, False)
for a, data, bus in packets:
safety.safety_fwd_hook(bus, a)
assert safety.safety_rx_hook(native.make_CANPacket(a, bus, data))
card.CI.update([(stamp, packets)])
safety.safety_tick()
assert safety.safety_config_valid() and safety.aol_get_permission_mask() == 0
tx = 0
active = 0
checkpoints = {}
old_axis = None
for tick in range(1, 391):
now = 10_020_000_000 + (tick - 1) * 10_000_000
if tick == 390:
now += 20_271_875
decision_ns = now + (6_268_334 if tick == 389 else 11_981_615 if tick == 390 else 0)
driving = tick > 300
safety.set_timer(now // 1000)
safety.set_aol_test_heartbeat(True)
packets = physical(tick + 100, now, driving)
for a, data, bus in packets:
safety.safety_fwd_hook(bus, a)
assert safety.safety_rx_hook(native.make_CANPacket(a, bus, data))
raw = messaging.new_message('can', len(packets), valid=True, logMonoTime=now)
for dst, (address, data, bus) in zip(raw.can, packets, strict=True):
dst.address, dst.dat, dst.src = address, data, bus
with patch('openpilot.selfdrive.car.card.messaging.drain_sock_raw', return_value=[raw.to_bytes()]), \
patch.object(card.sm, 'update', side_effect=lambda timeout, _now=now: receive_card(_now)), \
patch('openpilot.selfdrive.car.card.time.monotonic_ns', return_value=now), \
patch('openpilot.selfdrive.car.card.time.clock_gettime_ns', return_value=now):
cs, _ = card.state_update(defer_radar=True)
safety.safety_tick()
assert cs.canValid and not cs.canTimeout
assert card._aol_can_observation[0] is cs and card._aol_can_observation[1:3] == (now, now)
allowed = card.aol_card_intent.output(cs)[0]
request = int(allowed) | (2 if sd.aol_axis_decision.desired_longitudinal else 0)
safety.set_timer(decision_ns // 1000)
safety.aol_set_host_request(request)
mask = safety.aol_get_permission_mask()
message = messaging.new_message
with patch(
"openpilot.selfdrive.car.card.messaging.new_message",
side_effect=lambda *args, _clock=message, _now=now, **kwargs: _clock(*args, **dict(kwargs, logMonoTime=_now)),
):
card.state_publish(cs, None)
negative = tick == 390 and condition != "healthy"
if negative:
# Typed receiver perturbations start after a complete actual CAN/native
# baseline; they cannot manufacture native permission or renew old output.
from dataclasses import replace
from openpilot.starpilot.aol.wire import decode_intent, encode_intent
current = captured["carState"].carState
if condition == "new_brake":
sd.CS_prev = sd.CS_prev.as_builder()
sd.CS_prev.brakePressed = False
elif condition == "new_paddle":
current.regenBraking = True
elif condition == "new_gas":
current.gasPressed = True
elif condition == "cancel":
current.buttonEvents = [structs.CarState.ButtonEvent(type="cancel", pressed=True)]
elif condition == "invalid_current":
current.canValid, current.canTimeout = False, True
elif condition == "permanent_eps":
current.steerFaultPermanent = True
elif condition == "main_off":
current.cruiseState.available = False
elif condition == "gear_unknown":
current.gearShifter = "unknown"
elif condition == "stock_disable_edge":
sd.CS_prev = sd.CS_prev.as_builder()
sd.CS_prev.cruiseState.enabled = True
if condition in ("future_current", "old_current"):
source = decision_ns + 1 if condition == "future_current" else old_axis.sourceCarStateMonoTime
captured["carState"].logMonoTime = source
wire = captured["aolIntentWire"]
wire.aolIntentWire = encode_intent(replace(decode_intent(wire.aolIntentWire), carStateLogMonoTime=source))
changes = {"producer": {"producerSessionId": "other-card"}, "settings": {"settingsQualified": False},
"pause_lateral": {"pauseLateral": True}}
if condition in changes:
wire = captured["aolIntentWire"]
wire.aolIntentWire = encode_intent(replace(decode_intent(wire.aolIntentWire), **changes[condition]))
if condition == "epoch":
sd.aol_intent_companion.integrity_epoch += 1
axis, _, cc = step(
tick,
state_stamp=now,
offset_ns=decision_ns - 10_000_000_000 - tick * 10_000_000,
car_state_message=captured["carState"],
intent_message=captured["aolIntentWire"],
car_output_message=captured["carOutput"],
car_output_valid=captured["carOutput"].valid,
allowed=bool(mask & 1) and not (negative and condition == "native_denied"),
native_valid=not (negative and condition == "native_invalid"),
panda_fault=negative and condition == "rx_fault",
relay_fault=negative and condition == "relay_fault",
long_allowed=bool(mask & 2),
native_request_lat=bool(request & 1),
native_request_long=bool(request & 2),
panda_controls_allowed=safety.get_controls_allowed(),
authority_message=captured.get("starpilotCarState"),
)
if negative:
assert old_axis.validUntilMonoTime < decision_ns
assert not axis.lateralActive and not cc.latActive, (condition, axis.to_dict(), sd.events.names)
assert not axis.longitudinalActive and not cc.longActive
return
if tick == 389:
old_axis = axis
receive_card(decision_ns, control=False)
if tick == 390:
assert old_axis.validUntilMonoTime < decision_ns
old_deadline = old_axis.validUntilMonoTime
receive_card(decision_ns)
assert old_axis.validUntilMonoTime == old_deadline
assert axis.validUntilMonoTime == decision_ns + 30_000_000
assert not cc.enabled and not cc.longActive and not safety.get_controls_allowed()
if tick == 390:
assert not sd.aol_authority_lost and str(axis.faultReason) == "none"
assert axis.lateralActive and cc.latActive and not axis.longitudinalActive
assert axis.sourceCarStateMonoTime == now and axis.observedMonoTime == decision_ns
if tick in (360, 420, 480, 520):
checkpoints[tick] = (
bool(cc.enabled),
bool(cc.longActive),
bool(cc.latActive),
card.v_cruise_helper.v_cruise_kph,
bool(cs.brakePressed),
bool(cs.regenBraking),
)
if not driving:
assert str(cs.gearShifter) == "park" and cs.seatbeltUnlatched and cs.standstill
assert not axis.lateralActive and not cc.latActive and safety.aol_get_request_mask() == 0 and mask == 0
assert str(axis.faultReason) != "critical", (
tick,
axis.to_dict(),
sd.events.names,
)
failed = card.aol_disarming_fault(cs, int(card.sm.logMonoTime["onroadEvents"]), decision_ns)
if failed:
print(
"SDGM_FAILURE",
tick,
gesture,
axis.to_dict(),
[e.to_dict() for e in sd.events.to_msg()],
card.aol_transport_feedback.__dict__,
checkpoints,
)
assert not failed
assert card.aol_card_intent.allowed_latch and not card.aol_card_intent._main_cycle_required
command = card.sm["carControl"]
captured.pop("sendcan", None)
with (
patch(
"openpilot.selfdrive.car.card.time.monotonic_ns",
return_value=decision_ns + 2,
),
patch(
"openpilot.selfdrive.car.card.time.monotonic",
return_value=(decision_ns + 2) / 1e9,
),
patch(
"openpilot.selfdrive.car.card.time.clock_gettime_ns",
return_value=decision_ns + 2,
),
):
card.controls_update(cs, command)
emitted = captured["sendcan"]
if not emitted.valid:
# The first command is exactly at the real source floor; it cannot emit.
assert tick == 1 and not emitted.sendcan and not cc.enabled and not cc.latActive and not cc.longActive
card.CS_prev = cs
continue
assert emitted.valid
for frame in emitted.sendcan:
assert safety.safety_tx_hook(native.make_CANPacket(frame.address, frame.src, frame.dat))
tx += 1
if frame.address == 0x180:
if not driving:
assert not frame.dat[0] & 8
else:
active += bool(frame.dat[0] & 8)
card.CS_prev = cs
assert active > 0 and tx > 0 and card.aol_card_intent.allowed_latch
assert not sd.aol_authority_lost
@pytest.mark.parametrize("c9", [False, True])
@pytest.mark.parametrize("condition", ["new_brake", "new_paddle", "cancel", "invalid_current", "future_current",
"old_current", "new_gas", "producer", "epoch", "settings", "pause_lateral", "native_denied", "native_invalid", "rx_fault",
"relay_fault", "permanent_eps", "main_off", "gear_unknown", "stock_disable_edge"])
def test_fresh_held_successor_rejects_changed_identity_health_or_physical_input(c9, condition):
exercise_sdgm(c9=c9, alpha=condition != "stock_disable_edge", condition=condition)
@@ -1,16 +1,45 @@
"""Volt route c0508: source cutoff must not replace an unexpired axis proof."""
from collections import deque
from contextlib import ExitStack
from unittest.mock import patch
import pytest
from opendbc.car.gm.tests.test_volt_sdgm_control import sdgm_params
from openpilot.cereal import messaging
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.aol.intent import read_settings
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
from openpilot.starpilot.aol.tests.test_input_deadline_continuity import BASE, intent
def bind_steering_receipts(sd, step, cleanup):
"""Deliver the same declared producer receipt through its actual socket path."""
if sd.steering_companion is None:
return step
pending = deque()
original = messaging.recv_one_or_none
def receive(sock):
if sock is sd.steering_companion.sock:
return pending.popleft() if pending else None
return original(sock)
cleanup.enter_context(patch('openpilot.selfdrive.selfdrived.selfdrived.messaging.recv_one_or_none', side_effect=receive))
def observed_step(tick, **kwargs):
if not kwargs.get('timeout', False):
stamp = kwargs.get('state_stamp', 10_000_000_000 + tick * 10_000_000 + kwargs.get('offset_ns', 0))
receipt = messaging.new_message('starpilotCarState', valid=True, logMonoTime=stamp)
receipt.starpilotCarState.sourceCarStateMonoTime = stamp
receipt.starpilotCarState.lateralAuthorityUnavailable = kwargs.get('authority_unavailable', False)
kwargs['authority_message'] = receipt
pending.append(receipt.as_reader())
return step(tick, **kwargs)
return observed_step
def profiles():
from opendbc.car.gm.tests.test_volt_camera_control import camera_params
from opendbc.car.gm.tests.test_volt_camera_removed import removed_params
@@ -38,6 +67,7 @@ def test_volt_cutoff_keeps_only_original_lease_then_consumes_next_source(mode, s
cleanup.callback(case.doCleanups)
full = mode == 'ordinary'
sd, controls, step = case._authority_callers(full, cp=cp.as_reader().as_builder(), genuine_events=True)
step = bind_steering_receipts(sd, step, cleanup)
sd.aol_settings = read_settings(sd.params)
cs = fixture.car_state(brake=mode == 'brake')
cs.regenBraking = mode == 'regen'
@@ -46,6 +76,13 @@ def test_volt_cutoff_keeps_only_original_lease_then_consumes_next_source(mode, s
assert old.lateralActive and command.latActive and bool(command.longActive) == full
deadline = old.validUntilMonoTime
clock = [BASE + 29_900_000]
actual_controls_receive = controls.sm.update_msgs
def empty_control_receive(timeout):
assert timeout == 0
actual_controls_receive(clock[0] / 1e9, [])
cleanup.enter_context(patch.object(controls.sm, 'update', side_effect=empty_control_receive))
original = sd.update_process_health
def finish_preamble():
@@ -57,6 +94,15 @@ def test_volt_cutoff_keeps_only_original_lease_then_consumes_next_source(mode, s
patch.object(sd, 'update_process_health', side_effect=finish_preamble):
held, _, _ = step(5, timeout=True, state_stamp=BASE, offset_ns=-100_000,
intent_message=intent(BASE, 3), observed_state=cs, native_request_long=full)
from opendbc.car.gm.steering_authority import monitored_profile
if monitored_profile(cp):
# This monitored profile also needs its original steering-health receipt.
# At this point that source is older than30ms. Its missing successor cannot
# inherit health from an unexpired axis envelope or a fresh Panda grant.
assert not sd.aol_axis_publication_deferred
assert not held.lateralActive and not held.longitudinalActive
assert sd.steering_authority_unavailable and sd.aol_authority_lost
return
assert sd.aol_axis_publication_deferred
assert held.to_dict() == old.to_dict() and held.validUntilMonoTime == deadline
assert not sd.aol_authority_lost
@@ -90,6 +136,7 @@ def test_volt_deferral_cannot_hide_driver_edges_or_failed_proof(bad):
with OpenpilotPrefix(), ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
sd, _, step = case._authority_callers(False, cp=sdgm_params(alpha=True), genuine_events=True)
step = bind_steering_receipts(sd, step, cleanup)
sd.aol_settings = read_settings(sd.params)
old, _, command = step(2, state_stamp=BASE, offset_ns=4_530_833,
intent_message=intent(BASE, 3), native_request_long=False)
@@ -135,6 +182,7 @@ def test_ascm_separate_steering_companion_expiry_still_withdraws_outputs(cp):
with OpenpilotPrefix(), ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
sd, _, step = case._authority_callers(True, cp=cp.as_reader().as_builder(), genuine_events=True)
step = bind_steering_receipts(sd, step, cleanup)
sd.aol_settings = read_settings(sd.params)
from collections import deque
queue = deque()
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "3431c43b06385ea8e9a91a7820b952792f1a1c27069c5ac04fc7f17227490b4c", "schema_version": 1}
{"protected_inventory_sha256": "8b4e37f4b6b70b41edab93d19b3690a747cd7eeeb7db5c681f24252e26f13818", "schema_version": 1}