mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
3778552160
Interpolate the measured Ioniq 6 steering delay instead of rounding delayed model requests to whole frames. Bound unsaturated friction growth without exceeding the current requested component or retaining its old direction; preserve full breakaway response, saved tunes and native limits. Cover actual Controls, replay state and controller timing.
120 lines
5.6 KiB
Python
120 lines
5.6 KiB
Python
"""Current controller latency interpolation and bounded friction growth."""
|
|
import math
|
|
import random
|
|
from types import SimpleNamespace
|
|
from unittest import mock
|
|
import pytest
|
|
from opendbc.car import structs
|
|
from opendbc.car.car_helpers import interfaces
|
|
from opendbc.car.hyundai.values import CAR
|
|
from openpilot.starpilot.flm.controller_replay import SelectedControllerFactory, capture_snapshot, restore_snapshot
|
|
from openpilot.starpilot.flm.tests.test_registered_controls_global import controls_for, frame
|
|
|
|
|
|
def actual(profile='2023'):
|
|
cp = interfaces[CAR.HYUNDAI_IONIQ_6].get_non_essential_params(CAR.HYUNDAI_IONIQ_6)
|
|
cp.lateralTuning.torque.latAccelFactor, cp.lateralTuning.torque.friction = 3., .09
|
|
if profile == '2025':
|
|
fw = cp.init('carFw', 2)
|
|
fw[0].fwVersion, fw[1].fwVersion = b'IONIQ6-230915', b'IONIQ6-240206'
|
|
controller, vm = SelectedControllerFactory(cp, 'starpilot', turn_assist=True).create()
|
|
cs = structs.CarState.new_message(vEgo=30., gearShifter='drive')
|
|
return cp, controller, vm, cs, SimpleNamespace(angleOffsetDeg=0., roll=0.)
|
|
|
|
|
|
@pytest.mark.parametrize('delay', [.01, .1, .1009, .299185186624527, .300019234418869, 1., 2., -.1])
|
|
def test_actual_controller_uses_continuous_original_delay_without_new_state(delay):
|
|
cp, controller, vm, cs, params = actual()
|
|
policy = controller.starpilot_extension.policy
|
|
values = [i * .000001 for i in range(policy.request_buffer_len)]
|
|
policy.curvature_request_buffer.extend(values)
|
|
tune = controller.torque_params.to_dict()
|
|
bounded = max(1., min(delay / controller.dt, float(len(values))))
|
|
# The stored requests lie on an affine timeline: evaluate that timeline at
|
|
# the requested fractional age independently of the two-sample implementation.
|
|
expected = (len(values) - bounded) * .000001 * cs.vEgo ** 2
|
|
with mock.patch.object(policy.jerk_filter, 'update', return_value=0.):
|
|
_, _, state = controller.update(True, cs, vm, params, False, .0001, False, delay)
|
|
assert state.desiredLateralAccel == pytest.approx(expected, abs=1e-8)
|
|
assert controller.torque_params.to_dict() == tune
|
|
assert cp.lateralTuning.torque.latAccelFactor == 3.
|
|
|
|
|
|
def test_actual_controller_delay_boundary_is_continuous_with_stepped_model_history():
|
|
_, controller, vm, cs, params = actual()
|
|
policy = controller.starpilot_extension.policy
|
|
policy.curvature_request_buffer.extend([0.] * 100)
|
|
policy.curvature_request_buffer[-29] = .0002
|
|
snap = capture_snapshot(controller, vm)
|
|
results = []
|
|
for delay in (.299999999, .300000001):
|
|
restore_snapshot(controller, vm, snap)
|
|
with mock.patch.object(policy.jerk_filter, 'update', return_value=0.):
|
|
output, _, state = controller.update(True, cs, vm, params, False, 0., False, delay)
|
|
results.append((output, state.desiredLateralAccel))
|
|
assert abs(results[0][0] - results[1][0]) < 1e-6
|
|
assert abs(results[0][1] - results[1][1]) < 1e-6
|
|
|
|
|
|
@pytest.mark.parametrize('profile', ['2023', '2025'])
|
|
def test_friction_never_exceeds_current_request_or_retains_opposite_sign_and_reset_replays(profile):
|
|
_, controller, vm, cs, params = actual(profile)
|
|
policy = controller.starpilot_extension.policy
|
|
tune = controller.torque_params.to_dict()
|
|
rng = random.Random(814)
|
|
for request in [.2, .3, .04, 0., -.2, -.3] + [rng.uniform(-.32, .32) for _ in range(300)]:
|
|
value = policy.center_friction(request, cs, saturated=False)
|
|
assert math.isfinite(value) and abs(value) <= abs(request)
|
|
assert value * request >= 0.
|
|
if request == 0.:
|
|
assert value == 0.
|
|
assert policy.center_friction(.7, cs, saturated=True) == .7
|
|
assert policy.prev_center_friction is None
|
|
empty = capture_snapshot(controller, vm)
|
|
assert policy.center_friction(0., cs, saturated=False) == 0.
|
|
assert policy.center_friction(.2, cs, saturated=False) == pytest.approx(.025)
|
|
saved = capture_snapshot(controller, vm)
|
|
assert len(saved.values) == len(empty.values)
|
|
value = policy.prev_center_friction
|
|
controller.update(False, cs, vm, params, False, 0., False, .1)
|
|
assert policy.prev_center_friction is None
|
|
restore_snapshot(controller, vm, saved)
|
|
assert policy.prev_center_friction == value
|
|
restore_snapshot(controller, vm, empty)
|
|
assert policy.prev_center_friction is None
|
|
assert controller.torque_params.to_dict() == tune
|
|
|
|
|
|
@pytest.mark.parametrize('gate', ['speed', 'override', 'manual', 'saturated'])
|
|
def test_current_raw_friction_preserved_when_growth_guard_not_eligible(gate):
|
|
_, controller, _, cs, _ = actual()
|
|
policy = controller.starpilot_extension.policy
|
|
policy.prev_center_friction = -.3
|
|
if gate == 'speed':
|
|
cs.vEgo = 12.
|
|
if gate == 'override':
|
|
cs.steeringPressed = True
|
|
if gate == 'manual':
|
|
policy.surface = object()
|
|
assert policy.center_friction(.25, cs, saturated=gate == 'saturated') == .25
|
|
assert policy.prev_center_friction is None
|
|
|
|
|
|
@pytest.mark.parametrize('profile', ['2023', '2025'])
|
|
def test_actual_controls_factory_path_retains_cp_tunes_and_native_output_bounds(profile):
|
|
cp, _, _, _, _ = actual(profile)
|
|
with controls_for(cp, 'starpilot', True) as controls:
|
|
original = controls.CP.as_builder().to_bytes()
|
|
tune = controls.LaC.torque_params.to_dict()
|
|
for tick in range(200):
|
|
command, _ = frame(controls, 700_000_000_000 + tick * 10_000_000, tick,
|
|
active=tick >= 20, enabled=tick >= 20)
|
|
assert math.isfinite(command.actuators.torque)
|
|
assert abs(command.actuators.torque) <= controls.LaC.steer_max
|
|
if tick < 20:
|
|
assert not command.latActive and command.actuators.torque == 0.
|
|
if tick >= 80:
|
|
assert command.latActive
|
|
assert controls.LaC.torque_params.to_dict() == tune
|
|
assert controls.CP.as_builder().to_bytes() == original
|