IQ.Pilot Release Commit @ bec7652

This commit is contained in:
IQ.Lvbs history cleanup
2026-08-22 23:42:41 -05:00
commit 58039e647c
4603 changed files with 1236178 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
from __future__ import annotations
from dataclasses import dataclass
import math
from iqpilot.cereal import custom, log
import iqpilot.cereal.messaging as messaging
from iqpilot.common.realtime import DT_MDL
from iqpilot.selfdrive.controls.lib.desire_helper import DesireHelper
from iqpilot.selfdrive.controls.lib.helpers.lane_change import AutoLaneChangeMode
from iqpilot.selfdrive.controls.lib.helpers.lateral_edge_guard import (
BLOCK_DEBOUNCE_S,
CLEAR_DEBOUNCE_S,
MAX_VALID_ROAD_EDGE_STD_M,
MIN_ACTIVE_SPEED_MPS,
REQUIRED_ROAD_EDGE_DISTANCE_M,
UNAVAILABLE_HOLD_S,
LateralEdgeGuard,
RoadEdgeDataState,
evaluate_road_edge,
)
from iqpilot.selfdrive.selfdrived.iq_events import EVENTS_IQ, ET
from iqpilot.selfdrive.selfdrived.selfdrived import SelfdriveD
@dataclass
class Edge:
x: list[float]
y: list[float]
@dataclass
class ModelData:
roadEdges: list[Edge]
roadEdgeStds: list[float]
class CarState:
def __init__(self, left_blindspot: bool = False) -> None:
self.vEgo = MIN_ACTIVE_SPEED_MPS + 1.0
self.leftBlinker = True
self.rightBlinker = False
self.leftBlindspot = left_blindspot
self.rightBlindspot = False
self.steeringPressed = True
self.steeringTorque = 1.0
self.brakePressed = False
self.standstill = False
def edge_model(left_distance_m: float = 6.0, right_distance_m: float = 6.0,
left_std_m: float = 0.0, right_std_m: float = 0.0) -> ModelData:
xs = [5.0, 20.0, 40.0]
return ModelData(
[Edge(xs, [-left_distance_m] * len(xs)), Edge(xs, [right_distance_m] * len(xs))],
[left_std_m, right_std_m],
)
def cycles(duration_s: float) -> int:
return math.ceil(duration_s / DT_MDL)
def update_for(guard: LateralEdgeGuard, modeldata: ModelData | None, duration_s: float,
speed_mps: float = MIN_ACTIVE_SPEED_MPS) -> None:
for _ in range(cycles(duration_s)):
guard.update(modeldata, speed_mps, DT_MDL)
def test_valid_geometry_blocks_and_clear_geometry_does_not_block() -> None:
blocked = evaluate_road_edge(edge_model(4.0).roadEdges[0], 0.2, log.LaneChangeDirection.left)
clear = evaluate_road_edge(edge_model(6.0).roadEdges[0], 0.2, log.LaneChangeDirection.left)
assert blocked.state == RoadEdgeDataState.VALID
assert blocked.should_block is True
assert clear.state == RoadEdgeDataState.VALID
assert clear.should_block is False
def test_unavailable_and_invalid_are_distinct() -> None:
unavailable = evaluate_road_edge(Edge([5.0], []), 0.2, log.LaneChangeDirection.left)
invalid = evaluate_road_edge(edge_model().roadEdges[0], MAX_VALID_ROAD_EDGE_STD_M + 0.01,
log.LaneChangeDirection.left)
assert unavailable.state == RoadEdgeDataState.UNAVAILABLE
assert unavailable.lateral_distance_m is None
assert invalid.state == RoadEdgeDataState.INVALID
assert invalid.should_block is None
def test_two_sigma_bound_uses_std_in_metres() -> None:
measurement = evaluate_road_edge(edge_model(5.0).roadEdges[0], 0.2, log.LaneChangeDirection.left)
assert measurement.lateral_distance_m == 5.0
assert measurement.conservative_distance_m == 4.6
assert measurement.should_block is True
def test_distance_threshold_on_either_side() -> None:
epsilon_m = 0.001
for direction, edge_index in ((log.LaneChangeDirection.left, 0), (log.LaneChangeDirection.right, 1)):
below = edge_model(REQUIRED_ROAD_EDGE_DISTANCE_M - epsilon_m, REQUIRED_ROAD_EDGE_DISTANCE_M - epsilon_m)
above = edge_model(REQUIRED_ROAD_EDGE_DISTANCE_M + epsilon_m, REQUIRED_ROAD_EDGE_DISTANCE_M + epsilon_m)
assert evaluate_road_edge(below.roadEdges[edge_index], 0.0, direction).should_block is True
assert evaluate_road_edge(above.roadEdges[edge_index], 0.0, direction).should_block is False
def test_block_debounce_rejects_a_single_clear_frame() -> None:
guard = LateralEdgeGuard()
blocking = edge_model(4.0)
clear = edge_model(6.0)
update_for(guard, blocking, BLOCK_DEBOUNCE_S - DT_MDL)
assert guard.block_for_direction(log.LaneChangeDirection.left) == custom.IQLateralEdgeBlock.none
guard.update(clear, MIN_ACTIVE_SPEED_MPS, DT_MDL)
update_for(guard, blocking, BLOCK_DEBOUNCE_S)
assert guard.block_for_direction(log.LaneChangeDirection.left) == custom.IQLateralEdgeBlock.left
def test_clear_debounce_rejects_a_single_blocking_frame() -> None:
guard = LateralEdgeGuard()
update_for(guard, edge_model(4.0), BLOCK_DEBOUNCE_S)
update_for(guard, edge_model(6.0), CLEAR_DEBOUNCE_S - DT_MDL)
assert guard.block_for_direction(log.LaneChangeDirection.left) == custom.IQLateralEdgeBlock.left
guard.update(edge_model(4.0), MIN_ACTIVE_SPEED_MPS, DT_MDL)
update_for(guard, edge_model(6.0), CLEAR_DEBOUNCE_S)
assert guard.block_for_direction(log.LaneChangeDirection.left) == custom.IQLateralEdgeBlock.none
def test_unavailable_holds_then_falls_back_to_not_blocking() -> None:
guard = LateralEdgeGuard()
update_for(guard, edge_model(4.0), BLOCK_DEBOUNCE_S)
update_for(guard, None, UNAVAILABLE_HOLD_S - DT_MDL)
assert guard.left_measurement.state == RoadEdgeDataState.UNAVAILABLE
assert guard.block_for_direction(log.LaneChangeDirection.left) == custom.IQLateralEdgeBlock.left
guard.update(None, MIN_ACTIVE_SPEED_MPS, DT_MDL)
assert guard.block_for_direction(log.LaneChangeDirection.left) == custom.IQLateralEdgeBlock.none
def test_invalid_measurement_clears_through_release_debounce() -> None:
guard = LateralEdgeGuard()
update_for(guard, edge_model(4.0), BLOCK_DEBOUNCE_S)
invalid = edge_model(4.0, left_std_m=MAX_VALID_ROAD_EDGE_STD_M + 0.01)
update_for(guard, invalid, CLEAR_DEBOUNCE_S - DT_MDL)
assert guard.block_for_direction(log.LaneChangeDirection.left) == custom.IQLateralEdgeBlock.left
guard.update(invalid, MIN_ACTIVE_SPEED_MPS, DT_MDL)
assert guard.block_for_direction(log.LaneChangeDirection.left) == custom.IQLateralEdgeBlock.none
def test_speed_gate_is_inactive_below_threshold() -> None:
guard = LateralEdgeGuard()
update_for(guard, edge_model(4.0), BLOCK_DEBOUNCE_S, MIN_ACTIVE_SPEED_MPS - 0.01)
assert guard.block_for_direction(log.LaneChangeDirection.left) == custom.IQLateralEdgeBlock.none
update_for(guard, edge_model(4.0), BLOCK_DEBOUNCE_S, MIN_ACTIVE_SPEED_MPS)
assert guard.block_for_direction(log.LaneChangeDirection.left) == custom.IQLateralEdgeBlock.left
def test_desire_helper_keeps_edge_block_out_of_blindspot_path() -> None:
helper = DesireHelper()
helper.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE
helper.lane_change_state = log.LaneChangeState.preLaneChange
helper.lane_change_direction = log.LaneChangeDirection.left
update_for(helper.lateral_edge_guard, edge_model(4.0), BLOCK_DEBOUNCE_S)
blindspot_arguments: list[bool] = []
def record_blindspot(blindspot_detected: bool, brake_pressed: bool) -> None:
blindspot_arguments.append(blindspot_detected)
helper.alc.update_lane_change = record_blindspot
helper.update(CarState(left_blindspot=False), True, 1.0, modeldata=edge_model(4.0))
assert blindspot_arguments == [False]
assert helper.lateral_edge_block == custom.IQLateralEdgeBlock.left
assert helper.lane_change_state == log.LaneChangeState.preLaneChange
helper.update(CarState(left_blindspot=True), True, 1.0, modeldata=edge_model(4.0))
assert blindspot_arguments[-1] is True
def test_published_edge_block_maps_to_distinct_event_and_alert() -> None:
message = messaging.new_message("iqDriveModelData")
message.iqDriveModelData.lateralEdgeBlock = custom.IQLateralEdgeBlock.right
class SubMaster:
updated = {"iqDriveModelData": True}
def __getitem__(self, service: str):
assert service == "iqDriveModelData"
return message.iqDriveModelData
selfdrived = SelfdriveD.__new__(SelfdriveD)
selfdrived.sm = SubMaster()
selfdrived._cached_model_event_names = ()
selfdrived._refresh_cached_model_events()
event_name = custom.IQOnroadEvent.EventName.lateralEdgeBlocked
assert selfdrived._cached_model_event_names == (event_name,)
alert = EVENTS_IQ[event_name][ET.WARNING]
assert alert.alert_text_1 == "Lane Change Blocked"
assert alert.alert_text_2 == "Road edge detected"