forked from IQ.Lvbs/IQ.Pilot
IQ.Pilot Release Commit @ 0798119
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
|
||||
Original concept ("Increased Stop Distance") by SpysyWeeb (github.com/SpysyWeeb)
|
||||
"""
|
||||
from types import SimpleNamespace
|
||||
|
||||
from iqdbc.car.interfaces import ACCEL_MIN
|
||||
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||
from openpilot.iqpilot.selfdrive.controls.lib.custom_stop_distance import (
|
||||
CustomStopDistance,
|
||||
MIN_ADJUSTED_D_REL,
|
||||
)
|
||||
|
||||
|
||||
def _build(distance):
|
||||
c = CustomStopDistance.__new__(CustomStopDistance)
|
||||
c.frame = 0
|
||||
c.distance = float(distance)
|
||||
return c
|
||||
|
||||
|
||||
def _model_msg(stop_distance, end_velocity):
|
||||
x = [0.0] * (ModelConstants.IDX_N - 1) + [stop_distance]
|
||||
v = [0.0] * (ModelConstants.IDX_N - 1) + [end_velocity]
|
||||
return SimpleNamespace(position=SimpleNamespace(x=x), velocity=SimpleNamespace(x=v))
|
||||
|
||||
|
||||
def test_zero_distance_is_a_no_op():
|
||||
c = _build(0)
|
||||
lead = {'status': True, 'dRel': 10.0, 'vLead': 0.0}
|
||||
assert c.apply_lead(dict(lead)) == lead
|
||||
|
||||
|
||||
def test_positive_distance_reduces_reported_lead_distance():
|
||||
c = _build(2)
|
||||
lead = {'status': True, 'dRel': 10.0, 'vLead': 0.0}
|
||||
out = c.apply_lead(dict(lead))
|
||||
assert out['dRel'] == 8.0
|
||||
|
||||
|
||||
def test_negative_distance_increases_reported_lead_distance():
|
||||
c = _build(-2)
|
||||
lead = {'status': True, 'dRel': 10.0, 'vLead': 0.0}
|
||||
out = c.apply_lead(dict(lead))
|
||||
assert out['dRel'] == 12.0
|
||||
|
||||
|
||||
def test_positive_distance_never_reports_below_floor():
|
||||
c = _build(2)
|
||||
lead = {'status': True, 'dRel': 1.5, 'vLead': 0.0}
|
||||
out = c.apply_lead(dict(lead))
|
||||
assert out['dRel'] == MIN_ADJUSTED_D_REL
|
||||
|
||||
|
||||
def test_positive_distance_never_reports_further_than_reality():
|
||||
c = _build(2)
|
||||
lead = {'status': True, 'dRel': 0.5, 'vLead': 0.0}
|
||||
out = c.apply_lead(dict(lead))
|
||||
assert out['dRel'] == 0.5
|
||||
|
||||
|
||||
def test_offset_fades_out_as_lead_speeds_up():
|
||||
c = _build(2)
|
||||
lead = {'status': True, 'dRel': 10.0, 'vLead': 3.0}
|
||||
out = c.apply_lead(dict(lead))
|
||||
assert out['dRel'] == 10.0
|
||||
|
||||
|
||||
def test_no_lead_is_untouched():
|
||||
c = _build(2)
|
||||
lead = {'status': False, 'dRel': 10.0, 'vLead': 0.0}
|
||||
out = c.apply_lead(dict(lead))
|
||||
assert out['dRel'] == 10.0
|
||||
|
||||
|
||||
def test_e2e_negative_distance_is_a_no_op():
|
||||
c = _build(-2)
|
||||
a_target, should_stop = c.adjust_e2e_stop(-0.5, False, 0.2, _model_msg(3.0, 0.0))
|
||||
assert (a_target, should_stop) == (-0.5, False)
|
||||
|
||||
|
||||
def test_e2e_zero_distance_is_a_no_op():
|
||||
c = _build(0)
|
||||
a_target, should_stop = c.adjust_e2e_stop(-0.5, False, 0.2, _model_msg(3.0, 0.0))
|
||||
assert (a_target, should_stop) == (-0.5, False)
|
||||
|
||||
|
||||
def test_e2e_stop_sign_plans_are_untouched():
|
||||
c = _build(2)
|
||||
# model plan still moving at the end -> proceeding through (stop sign), not held
|
||||
a_target, should_stop = c.adjust_e2e_stop(-0.5, False, 0.2, _model_msg(3.0, 5.0))
|
||||
assert (a_target, should_stop) == (-0.5, False)
|
||||
|
||||
|
||||
def test_e2e_holds_short_of_model_stop_when_already_stopped():
|
||||
c = _build(2)
|
||||
a_target, should_stop = c.adjust_e2e_stop(0.0, False, 0.1, _model_msg(stop_distance=3.0, end_velocity=0.0))
|
||||
assert should_stop is True
|
||||
|
||||
|
||||
def test_e2e_does_not_hold_once_past_offset_and_buffer():
|
||||
c = _build(2)
|
||||
a_target, should_stop = c.adjust_e2e_stop(0.0, False, 0.1, _model_msg(stop_distance=10.0, end_velocity=0.0))
|
||||
assert should_stop is False
|
||||
|
||||
|
||||
def test_e2e_deepens_braking_already_in_progress():
|
||||
c = _build(2)
|
||||
a_target, should_stop = c.adjust_e2e_stop(-0.5, False, 5.0, _model_msg(stop_distance=10.0, end_velocity=0.0))
|
||||
assert a_target < -0.5
|
||||
assert a_target >= ACCEL_MIN
|
||||
|
||||
|
||||
def test_e2e_never_relaxes_braking():
|
||||
c = _build(2)
|
||||
a_target, should_stop = c.adjust_e2e_stop(0.0, False, 5.0, _model_msg(stop_distance=10.0, end_velocity=0.0))
|
||||
assert a_target == 0.0
|
||||
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.iqpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlannerIQ
|
||||
|
||||
|
||||
class _FakeIQDynamic:
|
||||
def __init__(self, requested=True, model_length=4.0, model_stop_time=5.0, minimum_force_stop_length=15.0):
|
||||
self._requested = requested
|
||||
self.model_length = model_length
|
||||
self.model_stop_time = model_stop_time
|
||||
self.minimum_force_stop_length = minimum_force_stop_length
|
||||
|
||||
def force_stop_requested(self):
|
||||
return self._requested
|
||||
|
||||
|
||||
def _build_planner(iq_dynamic):
|
||||
planner = LongitudinalPlannerIQ.__new__(LongitudinalPlannerIQ)
|
||||
planner.iq_dynamic = iq_dynamic
|
||||
planner.force_stop_timer = 0.0
|
||||
planner.forcing_stop = False
|
||||
planner.override_force_stop = False
|
||||
planner.override_force_stop_timer = 0.0
|
||||
planner.tracked_model_length = 0.0
|
||||
return planner
|
||||
|
||||
|
||||
def _build_sm(gas_pressed=False, accel_pressed=False, standstill=False):
|
||||
return {
|
||||
"carState": SimpleNamespace(gasPressed=gas_pressed, standstill=standstill),
|
||||
"iqCarState": SimpleNamespace(accelPressed=accel_pressed),
|
||||
}
|
||||
|
||||
|
||||
def test_force_stop_uses_model_stop_time_as_ramp():
|
||||
planner = _build_planner(_FakeIQDynamic(model_length=20.0, model_stop_time=5.0, minimum_force_stop_length=0.0))
|
||||
sm = _build_sm()
|
||||
|
||||
output = 12.0
|
||||
for _ in range(int(1.0 / DT_MDL)):
|
||||
output = planner._apply_force_stop(12.0, 0.0, sm, True)
|
||||
|
||||
assert planner.forcing_stop
|
||||
assert output == 4.0
|
||||
|
||||
|
||||
def test_force_stop_respects_minimum_force_stop_length():
|
||||
planner = _build_planner(_FakeIQDynamic(model_length=4.0, model_stop_time=5.0, minimum_force_stop_length=15.0))
|
||||
sm = _build_sm()
|
||||
|
||||
output = 12.0
|
||||
for _ in range(int(1.0 / DT_MDL)):
|
||||
output = planner._apply_force_stop(12.0, 0.0, sm, True)
|
||||
|
||||
assert planner.forcing_stop
|
||||
assert planner.tracked_model_length == 15.0
|
||||
assert output == 3.0
|
||||
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from cereal import custom, log
|
||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper, LaneChangeState
|
||||
from openpilot.iqpilot.selfdrive.controls.lib.helpers.lane_change import AutoLaneChangeMode
|
||||
|
||||
ManeuverType = custom.IQNavState.ManeuverType
|
||||
NavDirection = custom.NavDirection
|
||||
LaneChangeDirection = log.LaneChangeDirection
|
||||
|
||||
|
||||
class DummyCarState:
|
||||
def __init__(self, vEgo=25.0, leftBlinker=False, rightBlinker=False, leftBlindspot=False, rightBlindspot=False,
|
||||
steeringPressed=False, steeringTorque=0, brakePressed=False):
|
||||
self.vEgo = vEgo
|
||||
self.leftBlinker = leftBlinker
|
||||
self.rightBlinker = rightBlinker
|
||||
self.leftBlindspot = leftBlindspot
|
||||
self.rightBlindspot = rightBlindspot
|
||||
self.steeringPressed = steeringPressed
|
||||
self.steeringTorque = steeringTorque
|
||||
self.brakePressed = brakePressed
|
||||
|
||||
|
||||
class DummyNavState:
|
||||
def __init__(self, active=True, nextManeuverValid=True, nextManeuverType=int(ManeuverType.exit),
|
||||
nextManeuverDistance=300.0, nextManeuverDirection=int(NavDirection.right)):
|
||||
self.active = active
|
||||
self.nextManeuverValid = nextManeuverValid
|
||||
self.nextManeuverType = nextManeuverType
|
||||
self.nextManeuverDistance = nextManeuverDistance
|
||||
self.nextManeuverDirection = nextManeuverDirection
|
||||
|
||||
|
||||
def _make_dh(enabled: bool, enable_bsm: bool):
|
||||
dh = DesireHelper()
|
||||
dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE
|
||||
dh.nav_exit._read_enabled = lambda: enabled # bypass the (unregistered) param in tests
|
||||
dh.nav_exit._enable_bsm = enable_bsm
|
||||
return dh
|
||||
|
||||
|
||||
def _run(dh, carstate, nav_state, n=20):
|
||||
for _ in range(n):
|
||||
dh.update(carstate, True, 1.0, nav_state)
|
||||
return dh.desire
|
||||
|
||||
|
||||
def test_feature_off_no_exit_lane_change():
|
||||
dh = _make_dh(enabled=False, enable_bsm=True)
|
||||
cs = DummyCarState(rightBlindspot=False)
|
||||
assert _run(dh, cs, DummyNavState()) == log.Desire.none
|
||||
|
||||
|
||||
def test_no_bsm_requires_nudge_holds_without_one():
|
||||
# No blindspot monitor: nav exit must NOT auto-start; without a nudge it stays in preLaneChange.
|
||||
dh = _make_dh(enabled=True, enable_bsm=False)
|
||||
cs = DummyCarState(steeringPressed=False)
|
||||
assert _run(dh, cs, DummyNavState()) == log.Desire.none
|
||||
assert dh.lane_change_state == LaneChangeState.preLaneChange
|
||||
assert dh.lane_change_direction == LaneChangeDirection.right
|
||||
|
||||
|
||||
def test_no_bsm_starts_on_driver_nudge():
|
||||
# Driver nudges the wheel toward the exit (right -> negative torque) -> lane change starts.
|
||||
dh = _make_dh(enabled=True, enable_bsm=False)
|
||||
cs = DummyCarState(steeringPressed=True, steeringTorque=-1)
|
||||
assert _run(dh, cs, DummyNavState()) == log.Desire.laneChangeRight
|
||||
|
||||
|
||||
def test_bsm_auto_starts_when_clear():
|
||||
dh = _make_dh(enabled=True, enable_bsm=True)
|
||||
cs = DummyCarState(rightBlindspot=False)
|
||||
assert _run(dh, cs, DummyNavState()) == log.Desire.laneChangeRight
|
||||
|
||||
|
||||
def test_bsm_holds_when_blindspot_occupied():
|
||||
dh = _make_dh(enabled=True, enable_bsm=True)
|
||||
cs = DummyCarState(rightBlindspot=True)
|
||||
assert _run(dh, cs, DummyNavState()) == log.Desire.none
|
||||
|
||||
|
||||
def test_only_exit_maneuvers_trigger():
|
||||
# A turn maneuver (not an exit) must not trigger the exit lane change.
|
||||
dh = _make_dh(enabled=True, enable_bsm=True)
|
||||
cs = DummyCarState(rightBlindspot=False)
|
||||
nav = DummyNavState(nextManeuverType=int(ManeuverType.turn))
|
||||
assert _run(dh, cs, nav) == log.Desire.none
|
||||
|
||||
|
||||
def test_too_far_does_not_trigger():
|
||||
dh = _make_dh(enabled=True, enable_bsm=True)
|
||||
cs = DummyCarState(rightBlindspot=False)
|
||||
nav = DummyNavState(nextManeuverDistance=900.0)
|
||||
assert _run(dh, cs, nav) == log.Desire.none
|
||||
@@ -0,0 +1,463 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.iqpilot.common.slc_variables import OFFSET_MAP_IMPERIAL
|
||||
from openpilot.iqpilot.selfdrive.controls.lib.slc_vcruise import SLCVCruise, CRUISING_SPEED
|
||||
from openpilot.iqpilot.selfdrive.controls.lib.speed_limit_controller import SpeedLimitController, POLICY_MAP_DATA_PRIORITY, POLICY_COMBINED
|
||||
|
||||
|
||||
class FakeParams:
|
||||
def __init__(self):
|
||||
self.values = {}
|
||||
|
||||
def get(self, key, encoding=None):
|
||||
_ = encoding
|
||||
return self.values.get(key)
|
||||
|
||||
def get_bool(self, key):
|
||||
return bool(self.values.get(key, False))
|
||||
|
||||
def put_nonblocking(self, key, value):
|
||||
self.values[key] = value
|
||||
|
||||
def put(self, key, value):
|
||||
self.values[key] = value
|
||||
|
||||
|
||||
def _build_sm(v_cruise_cluster=100.0, v_ego_cluster=27.8, gas=False, enabled=True, iq_limit=0.0):
|
||||
# vCruiseCluster is in kph in carState.
|
||||
return {
|
||||
"carState": SimpleNamespace(vCruiseCluster=v_cruise_cluster, vEgoCluster=v_ego_cluster, gasPressed=gas,
|
||||
steeringAngleDeg=0.0, buttonEvents=[]),
|
||||
"iqCarState": SimpleNamespace(speedLimit=iq_limit, accelPressed=False, decelPressed=False),
|
||||
"selfdriveState": SimpleNamespace(enabled=enabled),
|
||||
"liveParameters": SimpleNamespace(angleOffsetDeg=0.0),
|
||||
}
|
||||
|
||||
|
||||
class _FakeSLC:
|
||||
def __init__(self):
|
||||
self.target = 0.0
|
||||
self.source = "None"
|
||||
self.active_target = 0.0
|
||||
self.active_source = "None"
|
||||
self.unconfirmed_speed_limit = 0.0
|
||||
self.overridden_speed = 0.0
|
||||
self.pending_events = []
|
||||
self.assist_state = None
|
||||
self.output_a_target = 0.0
|
||||
self.update_limits_calls = 0
|
||||
self.update_override_calls = 0
|
||||
self._offset = 0.0
|
||||
|
||||
def update_limits(self, *_args, **_kwargs):
|
||||
self.update_limits_calls += 1
|
||||
|
||||
def update_override(self, *_args, **_kwargs):
|
||||
self.update_override_calls += 1
|
||||
|
||||
def get_offset(self, _is_metric):
|
||||
return self._offset
|
||||
|
||||
|
||||
def _base_slc_params_controller():
|
||||
return {
|
||||
"slc_policy": POLICY_MAP_DATA_PRIORITY,
|
||||
"slc_auto_confirm": False,
|
||||
"slc_fallback_previous_speed_limit": False,
|
||||
"slc_fallback_set_speed": False,
|
||||
"speed_limit_confirmation_higher": False,
|
||||
"speed_limit_confirmation_lower": False,
|
||||
"slc_online_filler": True,
|
||||
"map_speed_lookahead_higher": 5.0,
|
||||
"map_speed_lookahead_lower": 5.0,
|
||||
}
|
||||
|
||||
|
||||
def test_speed_limit_controller_resolves_source_by_priority():
|
||||
params = FakeParams()
|
||||
controller = SpeedLimitController(params)
|
||||
controller.update_gps = lambda _sm: None
|
||||
controller._resolver.update_map_data = lambda *_args, **_kwargs: None
|
||||
controller.get_mapbox_speed_limit = lambda *_args, **_kwargs: None
|
||||
controller.mapbox_requests["total_requests"] = 0
|
||||
controller.mapbox_requests["max_requests"] = 999999
|
||||
controller.mapbox_limit = 22.0
|
||||
controller._resolver.map_speed_limit = 18.0 # map data wins in map_data_priority policy
|
||||
|
||||
sm = _build_sm(iq_limit=25.0)
|
||||
slc_params = _base_slc_params_controller()
|
||||
slc_params["slc_policy"] = POLICY_MAP_DATA_PRIORITY
|
||||
|
||||
controller.update_limits(25.0, datetime.now(), True, 30.0, 27.0, sm, slc_params)
|
||||
assert controller.active_source == "Map Data"
|
||||
assert controller.active_target == 18.0
|
||||
|
||||
|
||||
def test_speed_limit_controller_combined_mode_prefers_smallest_limit():
|
||||
params = FakeParams()
|
||||
controller = SpeedLimitController(params)
|
||||
controller.update_gps = lambda _sm: None
|
||||
controller._resolver.update_map_data = lambda *_args, **_kwargs: None
|
||||
controller.get_mapbox_speed_limit = lambda *_args, **_kwargs: None
|
||||
controller.mapbox_requests["total_requests"] = 0
|
||||
controller.mapbox_requests["max_requests"] = 999999
|
||||
controller.mapbox_limit = 24.0
|
||||
controller._resolver.map_speed_limit = 16.0 # smallest of: dashboard=28, mapbox=24, map_data=16
|
||||
|
||||
sm = _build_sm(iq_limit=28.0)
|
||||
slc_params = _base_slc_params_controller()
|
||||
slc_params["slc_policy"] = POLICY_COMBINED
|
||||
|
||||
controller.update_limits(28.0, datetime.now(), True, 31.0, 27.0, sm, slc_params)
|
||||
assert controller.active_source == "Map Data"
|
||||
assert controller.active_target == 16.0
|
||||
|
||||
|
||||
def test_slc_vcruise_applies_target_without_increasing_cruise():
|
||||
slc = SLCVCruise()
|
||||
slc.slc = _FakeSLC()
|
||||
slc.slc.target = 23.0
|
||||
slc.slc.source = "Dashboard"
|
||||
slc.slc.active_target = 23.0
|
||||
slc.slc.active_source = "Dashboard"
|
||||
slc.slc._offset = 1.0
|
||||
|
||||
slc._get_slc_params = lambda: {
|
||||
"speed_limit_controller": True,
|
||||
"speed_limit_mode": 3,
|
||||
"show_speed_limits": False,
|
||||
"is_metric": True,
|
||||
"slc_policy": POLICY_MAP_DATA_PRIORITY,
|
||||
"slc_auto_confirm": False,
|
||||
"speed_limit_confirmation_higher": False,
|
||||
"speed_limit_confirmation_lower": False,
|
||||
"map_speed_lookahead_higher": 5.0,
|
||||
"map_speed_lookahead_lower": 5.0,
|
||||
"slc_fallback_experimental_mode": False,
|
||||
"slc_fallback_set_speed": False,
|
||||
"slc_fallback_previous_speed_limit": False,
|
||||
"speed_limit_controller_override_manual": True,
|
||||
"speed_limit_controller_override_set_speed": False,
|
||||
"slc_online_filler": False,
|
||||
}
|
||||
|
||||
v_cruise = 30.0
|
||||
sm = _build_sm(v_cruise_cluster=v_cruise * CV.MS_TO_KPH, v_ego_cluster=27.0, iq_limit=23.0)
|
||||
out = slc.update(apply_enabled=True, now=None, time_validated=True, v_cruise=v_cruise, v_ego=27.0, sm=sm)
|
||||
|
||||
assert slc.slc.update_limits_calls == 1
|
||||
assert slc.slc.update_override_calls == 1
|
||||
assert out <= v_cruise
|
||||
assert out >= CRUISING_SPEED
|
||||
|
||||
|
||||
def test_slc_vcruise_show_only_does_not_modify_cruise():
|
||||
slc = SLCVCruise()
|
||||
slc.slc = _FakeSLC()
|
||||
slc.slc.target = 21.0
|
||||
slc.slc.source = "Map Data"
|
||||
slc.slc.active_target = 21.0
|
||||
slc.slc.active_source = "Map Data"
|
||||
slc._get_slc_params = lambda: {
|
||||
"speed_limit_controller": False,
|
||||
"speed_limit_mode": 1,
|
||||
"show_speed_limits": True,
|
||||
"is_metric": True,
|
||||
"slc_policy": POLICY_MAP_DATA_PRIORITY,
|
||||
"slc_auto_confirm": False,
|
||||
"speed_limit_confirmation_higher": False,
|
||||
"speed_limit_confirmation_lower": False,
|
||||
"map_speed_lookahead_higher": 5.0,
|
||||
"map_speed_lookahead_lower": 5.0,
|
||||
"slc_fallback_experimental_mode": False,
|
||||
"slc_fallback_set_speed": False,
|
||||
"slc_fallback_previous_speed_limit": False,
|
||||
"speed_limit_controller_override_manual": True,
|
||||
"speed_limit_controller_override_set_speed": False,
|
||||
"slc_online_filler": False,
|
||||
}
|
||||
|
||||
v_cruise = 29.0
|
||||
sm = _build_sm(v_cruise_cluster=v_cruise * CV.MS_TO_KPH, v_ego_cluster=26.0, iq_limit=21.0)
|
||||
out = slc.update(apply_enabled=True, now=None, time_validated=True, v_cruise=v_cruise, v_ego=26.0, sm=sm)
|
||||
|
||||
assert slc.slc.update_limits_calls == 1
|
||||
assert slc.slc.update_override_calls == 0
|
||||
assert out == v_cruise
|
||||
|
||||
|
||||
def test_slc_vcruise_auto_raises_for_higher_limit_when_confirmation_disabled():
|
||||
slc = SLCVCruise()
|
||||
slc.slc = _FakeSLC()
|
||||
slc.slc.target = 20.0
|
||||
slc.slc.source = "Map Data"
|
||||
slc.slc.active_target = 20.0
|
||||
slc.slc.active_source = "Map Data"
|
||||
|
||||
slc._get_slc_params = lambda: {
|
||||
"speed_limit_controller": True,
|
||||
"speed_limit_mode": 3,
|
||||
"show_speed_limits": False,
|
||||
"is_metric": True,
|
||||
"slc_policy": POLICY_MAP_DATA_PRIORITY,
|
||||
"slc_auto_confirm": False,
|
||||
"speed_limit_confirmation_higher": False,
|
||||
"speed_limit_confirmation_lower": False,
|
||||
"map_speed_lookahead_higher": 5.0,
|
||||
"map_speed_lookahead_lower": 5.0,
|
||||
"slc_fallback_experimental_mode": False,
|
||||
"slc_fallback_set_speed": False,
|
||||
"slc_fallback_previous_speed_limit": False,
|
||||
"speed_limit_controller_override_manual": True,
|
||||
"speed_limit_controller_override_set_speed": False,
|
||||
"slc_online_filler": False,
|
||||
}
|
||||
|
||||
v_cruise = 13.5
|
||||
sm = _build_sm(v_cruise_cluster=v_cruise * CV.MS_TO_KPH, v_ego_cluster=13.5, iq_limit=20.0)
|
||||
out = slc.update(apply_enabled=True, now=None, time_validated=True, v_cruise=v_cruise, v_ego=13.5, sm=sm)
|
||||
|
||||
assert out > v_cruise
|
||||
assert out == 20.0
|
||||
|
||||
|
||||
def test_slc_vcruise_does_not_auto_raise_when_higher_confirmation_enabled():
|
||||
slc = SLCVCruise()
|
||||
slc.slc = _FakeSLC()
|
||||
slc.slc.target = 20.0
|
||||
slc.slc.source = "Map Data"
|
||||
slc.slc.active_target = 20.0
|
||||
slc.slc.active_source = "Map Data"
|
||||
|
||||
slc._get_slc_params = lambda: {
|
||||
"speed_limit_controller": True,
|
||||
"speed_limit_mode": 3,
|
||||
"show_speed_limits": False,
|
||||
"is_metric": True,
|
||||
"slc_policy": POLICY_MAP_DATA_PRIORITY,
|
||||
"slc_auto_confirm": False,
|
||||
"speed_limit_confirmation_higher": True,
|
||||
"speed_limit_confirmation_lower": False,
|
||||
"map_speed_lookahead_higher": 5.0,
|
||||
"map_speed_lookahead_lower": 5.0,
|
||||
"slc_fallback_experimental_mode": False,
|
||||
"slc_fallback_set_speed": False,
|
||||
"slc_fallback_previous_speed_limit": False,
|
||||
"speed_limit_controller_override_manual": True,
|
||||
"speed_limit_controller_override_set_speed": False,
|
||||
"slc_online_filler": False,
|
||||
}
|
||||
|
||||
v_cruise = 13.5
|
||||
sm = _build_sm(v_cruise_cluster=v_cruise * CV.MS_TO_KPH, v_ego_cluster=13.5, iq_limit=20.0)
|
||||
out = slc.update(apply_enabled=True, now=None, time_validated=True, v_cruise=v_cruise, v_ego=13.5, sm=sm)
|
||||
|
||||
assert out == v_cruise
|
||||
|
||||
|
||||
class _FakeSM(dict):
|
||||
def __init__(self, services, alive=None):
|
||||
super().__init__(services)
|
||||
self.alive = alive or {}
|
||||
|
||||
|
||||
def _construction_sm(active=True, alive=True, iq_limit=0.0):
|
||||
sm = _FakeSM(_build_sm(iq_limit=iq_limit))
|
||||
sm["iqConstructionZone"] = SimpleNamespace(active=active, orangeFraction=0.001, secondsSinceHit=1.0)
|
||||
sm.alive = {"iqConstructionZone": alive}
|
||||
return sm
|
||||
|
||||
|
||||
def _construction_controller():
|
||||
params = FakeParams()
|
||||
controller = SpeedLimitController(params)
|
||||
controller.update_gps = lambda _sm: None
|
||||
controller._resolver.update_map_data = lambda *_args, **_kwargs: None
|
||||
controller.get_mapbox_speed_limit = lambda *_args, **_kwargs: None
|
||||
controller.mapbox_requests["total_requests"] = 0
|
||||
controller.mapbox_requests["max_requests"] = 999999
|
||||
return controller
|
||||
|
||||
|
||||
def _construction_slc_params():
|
||||
slc_params = _base_slc_params_controller()
|
||||
slc_params["slc_online_filler"] = False
|
||||
slc_params["construction_zone_assist"] = True
|
||||
slc_params["construction_zone_speed"] = 60.0
|
||||
slc_params["is_metric"] = False
|
||||
return slc_params
|
||||
|
||||
|
||||
def test_construction_zone_clamps_higher_limit():
|
||||
controller = _construction_controller()
|
||||
controller._resolver.map_speed_limit = 31.3 # ~70 mph
|
||||
sm = _construction_sm()
|
||||
|
||||
controller.update_limits(0.0, None, True, 33.0, 30.0, sm, _construction_slc_params())
|
||||
assert controller.active_source == "Construction"
|
||||
assert abs(controller.active_target - 60.0 * CV.MPH_TO_MS) < 1e-6
|
||||
|
||||
|
||||
def test_construction_zone_does_not_raise_lower_limit():
|
||||
controller = _construction_controller()
|
||||
controller._resolver.map_speed_limit = 20.0 # below the 60 mph clamp
|
||||
sm = _construction_sm()
|
||||
|
||||
controller.update_limits(0.0, None, True, 33.0, 30.0, sm, _construction_slc_params())
|
||||
assert controller.active_source == "Map Data"
|
||||
assert controller.active_target == 20.0
|
||||
|
||||
|
||||
def test_construction_zone_applies_without_other_sources():
|
||||
controller = _construction_controller()
|
||||
controller._resolver.map_speed_limit = 0.0
|
||||
sm = _construction_sm()
|
||||
|
||||
controller.update_limits(0.0, None, True, 33.0, 30.0, sm, _construction_slc_params())
|
||||
assert controller.active_source == "Construction"
|
||||
assert abs(controller.active_target - 60.0 * CV.MPH_TO_MS) < 1e-6
|
||||
|
||||
|
||||
def test_construction_zone_ignored_when_not_alive_or_inactive_or_disabled():
|
||||
for kwargs, slc_toggle in (
|
||||
(dict(alive=False), True),
|
||||
(dict(active=False), True),
|
||||
(dict(), False),
|
||||
):
|
||||
controller = _construction_controller()
|
||||
controller._resolver.map_speed_limit = 31.3
|
||||
sm = _construction_sm(**kwargs)
|
||||
slc_params = _construction_slc_params()
|
||||
slc_params["construction_zone_assist"] = slc_toggle
|
||||
|
||||
controller.update_limits(0.0, None, True, 33.0, 30.0, sm, slc_params)
|
||||
assert controller.active_source == "Map Data"
|
||||
assert controller.active_target == 31.3
|
||||
|
||||
|
||||
def test_construction_zone_metric_speed_units():
|
||||
controller = _construction_controller()
|
||||
controller._resolver.map_speed_limit = 33.0
|
||||
sm = _construction_sm()
|
||||
slc_params = _construction_slc_params()
|
||||
slc_params["is_metric"] = True
|
||||
slc_params["construction_zone_speed"] = 100.0 # kph
|
||||
|
||||
controller.update_limits(0.0, None, True, 36.0, 33.0, sm, slc_params)
|
||||
assert controller.active_source == "Construction"
|
||||
assert abs(controller.active_target - 100.0 * CV.KPH_TO_MS) < 1e-6
|
||||
|
||||
|
||||
def test_construction_zone_never_raises_cruise_even_with_auto_raise():
|
||||
slc = SLCVCruise()
|
||||
slc.slc = _FakeSLC()
|
||||
slc.slc.target = 60.0 * CV.MPH_TO_MS
|
||||
slc.slc.source = "Construction"
|
||||
slc.slc.active_target = slc.slc.target
|
||||
slc.slc.active_source = "Construction"
|
||||
slc.slc._offset = 2.0 # must be ignored for Construction
|
||||
|
||||
slc._get_slc_params = lambda: {
|
||||
"speed_limit_controller": True,
|
||||
"speed_limit_mode": 3,
|
||||
"show_speed_limits": False,
|
||||
"is_metric": False,
|
||||
"slc_policy": POLICY_MAP_DATA_PRIORITY,
|
||||
"slc_auto_confirm": False,
|
||||
"speed_limit_confirmation_higher": False, # auto-raise allowed
|
||||
"speed_limit_confirmation_lower": False,
|
||||
"map_speed_lookahead_higher": 5.0,
|
||||
"map_speed_lookahead_lower": 5.0,
|
||||
"slc_fallback_experimental_mode": False,
|
||||
"slc_fallback_set_speed": False,
|
||||
"slc_fallback_previous_speed_limit": False,
|
||||
"speed_limit_controller_override_manual": True,
|
||||
"speed_limit_controller_override_set_speed": False,
|
||||
"slc_online_filler": False,
|
||||
"construction_zone_assist": True,
|
||||
"construction_zone_speed": 60.0,
|
||||
}
|
||||
|
||||
# user cruising below the construction clamp: must not be raised to it
|
||||
v_cruise = 22.0
|
||||
sm = _build_sm(v_cruise_cluster=v_cruise * CV.MS_TO_KPH, v_ego_cluster=22.0)
|
||||
out = slc.update(apply_enabled=True, now=None, time_validated=True, v_cruise=v_cruise, v_ego=22.0, sm=sm)
|
||||
assert out == v_cruise
|
||||
assert slc.slc_offset == 0
|
||||
|
||||
# user cruising above it: clamped down
|
||||
v_cruise = 33.0
|
||||
sm = _build_sm(v_cruise_cluster=v_cruise * CV.MS_TO_KPH, v_ego_cluster=33.0)
|
||||
out = slc.update(apply_enabled=True, now=None, time_validated=True, v_cruise=v_cruise, v_ego=33.0, sm=sm)
|
||||
assert abs(out - 60.0 * CV.MPH_TO_MS) < 1e-6
|
||||
|
||||
|
||||
def _offset_controller(pct1=10.0, pct2=5.0, pct3=8.0):
|
||||
params = FakeParams()
|
||||
params.put("speed_limit_offset1", pct1)
|
||||
params.put("speed_limit_offset2", pct2)
|
||||
params.put("speed_limit_offset3", pct3)
|
||||
controller = SpeedLimitController(params)
|
||||
controller._assist.source = "Map Data"
|
||||
return controller
|
||||
|
||||
|
||||
def test_get_offset_percent_per_zone():
|
||||
controller = _offset_controller()
|
||||
|
||||
controller._assist.target = 6.7 # ~15 mph -> zone 1
|
||||
assert abs(controller.get_offset(False) - 6.7 * 0.10) < 1e-9
|
||||
|
||||
controller._assist.target = 13.4 # ~30 mph -> zone 2
|
||||
assert abs(controller.get_offset(False) - 13.4 * 0.05) < 1e-9
|
||||
|
||||
controller._assist.target = 31.3 # ~70 mph -> zone 3 (open-ended)
|
||||
assert abs(controller.get_offset(False) - 31.3 * 0.08) < 1e-9
|
||||
|
||||
|
||||
def test_get_offset_zone_lower_bound_inclusive():
|
||||
controller = _offset_controller()
|
||||
boundary = OFFSET_MAP_IMPERIAL[1][0]
|
||||
controller._assist.target = boundary
|
||||
assert abs(controller.get_offset(False) - boundary * 0.05) < 1e-9
|
||||
|
||||
|
||||
def test_get_offset_zero_without_real_limit_source():
|
||||
for source in ("None", "Construction"):
|
||||
controller = _offset_controller()
|
||||
controller._assist.source = source
|
||||
controller._assist.target = 30.0
|
||||
assert controller.get_offset(False) == 0.0
|
||||
|
||||
|
||||
def test_get_offset_percent_clamped():
|
||||
controller = _offset_controller(pct3=500.0)
|
||||
controller._assist.target = 30.0
|
||||
assert abs(controller.get_offset(False) - 30.0 * 0.50) < 1e-9
|
||||
|
||||
|
||||
def test_construction_zone_fires_event_once_per_zone_entry():
|
||||
from cereal import custom
|
||||
event = custom.IQOnroadEvent.EventName.constructionZoneDetected
|
||||
|
||||
controller = _construction_controller()
|
||||
controller._resolver.map_speed_limit = 31.3
|
||||
slc_params = _construction_slc_params()
|
||||
|
||||
controller.update_limits(0.0, None, True, 33.0, 30.0, _construction_sm(), slc_params)
|
||||
assert event in controller.pending_events
|
||||
|
||||
controller.update_limits(0.0, None, True, 33.0, 30.0, _construction_sm(), slc_params)
|
||||
assert event not in controller.pending_events
|
||||
|
||||
# zone releases, then a new zone: fires again
|
||||
controller.update_limits(0.0, None, True, 33.0, 30.0, _construction_sm(active=False), slc_params)
|
||||
assert event not in controller.pending_events
|
||||
controller.update_limits(0.0, None, True, 33.0, 30.0, _construction_sm(), slc_params)
|
||||
assert event in controller.pending_events
|
||||
100
iqpilot/selfdrive/controls/lib/tests/test_smooth_stops.py
Normal file
100
iqpilot/selfdrive/controls/lib/tests/test_smooth_stops.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
|
||||
Original concept and implementation by SpysyWeeb (github.com/SpysyWeeb)
|
||||
"""
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.iqpilot.selfdrive.controls.lib.smooth_stops import (
|
||||
SmoothStopController,
|
||||
read_smooth_stops_enabled,
|
||||
STANDSTILL_SPEED,
|
||||
STANDSTILL_HOLD_SPEED,
|
||||
SETTLE_DECEL,
|
||||
TAPER_SPEED,
|
||||
STOP_KISS_DECEL,
|
||||
SETTLE_JERK,
|
||||
EMERGENCY_DECEL,
|
||||
)
|
||||
|
||||
JERK_STEP = SETTLE_JERK * DT_CTRL
|
||||
|
||||
|
||||
def _build(enabled=True):
|
||||
c = SmoothStopController.__new__(SmoothStopController)
|
||||
c.enabled = enabled
|
||||
c._v_min = float("inf")
|
||||
c._stall_s = 0.0
|
||||
return c
|
||||
|
||||
|
||||
def test_unified_toggle_reads_force_stops():
|
||||
seen = {}
|
||||
|
||||
class FakeParams:
|
||||
def get_bool(self, key):
|
||||
seen["key"] = key
|
||||
return True
|
||||
|
||||
assert read_smooth_stops_enabled(FakeParams()) is True
|
||||
assert seen["key"] == "IQForceStops"
|
||||
|
||||
|
||||
def test_hold_only_arms_at_standstill():
|
||||
c = _build()
|
||||
assert not c.want_hold(True, 0.5, False)
|
||||
assert not c.want_hold(True, STANDSTILL_SPEED + 0.05, False)
|
||||
assert not c.want_hold(True, 1.0, True)
|
||||
assert not c.want_hold(True, STANDSTILL_HOLD_SPEED + 0.05, True)
|
||||
assert c.want_hold(True, STANDSTILL_SPEED - 0.01, False)
|
||||
assert c.want_hold(True, STANDSTILL_HOLD_SPEED - 0.01, True)
|
||||
assert not c.want_hold(False, 0.0, True)
|
||||
|
||||
|
||||
def test_settle_feathers_toward_baseline():
|
||||
c = _build()
|
||||
out = c.settle(a_target=0.0, v_ego=1.0, lead_distance=0.0, has_lead=False, last_output=0.0)
|
||||
assert out == -JERK_STEP
|
||||
|
||||
|
||||
def test_settle_never_softer_than_mpc():
|
||||
c = _build()
|
||||
out = c.settle(a_target=-2.0, v_ego=1.0, lead_distance=0.0, has_lead=False, last_output=-1.0)
|
||||
assert out == -1.0 - JERK_STEP
|
||||
assert out < -1.0
|
||||
|
||||
|
||||
def test_settle_emergency_bypasses_jerk_limit():
|
||||
c = _build()
|
||||
out = c.settle(a_target=-3.4, v_ego=2.0, lead_distance=0.0, has_lead=False, last_output=0.0)
|
||||
assert out == -3.4
|
||||
assert out <= -EMERGENCY_DECEL
|
||||
|
||||
|
||||
def test_settle_lead_firms_up_when_close():
|
||||
c = _build()
|
||||
assert c.settle(a_target=0.0, v_ego=1.0, lead_distance=50.0, has_lead=True, last_output=-SETTLE_DECEL) == -SETTLE_DECEL
|
||||
c = _build()
|
||||
assert c.settle(a_target=0.0, v_ego=1.0, lead_distance=3.0, has_lead=True, last_output=-1.0) == -1.0
|
||||
|
||||
|
||||
def test_settle_anti_creep_firms_up_when_not_slowing():
|
||||
c = _build()
|
||||
out = c.settle(a_target=0.0, v_ego=0.5, lead_distance=0.0, has_lead=False, last_output=-SETTLE_DECEL)
|
||||
for _ in range(60):
|
||||
out = c.settle(a_target=0.0, v_ego=0.5, lead_distance=0.0, has_lead=False, last_output=out)
|
||||
assert out < -SETTLE_DECEL
|
||||
|
||||
|
||||
def test_settle_eases_off_near_stop():
|
||||
c = _build()
|
||||
near = c.settle(a_target=0.0, v_ego=0.1, lead_distance=0.0, has_lead=False, last_output=-0.305)
|
||||
c = _build()
|
||||
high = c.settle(a_target=0.0, v_ego=0.9, lead_distance=0.0, has_lead=False, last_output=-0.745)
|
||||
assert near > high
|
||||
assert near == -(STOP_KISS_DECEL + (SETTLE_DECEL - STOP_KISS_DECEL) * (0.1 / TAPER_SPEED))
|
||||
|
||||
|
||||
def test_settle_kiss_decel_at_stop():
|
||||
c = _build()
|
||||
out = c.settle(a_target=0.0, v_ego=0.0, lead_distance=0.0, has_lead=False, last_output=-STOP_KISS_DECEL)
|
||||
assert out == -STOP_KISS_DECEL
|
||||
Reference in New Issue
Block a user