mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-29 09:33:48 +08:00
Remove unnecessary 3-second wait from automatic SLC adoption
This commit is contained in:
@@ -55,7 +55,7 @@ DESCRIPTIONS = {
|
||||
'curve_reset': 'Deletes saved cornering learning so Curve Speed Controller can learn your preferences again.',
|
||||
'SpeedLimitController': 'Adjusts the set speed to accepted speed limits using your source priority and offsets.',
|
||||
'ShowSpeedLimits': 'Shows recognized speed-limit signs on the driving screen.',
|
||||
'SLCConfirmation': 'Auto adopts after a 3-second flash. With Automatic adoption off, + accepts and − declines within 10 seconds.',
|
||||
'SLCConfirmation': 'Auto adopts new limits immediately. With Automatic adoption off, + accepts and − declines within 10 seconds.',
|
||||
'SLCPriority1': 'Chooses the first source checked for a fresh speed limit.',
|
||||
'SLCPriority2': 'Chooses the source used when the primary source has no fresh speed limit.',
|
||||
'SLCFallback': 'Keeps the previously accepted speed limit when sources are unavailable, until another limit is accepted.',
|
||||
|
||||
@@ -453,7 +453,7 @@ class SettingsGatewayTest(unittest.TestCase):
|
||||
self.assertNotIn("require your confirmation", primary["reason"])
|
||||
confirmation = next(row for row in rows if row["label"] == "Automatic adoption")
|
||||
self.assertEqual(confirmation["value"], "On")
|
||||
self.assertIn("3-second flash", confirmation["reason"])
|
||||
self.assertIn("immediately", confirmation["reason"])
|
||||
self.assertIn("accepts", confirmation["reason"])
|
||||
|
||||
with mock.patch.dict(os.environ, {"SLC_REPLAY_RUNTIME": "0", "SLC_VISION_DEVELOPMENT": "0"}):
|
||||
|
||||
@@ -9,8 +9,8 @@ confirmation presses, duplicated effects and foreign or obsolete contexts.
|
||||
|
||||
Override is presentation state; it never adds another planner target. Card's set
|
||||
speed remains the sole cruise target. The widget crosses out its latest source
|
||||
limit while override is active. A new limit still follows the three-second automatic
|
||||
or ten-second manual adoption policy. Acceptance clears the old override. Returning
|
||||
limit while override is active. A new limit is adopted immediately in automatic mode
|
||||
or follows the ten-second manual confirmation policy. Acceptance clears the old override. Returning
|
||||
the selected speed to the adopted adjusted limit also clears it.
|
||||
|
||||
Accelerator use neither creates an override nor cancels a preexisting set-speed
|
||||
|
||||
@@ -15,8 +15,8 @@ numeric limit does not start another decision. A decline remains effective acros
|
||||
a source gap or same-value provider change; a later different limit starts a new
|
||||
window, including when an earlier numeric limit returns.
|
||||
|
||||
With Automatic adoption on, the widget slowly flashes for three seconds before
|
||||
adoption. With it off, the stationary widget pulses purple twice slowly (700 ms
|
||||
With Automatic adoption on, qualified new limits are adopted immediately.
|
||||
With it off, the stationary widget pulses purple twice slowly (700 ms
|
||||
each), rests for one second, pulses twice quickly (300 ms each), and rests for one
|
||||
second before repeating. Each pair has a 200 ms gap. The arrow remains relative
|
||||
to vehicle speed; Vienna numerals stay black while their ring pulses.
|
||||
@@ -92,8 +92,8 @@ decision or manual override. Applied commands are not repeated.
|
||||
Declining or timing out a returned limit never revives an earlier adoption command;
|
||||
the historical adoption remains recorded independently of the new rejection.
|
||||
|
||||
Transport changes append to reserved custom messages. `SlcState` appends automatic
|
||||
countdown metadata at ordinals 51–52 and latest-received display metadata at 53–55.
|
||||
Transport changes append to reserved custom messages. `SlcState` retains automatic
|
||||
pending metadata at ordinals 51–52 and latest-received display metadata at 53–55.
|
||||
`SlcCruiseCommand` appends its source at 14 and
|
||||
a bidirectional adoption kind; historical fields and enum ordinals remain reserved.
|
||||
Official vehicle schemas are unchanged. Historical SLC ceiling fields stay empty.
|
||||
|
||||
@@ -7,7 +7,6 @@ from dataclasses import dataclass, replace
|
||||
from enum import Enum
|
||||
|
||||
CONFIRMATION_NS = 10_000_000_000
|
||||
AUTOMATIC_ADOPTION_NS = 3_000_000_000
|
||||
MAX_REJECTIONS = 256
|
||||
|
||||
|
||||
@@ -443,7 +442,7 @@ def step(state: State, observation: Observation, authority: Authority, policy: P
|
||||
state = replace(state, pending=pending, timer_running=True)
|
||||
|
||||
if pending.automatic:
|
||||
if pending.active_elapsed_ns >= AUTOMATIC_ADOPTION_NS and (authority.can_control or authority.fully_disengaged):
|
||||
if authority.can_control or authority.fully_disengaged:
|
||||
state, history = _accept(state, candidate, now_ns)
|
||||
return finish("accepted" if authority.can_control else "disengaged_accept",
|
||||
target=candidate.speed_mps if authority.can_control else None, history=history)
|
||||
|
||||
@@ -5,7 +5,7 @@ from dataclasses import FrozenInstanceError, replace
|
||||
from typing import Any, cast
|
||||
|
||||
from openpilot.starpilot.speed_limits.acceptance import (
|
||||
AUTOMATIC_ADOPTION_NS, CONFIRMATION_NS, MAX_REJECTIONS, AcceptedLimit, ActionKind, AdoptRequest, Authority, Candidate, DriverAction,
|
||||
CONFIRMATION_NS, MAX_REJECTIONS, AcceptedLimit, ActionKind, AdoptRequest, Authority, Candidate, DriverAction,
|
||||
IdentityKind, LongitudinalOwner, Mode, Observation, ObservationIdentity, ObservationKind, Policy, State, new_session, step,
|
||||
)
|
||||
|
||||
@@ -39,10 +39,10 @@ class TestAcceptance(unittest.TestCase):
|
||||
policy = Policy(confirmation, confirmation, fallback)
|
||||
candidate = replace(LOW, source=source)
|
||||
first = step(new_session('drive'), valid(candidate), DISENGAGED, policy, now_ns=0)
|
||||
self.assertIsNone(first.history_write)
|
||||
self.assertIsNotNone(first.state.pending)
|
||||
self.assertEqual(first.state.pending.automatic, not confirmation)
|
||||
ready = step(first.state, valid(candidate), ACTIVE, policy, now_ns=AUTOMATIC_ADOPTION_NS)
|
||||
self.assertEqual(first.state.pending is not None, confirmation)
|
||||
self.assertEqual(first.history_write is None, confirmation)
|
||||
self.assertIsNone(first.control_target_mps)
|
||||
ready = step(first.state, valid(candidate), ACTIVE, policy, now_ns=NS)
|
||||
accepted = self.act(ready.state, ActionKind.ACCEPT, now_ns=4 * NS, policy=policy) if confirmation else ready
|
||||
self.assertEqual(accepted.control_target_mps, candidate.speed_mps)
|
||||
lost = step(accepted.state, ABSENT, ACTIVE, policy, now_ns=5 * NS)
|
||||
@@ -203,18 +203,23 @@ class TestAcceptance(unittest.TestCase):
|
||||
self.assertEqual(result.basis, "driver_accept" if kind is ActionKind.ACCEPT else "driver_reject")
|
||||
self.assertTrue(result.action_receipt.consumed)
|
||||
|
||||
def test_first_and_changed_limits_use_countdown_or_confirmation(self):
|
||||
def test_first_and_changed_limits_adopt_immediately_or_require_confirmation(self):
|
||||
for confirmation in (False, True):
|
||||
policy = Policy(confirmation, confirmation)
|
||||
for candidate in (LOW, replace(HIGH, speed_mps=31.)):
|
||||
first = step(saved(), valid(candidate), ACTIVE, policy, now_ns=0)
|
||||
self.assertEqual(first.state.pending.automatic, not confirmation)
|
||||
self.assertEqual(first.control_target_mps, HIGH.speed_mps)
|
||||
ready = step(first.state, valid(candidate), ACTIVE, policy, now_ns=AUTOMATIC_ADOPTION_NS - 1)
|
||||
self.assertIsNotNone(ready.state.pending)
|
||||
done = step(ready.state, valid(candidate), ACTIVE, policy, now_ns=AUTOMATIC_ADOPTION_NS)
|
||||
self.assertEqual(done.state.pending is not None, confirmation)
|
||||
self.assertEqual(done.control_target_mps, HIGH.speed_mps if confirmation else candidate.speed_mps)
|
||||
for prior, candidate in itertools.product((new_session('drive'), saved()), (LOW, replace(HIGH, speed_mps=31.))):
|
||||
with self.subTest(confirmation=confirmation, prior=prior.accepted, candidate=candidate):
|
||||
first = step(prior, valid(candidate), ACTIVE, policy, now_ns=0)
|
||||
self.assertEqual(first.state.pending is not None, confirmation)
|
||||
if confirmation:
|
||||
self.assertFalse(first.state.pending.automatic)
|
||||
self.assertEqual(first.state.accepted, prior.accepted)
|
||||
self.assertIsNone(first.history_write)
|
||||
else:
|
||||
self.assertEqual(first.state.accepted.candidate, candidate)
|
||||
self.assertEqual(first.history_write, first.state.accepted)
|
||||
self.assertEqual(first.control_target_mps, candidate.speed_mps)
|
||||
repeated = step(first.state, valid(candidate), ACTIVE, policy, now_ns=1)
|
||||
self.assertIsNone(repeated.history_write)
|
||||
|
||||
def test_unchanged_speed_keeps_approval_without_silently_accepting_new_identity(self):
|
||||
for toward in (0.0, math.inf):
|
||||
@@ -231,14 +236,21 @@ class TestAcceptance(unittest.TestCase):
|
||||
automatic = step(saved(), valid(same_speed), ACTIVE, Policy(False, False), now_ns=0)
|
||||
self.assertIsNone(automatic.history_write)
|
||||
|
||||
def test_disengaged_automatic_adoption_waits_three_seconds(self):
|
||||
pending = step(saved(), valid(LOW), DISENGAGED, Policy(False, False), now_ns=0)
|
||||
self.assertEqual(pending.state.accepted.candidate, HIGH)
|
||||
ready = step(pending.state, valid(LOW), DISENGAGED, Policy(False, False), now_ns=AUTOMATIC_ADOPTION_NS)
|
||||
def test_disengaged_automatic_adoption_is_immediate_without_commanding_speed(self):
|
||||
ready = step(saved(), valid(LOW), DISENGAGED, Policy(False, False), now_ns=0)
|
||||
self.assertEqual(ready.state.accepted.candidate, LOW)
|
||||
self.assertIsNotNone(ready.history_write)
|
||||
self.assertIsNone(ready.control_target_mps)
|
||||
|
||||
def test_automatic_adoption_still_waits_for_authority_then_adopts_without_delay(self):
|
||||
policy = Policy(False, False)
|
||||
pending = self.pending(authority=PAUSED, policy=policy)
|
||||
self.assertEqual(pending.state.accepted.candidate, HIGH)
|
||||
self.assertIsNone(pending.history_write)
|
||||
ready = step(pending.state, valid(LOW), ACTIVE, policy, now_ns=1)
|
||||
self.assertIsNone(ready.state.pending)
|
||||
self.assertEqual(ready.control_target_mps, LOW.speed_mps)
|
||||
|
||||
def test_zero_active_axes_does_not_imply_fully_disengaged(self):
|
||||
result = self.pending(authority=PAUSED)
|
||||
self.assertEqual(result.state.accepted.candidate, HIGH)
|
||||
|
||||
@@ -209,10 +209,8 @@ class CompositionTests(unittest.TestCase):
|
||||
self.assertEqual(rejected.state.acceptance.last_timestamp_ns, 0)
|
||||
|
||||
def test_new_limit_never_creates_a_hidden_planner_target(self):
|
||||
pending = self.run_frame(frame(0, speed=20.))
|
||||
self.assertEqual(pending.acceptance.basis, 'automatic_pending')
|
||||
self.assertIsNone(pending.ceiling)
|
||||
accepted = self.run_frame(frame(acc.AUTOMATIC_ADOPTION_NS, speed=20.))
|
||||
accepted = self.run_frame(frame(0, speed=20.))
|
||||
self.assertEqual(accepted.acceptance.basis, 'accepted')
|
||||
self.assertEqual(accepted.domain.context.effective_cluster_mps, 22.)
|
||||
self.assertIsNone(accepted.ceiling)
|
||||
self.assertIsNone(accepted.state.lead_prior)
|
||||
|
||||
@@ -61,9 +61,9 @@ class OverrideTests(unittest.TestCase):
|
||||
self.decision(3)
|
||||
self.assertEqual(self.reduce(3, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 25.0)).contribution_mps, 25.0)
|
||||
self.decision(4, candidate=LOW)
|
||||
self.assertEqual(self.reduce(4, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 25.0)).contribution_mps, 25.)
|
||||
self.decision(3_000_000_004, candidate=LOW)
|
||||
self.assertIsNone(self.reduce(3_000_000_004, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 25.0)).contribution_mps)
|
||||
self.assertIsNone(self.reduce(4, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 25.0)).contribution_mps)
|
||||
self.decision(5, candidate=LOW)
|
||||
self.assertIsNone(self.reduce(5, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 25.0)).contribution_mps)
|
||||
|
||||
def test_decrease_only_reduces_an_existing_override(self):
|
||||
for existing, previous, chosen, expected in (
|
||||
@@ -92,9 +92,7 @@ class OverrideTests(unittest.TestCase):
|
||||
self.assertEqual(self.reduce(3, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 25.)).contribution_mps, 25.)
|
||||
mid = replace(SAME, speed_mps=22.)
|
||||
self.decision(4, candidate=mid)
|
||||
self.assertEqual(self.reduce(4, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 25.)).contribution_mps, 25.)
|
||||
self.decision(3_000_000_004, candidate=mid)
|
||||
self.assertIsNone(self.reduce(3_000_000_004, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 25.)).contribution_mps)
|
||||
self.assertIsNone(self.reduce(4, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 25.)).contribution_mps)
|
||||
|
||||
def test_pedal_tracks_current_ego_and_release_restores_persistence(self):
|
||||
self.arm()
|
||||
@@ -315,18 +313,17 @@ class OverrideTests(unittest.TestCase):
|
||||
self.arm()
|
||||
old_effect = self.classified(effect=2, now=3, chosen=26.0)
|
||||
self.decision(3, candidate=LOW)
|
||||
self.decision(3_000_000_003, candidate=LOW)
|
||||
lower = self.reduce(3_000_000_003, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 26.0), change=old_effect)
|
||||
lower = self.reduce(3, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 26.0), change=old_effect)
|
||||
self.assertIsNone(lower.contribution_mps)
|
||||
self.assertFalse(lower.event_receipt.consumed)
|
||||
self.assertNotEqual(lower.context, self.first.context)
|
||||
|
||||
ledger = arb.step(arb.new_ledger(SESSION), arb.BeginAction(SESSION, 2, arb.Origin.DRIVER_CRUISE,
|
||||
lower.context.context_id), now_ns=3_000_000_004).state
|
||||
ledger = arb.step(ledger, arb.ResolveAction(SESSION, 2, arb.Disposition.DRIVER_INTENT), now_ns=3_000_000_004).state
|
||||
fresh = arb.step(ledger, arb.CruiseChange(SESSION, 3, 2, 26.0, 27.0), now_ns=3_000_000_005).change
|
||||
self.decision(3_000_000_005, candidate=LOW)
|
||||
rearmed = self.reduce(3_000_000_005, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 27.0), change=fresh)
|
||||
lower.context.context_id), now_ns=4).state
|
||||
ledger = arb.step(ledger, arb.ResolveAction(SESSION, 2, arb.Disposition.DRIVER_INTENT), now_ns=4).state
|
||||
fresh = arb.step(ledger, arb.CruiseChange(SESSION, 3, 2, 26.0, 27.0), now_ns=5).change
|
||||
self.decision(5, candidate=LOW)
|
||||
rearmed = self.reduce(5, selected=ov.SelectedEvidence(ov.EvidenceKind.VALID, 27.0), change=fresh)
|
||||
self.assertTrue(rearmed.event_receipt.consumed)
|
||||
self.assertEqual(rearmed.contribution_mps, 27.0)
|
||||
|
||||
|
||||
@@ -1077,7 +1077,9 @@ class RuntimeReplayTests(unittest.TestCase):
|
||||
self.assertIn('longitudinalPlan', services)
|
||||
state = next(message.slcState for service, message in publisher.sent if service == 'slcState')
|
||||
self.assertFalse(state.hasCeiling)
|
||||
self.assertTrue(state.automaticAdoptionPending)
|
||||
self.assertTrue(state.hasAccepted)
|
||||
self.assertFalse(state.hasPending)
|
||||
self.assertFalse(state.automaticAdoptionPending)
|
||||
self.assertEqual(state.observationKind, 'valid')
|
||||
self.assertEqual(state.frameMonoTime, 2_010_000_000)
|
||||
|
||||
@@ -1332,10 +1334,12 @@ class RuntimeReplayTests(unittest.TestCase):
|
||||
source.carStateLogMonoTime = 2_000_000_000
|
||||
matched = runtime.step(self.sm, self.cp, now_ns=2_000_000_000)
|
||||
first_identity = matched.result.selection.observation.candidate.observation_identity.value
|
||||
self.assertTrue(matched.message.slcState.hasPending)
|
||||
self.assertTrue(matched.message.slcState.hasAccepted)
|
||||
self.assertFalse(matched.message.slcState.hasPending)
|
||||
self.sm.logMonoTime['carState'] = 2_010_000_000 # independent sockets deliver the next card tick first
|
||||
lagged = runtime.step(self.sm, self.cp, now_ns=2_010_000_000)
|
||||
self.assertTrue(lagged.message.slcState.hasPending)
|
||||
self.assertTrue(lagged.message.slcState.hasAccepted)
|
||||
self.assertFalse(lagged.message.slcState.hasPending)
|
||||
source.producerSessionId = 'restarted-card'
|
||||
restarted = runtime.step(self.sm, self.cp, now_ns=2_010_000_000)
|
||||
second_identity = restarted.result.selection.observation.candidate.observation_identity.value
|
||||
|
||||
@@ -73,11 +73,11 @@ def test_failed_adoption_retries_with_current_evidence_and_stops_after_applicati
|
||||
|
||||
|
||||
@pytest.mark.parametrize('change', ['manual', 'unclassified', 'source', 'returned_source', 'disengaged'])
|
||||
def test_expired_adoption_does_not_retry_after_driver_or_authority_changes(drive, change):
|
||||
def test_expired_adoption_does_not_revive_superseded_commands(drive, change):
|
||||
sm, cp = drive
|
||||
runtime = Runtime(parse({'SpeedLimitController': True, 'SLCPriority1': 'Dashboard'}))
|
||||
tick(runtime, sm, cp, 0)
|
||||
tick(runtime, sm, cp, 3)
|
||||
old_command = tick(runtime, sm, cp, 3).command.slcCruiseCommand
|
||||
kwargs = {}
|
||||
if change in ('manual', 'unclassified'):
|
||||
sm['carState'].vCruise = sm['carState'].vCruiseCluster = 61.
|
||||
@@ -93,7 +93,13 @@ def test_expired_adoption_does_not_retry_after_driver_or_authority_changes(drive
|
||||
else:
|
||||
sm['selfdriveState'].enabled = sm['carControl'].enabled = sm['carControl'].longActive = False
|
||||
sm['controlsState'].longControlState = 'off'
|
||||
assert tick(runtime, sm, cp, 3.2, **kwargs).command is None
|
||||
result = tick(runtime, sm, cp, 3.2, **kwargs)
|
||||
if change in ('source', 'returned_source'):
|
||||
assert result.message.slcState.acceptedMonoTime == START + 3_200_000_000
|
||||
assert result.command.slcCruiseCommand.commandId > old_command.commandId
|
||||
assert result.command.slcCruiseCommand.targetMps == (20. if change == 'source' else 25.)
|
||||
else:
|
||||
assert result.command is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('failure', ['rejected', 'expired'])
|
||||
@@ -213,18 +219,17 @@ def test_source_loss_preserves_latest_received_limit_and_offset_without_actions(
|
||||
|
||||
@pytest.mark.parametrize('limit', [12., 25.])
|
||||
@pytest.mark.parametrize('pcm', [False, True])
|
||||
def test_automatic_adoption_updates_set_speed_up_or_down_after_three_seconds(drive, limit, pcm):
|
||||
def test_automatic_adoption_updates_set_speed_up_or_down_immediately(drive, limit, pcm):
|
||||
from openpilot.starpilot.ui.onroad_state import OnroadState, speed_limit_from_message
|
||||
from openpilot.starpilot.ui.speed_limit_pulse import SpeedLimitPulse
|
||||
sm, cp = drive
|
||||
cp.pcmCruise = pcm
|
||||
sm['slcDashboardObservation'].speedMps = limit
|
||||
runtime = Runtime(parse({'SpeedLimitController': True, 'SLCPriority1': 'Dashboard'}))
|
||||
first = tick(runtime, sm, cp, 0)
|
||||
assert first.message.slcState.automaticAdoptionPending
|
||||
assert first.message.slcState.pendingSinceMonoTime == START
|
||||
assert not first.message.slcState.hasAccepted
|
||||
assert first.command is None and first.result.ceiling is None
|
||||
assert tick(runtime, sm, cp, 2.999).command is None
|
||||
adopted = tick(runtime, sm, cp, 3)
|
||||
adopted = tick(runtime, sm, cp, 0)
|
||||
assert adopted.message.slcState.hasAccepted
|
||||
assert not adopted.message.slcState.automaticAdoptionPending
|
||||
assert adopted.message.slcState.pendingSinceMonoTime == 0
|
||||
assert adopted.command is not None
|
||||
assert adopted.command.slcCruiseCommand.targetMps == pytest.approx(limit)
|
||||
assert adopted.result.ceiling is None and not adopted.message.slcState.hasCeiling
|
||||
@@ -233,16 +238,18 @@ def test_automatic_adoption_updates_set_speed_up_or_down_after_three_seconds(dri
|
||||
evidence = sm['slcDashboardObservation']
|
||||
observation = SimpleNamespace(status=Status.VALID, speed_mps=limit, episode=evidence.episode,
|
||||
observed_ns=evidence.observedMonoTime, valid_until_ns=evidence.validUntilMonoTime)
|
||||
current = physical_actions.state_current(ReplaySM({'slcState': state}, START + 3_000_000_000), observation,
|
||||
'card-producer', START + 3_000_000_000)
|
||||
current = physical_actions.state_current(ReplaySM({'slcState': state}, START), observation, 'card-producer', START)
|
||||
assert physical_actions.command_applicable(adopted.command.slcCruiseCommand, current, observation, 'card-producer',
|
||||
START + 3_000_000_000, 60 / 3.6, long_active=True, button_event=False)
|
||||
START, 60 / 3.6, long_active=True, button_event=False)
|
||||
helper = VCruiseHelper(cp)
|
||||
helper.v_cruise_kph = 60.
|
||||
helper.apply_slc_target(limit, True)
|
||||
sm['carState'].vCruise = sm['carState'].vCruiseCluster = helper.v_cruise_kph
|
||||
assert helper.v_cruise_kph == round(limit * 3.6)
|
||||
assert tick(runtime, sm, cp, 3.05).command is None
|
||||
pulse = SpeedLimitPulse()
|
||||
pulse.update(OnroadState(True, False, 20., helper.v_cruise_kph, speed_limit_from_message(state)), START / 1e9)
|
||||
assert not pulse.display_pulsing and pulse.start_time == -float('inf')
|
||||
assert tick(runtime, sm, cp, 0.05).command is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('response', ['accept', 'reject', 'timeout'])
|
||||
@@ -274,9 +281,8 @@ def test_new_limits_still_adopt_during_manual_override_in_either_direction(drive
|
||||
selected / 3.6, 'accel' if selected > 90 else 'decel', 'card-producer', START + 3_100_000_000))
|
||||
assert changed.message.slcState.driverOverrideActive
|
||||
sm['slcDashboardObservation'].speedMps = 18.
|
||||
new = tick(runtime, sm, cp, 3.2)
|
||||
assert new.message.slcState.automaticAdoptionPending and new.message.slcState.driverOverrideActive
|
||||
adopted = tick(runtime, sm, cp, 6.2)
|
||||
adopted = tick(runtime, sm, cp, 3.2)
|
||||
assert not adopted.message.slcState.hasPending
|
||||
assert adopted.command.slcCruiseCommand.targetMps == pytest.approx(18.)
|
||||
assert not adopted.message.slcState.driverOverrideActive
|
||||
|
||||
@@ -372,17 +378,17 @@ def test_ford_parser_buttons_adjust_set_speed_or_confirm_without_a_second_step(p
|
||||
assert (helper.slc_cruise_change is None) == pending
|
||||
|
||||
|
||||
def test_automatic_countdown_never_consumes_plus_or_minus_and_late_accept_is_declined():
|
||||
def test_automatic_adoption_does_not_consume_confirmation_actions_and_late_manual_accept_is_declined():
|
||||
candidate = acc.Candidate('dashboard', acc.ObservationIdentity(acc.IdentityKind.SESSION_VALUE, session_id='drive'), 20.)
|
||||
observation = acc.Observation(acc.ObservationKind.VALID, candidate)
|
||||
authority = acc.Authority(acc.Mode.LONGITUDINAL_ONLY, acc.LongitudinalOwner.SYSTEM, False, True, False, False)
|
||||
for automatic in (False, True):
|
||||
policy = acc.Policy(not automatic, not automatic)
|
||||
pending = acc.step(acc.new_session('drive'), observation, authority, policy, now_ns=0)
|
||||
action = acc.DriverAction('drive', 1, pending.state.pending.decision_id, acc.ActionKind.ACCEPT)
|
||||
output = acc.step(pending.state, observation, authority, policy, now_ns=1 if automatic else acc.CONFIRMATION_NS + 1, action=action)
|
||||
first = acc.step(acc.new_session('drive'), observation, authority, policy, now_ns=0)
|
||||
action = acc.DriverAction('drive', 1, first.state.next_decision_id - 1, acc.ActionKind.ACCEPT)
|
||||
output = acc.step(first.state, observation, authority, policy, now_ns=1 if automatic else acc.CONFIRMATION_NS + 1, action=action)
|
||||
assert not output.action_receipt.consumed
|
||||
assert output.state.accepted is None
|
||||
assert (output.state.accepted is not None) == automatic
|
||||
|
||||
|
||||
@pytest.mark.parametrize('source_name', ['map', 'vision'])
|
||||
|
||||
@@ -240,8 +240,7 @@ class SpeedDomainTests(unittest.TestCase):
|
||||
armed, _ = self.frame(1, selected_raw=24.0, change=effect)
|
||||
self.assertEqual(armed.state.persistent_selected_mps, 24.0)
|
||||
self.decision(2, speed=23.0)
|
||||
self.decision(3_000_000_002, speed=23.0)
|
||||
caught, coordinate = self.frame(3_000_000_002, selected_raw=24.0)
|
||||
caught, coordinate = self.frame(2, selected_raw=24.0)
|
||||
self.assertIsNone(caught.state.persistent_selected_mps)
|
||||
self.assertNotEqual(caught.context.context_id, first.context.context_id)
|
||||
self.assertEqual(coordinate.cap_mps, 25.0)
|
||||
|
||||
@@ -14,7 +14,6 @@ from openpilot.selfdrive.car.cruise import VCruiseHelper, SlcPendingConfirmation
|
||||
from openpilot.starpilot.car.toyota.tests.test_pedal_caller_native import firmware
|
||||
from openpilot.starpilot.speed_limits.runtime import Action, CruiseEvent, Runtime
|
||||
from openpilot.starpilot.speed_limits.runtime_settings import parse
|
||||
from openpilot.starpilot.speed_limits.acceptance import AUTOMATIC_ADOPTION_NS
|
||||
from openpilot.starpilot.speed_limits.tests import test_runtime_replay as replay
|
||||
from openpilot.starpilot.speed_limits.toyota import eligible, projection
|
||||
|
||||
@@ -308,7 +307,7 @@ def test_runtime_waits_for_bound_fresh_card_projection(helper, manual):
|
||||
return runtime.step(sm, helper.CP, now_ns=stamp, **kwargs)
|
||||
|
||||
pending = tick(2_000_000_000).message.slcState
|
||||
accepted_ns = 2_000_000_000 + (10_000_000 if manual else AUTOMATIC_ADOPTION_NS)
|
||||
accepted_ns = 2_000_000_000 + (10_000_000 if manual else 0)
|
||||
request = Action('drive', 1, pending.decisionId, pending.presentationId, 'accept') if manual else None
|
||||
accepted = tick(accepted_ns, request=request)
|
||||
assert accepted.message.slcState.hasAccepted and accepted.command is None
|
||||
|
||||
@@ -1073,7 +1073,7 @@ class FeatureSettingsOwner:
|
||||
rows.append(self._bool_row("ShowSpeedLimits", "Show speed limit signs", self.authority("preferences"),
|
||||
reason="Display signs independently of Speed Limit Controller; does not change cruise speed"))
|
||||
confirmation = self._bool_row("SLCConfirmation", "Automatic adoption", allowed,
|
||||
reason="On flashes new limits for 3 seconds, then sets cruise speed; Off asks for confirmation within 10 seconds")
|
||||
reason="On adopts new limits immediately; Off asks for confirmation within 10 seconds")
|
||||
rows.append(replace(confirmation, value={"On": "Off", "Off": "On"}.get(confirmation.value, confirmation.value), default_value="On"))
|
||||
source_capability = self._source_capability()
|
||||
vision_dev, maps_available = source_capability
|
||||
|
||||
@@ -39,21 +39,24 @@ def main() -> None:
|
||||
view = ShellView(fonts, args.asset_directory)
|
||||
try:
|
||||
scenes = {}
|
||||
for name, pending in (("pending", True), ("automatic", True), ("adoptable", False), ("override", False)):
|
||||
for name, pending in (("pending", True), ("automatic", False), ("adoptable", False), ("override", False)):
|
||||
observation = SpeedLimitObservation(kind=ObservationKind.VALID, source="dashboard", speed_limit_mps=22.0,
|
||||
accepted_speed_limit_mps=18.0, accepted_source="dashboard",
|
||||
accepted_adjusted_limit_mps=20.0, effective_cluster_target_mps=20.0,
|
||||
accepted_speed_limit_mps=22.0 if name == 'automatic' else 18.0, accepted_source="dashboard",
|
||||
accepted_adjusted_limit_mps=24.0 if name == 'automatic' else 20.0,
|
||||
effective_cluster_target_mps=24.0 if name == 'automatic' else 20.0,
|
||||
pending_adjusted_limit_mps=24.0 if pending else None,
|
||||
presentation_adjusted_limit_mps=24.0, limiting_max_set=True,
|
||||
pending_source="dashboard" if pending else "none",
|
||||
pending_speed_limit_mps=22.0 if pending else None,
|
||||
session_id="preview-drive", decision_id=7 if pending else 0,
|
||||
presentation_id=9, action_enabled=True,
|
||||
automatic_adoption_pending=name == "automatic", pending_since_ns=10_000_000_000,
|
||||
pending_since_ns=10_000_000_000 if pending else 0,
|
||||
driver_override_active=name == "override")
|
||||
onroad = replace(reference_onroad("onroad_engaged_no_camera"), speed_limit=observation,
|
||||
longitudinal_active=True, slc_system_long_available=True, slc_prompt_open=name == "pending",
|
||||
observed_ns=10_500_000_000)
|
||||
if name == 'automatic':
|
||||
onroad = replace(onroad, cruise_kph=24. * 3.6)
|
||||
snapshot = ShellSnapshot(ShellMode.ONROAD, reference_state(), reference_settings_state(), onroad,
|
||||
DeviceState(), SoftwareState(), TogglesState(), Destination.STAR)
|
||||
for _ in range(18):
|
||||
|
||||
@@ -156,11 +156,6 @@ class RetentionDrive:
|
||||
self.planner = native.LongitudinalPlanner(self.cp, init_v=45 * MPH)
|
||||
|
||||
def step(self, milliseconds, *, received=True, alive=True, status='matchedLimit', limit=45, request=None, **changes):
|
||||
if (self.runtime.state.last_now_ns is None and not self.runtime.settings.acceptance.confirm_higher and
|
||||
not getattr(self, '_priming', False)):
|
||||
self._priming = True
|
||||
self.step(milliseconds - 3000, received=received, alive=alive, status=status, limit=limit, **changes)
|
||||
self._priming = False
|
||||
now, boot = NOW + milliseconds * 1_000_000, BOOT + milliseconds * 1_000_000
|
||||
previous_stamp = self.sm.logMonoTime.get('mapdOut', BOOT)
|
||||
self.sm.advance(now)
|
||||
@@ -362,9 +357,9 @@ def test_retention_respects_lower_max_secondary_source_and_reset(tmp_path):
|
||||
secondary = RetentionDrive(tmp_path / 'secondary', dashboard=True)
|
||||
secondary.step(0)
|
||||
output, _, target = secondary.step(300, status='noCoverage')
|
||||
assert target == pytest.approx(published_cruise_mph(45), rel=0, abs=1e-10)
|
||||
assert output.message.slcState.automaticAdoptionPending
|
||||
assert output.message.slcState.acceptedSource == 'map'
|
||||
assert target == pytest.approx(published_cruise_mph(70), rel=0, abs=1e-10)
|
||||
assert not output.message.slcState.automaticAdoptionPending
|
||||
assert output.message.slcState.acceptedSource == 'dashboard'
|
||||
|
||||
|
||||
def test_compact_sign_keeps_accepted_value_without_pulsing_on_recovery(tmp_path):
|
||||
|
||||
Reference in New Issue
Block a user