Add openpilot tests
This commit is contained in:
1
selfdrive/test/longitudinal_maneuvers/.gitignore
vendored
Normal file
1
selfdrive/test/longitudinal_maneuvers/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
out/*
|
||||
0
selfdrive/test/longitudinal_maneuvers/__init__.py
Normal file
0
selfdrive/test/longitudinal_maneuvers/__init__.py
Normal file
71
selfdrive/test/longitudinal_maneuvers/maneuver.py
Normal file
71
selfdrive/test/longitudinal_maneuvers/maneuver.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import numpy as np
|
||||
from openpilot.selfdrive.test.longitudinal_maneuvers.plant import Plant
|
||||
|
||||
|
||||
class Maneuver:
|
||||
def __init__(self, title, duration, **kwargs):
|
||||
# Was tempted to make a builder class
|
||||
self.distance_lead = kwargs.get("initial_distance_lead", 200.0)
|
||||
self.speed = kwargs.get("initial_speed", 0.0)
|
||||
self.lead_relevancy = kwargs.get("lead_relevancy", 0)
|
||||
|
||||
self.breakpoints = kwargs.get("breakpoints", [0.0, duration])
|
||||
self.speed_lead_values = kwargs.get("speed_lead_values", [0.0 for i in range(len(self.breakpoints))])
|
||||
self.prob_lead_values = kwargs.get("prob_lead_values", [1.0 for i in range(len(self.breakpoints))])
|
||||
self.cruise_values = kwargs.get("cruise_values", [50.0 for i in range(len(self.breakpoints))])
|
||||
|
||||
self.only_lead2 = kwargs.get("only_lead2", False)
|
||||
self.only_radar = kwargs.get("only_radar", False)
|
||||
self.ensure_start = kwargs.get("ensure_start", False)
|
||||
self.enabled = kwargs.get("enabled", True)
|
||||
self.e2e = kwargs.get("e2e", False)
|
||||
self.force_decel = kwargs.get("force_decel", False)
|
||||
|
||||
self.duration = duration
|
||||
self.title = title
|
||||
|
||||
def evaluate(self):
|
||||
plant = Plant(
|
||||
lead_relevancy=self.lead_relevancy,
|
||||
speed=self.speed,
|
||||
distance_lead=self.distance_lead,
|
||||
enabled=self.enabled,
|
||||
only_lead2=self.only_lead2,
|
||||
only_radar=self.only_radar,
|
||||
e2e=self.e2e,
|
||||
force_decel=self.force_decel,
|
||||
)
|
||||
|
||||
valid = True
|
||||
logs = []
|
||||
while plant.current_time < self.duration:
|
||||
speed_lead = np.interp(plant.current_time, self.breakpoints, self.speed_lead_values)
|
||||
prob = np.interp(plant.current_time, self.breakpoints, self.prob_lead_values)
|
||||
cruise = np.interp(plant.current_time, self.breakpoints, self.cruise_values)
|
||||
log = plant.step(speed_lead, prob, cruise)
|
||||
|
||||
d_rel = log['distance_lead'] - log['distance'] if self.lead_relevancy else 200.
|
||||
v_rel = speed_lead - log['speed'] if self.lead_relevancy else 0.
|
||||
log['d_rel'] = d_rel
|
||||
log['v_rel'] = v_rel
|
||||
logs.append(np.array([plant.current_time,
|
||||
log['distance'],
|
||||
log['distance_lead'],
|
||||
log['speed'],
|
||||
speed_lead,
|
||||
log['acceleration']]))
|
||||
|
||||
if d_rel < .4 and (self.only_radar or prob > 0.5):
|
||||
print("Crashed!!!!")
|
||||
valid = False
|
||||
|
||||
if self.ensure_start and log['v_rel'] > 0 and log['speeds'][-1] <= 0.1:
|
||||
print('LongitudinalPlanner not starting!')
|
||||
valid = False
|
||||
if self.force_decel and log['speed'] > 1e-1 and log['acceleration'] > -0.04:
|
||||
print('Not stopping with force decel')
|
||||
valid = False
|
||||
|
||||
|
||||
print("maneuver end", valid)
|
||||
return valid, np.array(logs)
|
||||
172
selfdrive/test/longitudinal_maneuvers/plant.py
Normal file
172
selfdrive/test/longitudinal_maneuvers/plant.py
Normal file
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
import numpy as np
|
||||
|
||||
from cereal import log
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.common.realtime import Ratekeeper, DT_MDL
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
|
||||
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner
|
||||
from openpilot.selfdrive.controls.radard import _LEAD_ACCEL_TAU
|
||||
|
||||
|
||||
class Plant:
|
||||
messaging_initialized = False
|
||||
|
||||
def __init__(self, lead_relevancy=False, speed=0.0, distance_lead=2.0,
|
||||
enabled=True, only_lead2=False, only_radar=False, e2e=False, force_decel=False):
|
||||
self.rate = 1. / DT_MDL
|
||||
|
||||
if not Plant.messaging_initialized:
|
||||
Plant.radar = messaging.pub_sock('radarState')
|
||||
Plant.controls_state = messaging.pub_sock('controlsState')
|
||||
Plant.car_state = messaging.pub_sock('carState')
|
||||
Plant.plan = messaging.sub_sock('longitudinalPlan')
|
||||
Plant.messaging_initialized = True
|
||||
|
||||
self.v_lead_prev = 0.0
|
||||
|
||||
self.distance = 0.
|
||||
self.speed = speed
|
||||
self.acceleration = 0.0
|
||||
self.speeds = []
|
||||
|
||||
# lead car
|
||||
self.lead_relevancy = lead_relevancy
|
||||
self.distance_lead = distance_lead
|
||||
self.enabled = enabled
|
||||
self.only_lead2 = only_lead2
|
||||
self.only_radar = only_radar
|
||||
self.e2e = e2e
|
||||
self.force_decel = force_decel
|
||||
|
||||
self.rk = Ratekeeper(self.rate, print_delay_threshold=100.0)
|
||||
self.ts = 1. / self.rate
|
||||
time.sleep(0.1)
|
||||
self.sm = messaging.SubMaster(['longitudinalPlan'])
|
||||
|
||||
from openpilot.selfdrive.car.honda.values import CAR
|
||||
from openpilot.selfdrive.car.honda.interface import CarInterface
|
||||
|
||||
self.planner = LongitudinalPlanner(CarInterface.get_non_essential_params(CAR.CIVIC), init_v=self.speed)
|
||||
|
||||
@property
|
||||
def current_time(self):
|
||||
return float(self.rk.frame) / self.rate
|
||||
|
||||
def step(self, v_lead=0.0, prob=1.0, v_cruise=50.):
|
||||
# ******** publish a fake model going straight and fake calibration ********
|
||||
# note that this is worst case for MPC, since model will delay long mpc by one time step
|
||||
radar = messaging.new_message('radarState')
|
||||
control = messaging.new_message('controlsState')
|
||||
car_state = messaging.new_message('carState')
|
||||
model = messaging.new_message('modelV2')
|
||||
a_lead = (v_lead - self.v_lead_prev)/self.ts
|
||||
self.v_lead_prev = v_lead
|
||||
|
||||
if self.lead_relevancy:
|
||||
d_rel = np.maximum(0., self.distance_lead - self.distance)
|
||||
v_rel = v_lead - self.speed
|
||||
if self.only_radar:
|
||||
status = True
|
||||
elif prob > .5:
|
||||
status = True
|
||||
else:
|
||||
status = False
|
||||
else:
|
||||
d_rel = 200.
|
||||
v_rel = 0.
|
||||
prob = 0.0
|
||||
status = False
|
||||
|
||||
lead = log.RadarState.LeadData.new_message()
|
||||
lead.dRel = float(d_rel)
|
||||
lead.yRel = float(0.0)
|
||||
lead.vRel = float(v_rel)
|
||||
lead.aRel = float(a_lead - self.acceleration)
|
||||
lead.vLead = float(v_lead)
|
||||
lead.vLeadK = float(v_lead)
|
||||
lead.aLeadK = float(a_lead)
|
||||
# TODO use real radard logic for this
|
||||
lead.aLeadTau = float(_LEAD_ACCEL_TAU)
|
||||
lead.status = status
|
||||
lead.modelProb = float(prob)
|
||||
if not self.only_lead2:
|
||||
radar.radarState.leadOne = lead
|
||||
radar.radarState.leadTwo = lead
|
||||
|
||||
# Simulate model predicting slightly faster speed
|
||||
# this is to ensure lead policy is effective when model
|
||||
# does not predict slowdown in e2e mode
|
||||
position = log.XYZTData.new_message()
|
||||
position.x = [float(x) for x in (self.speed + 0.5) * np.array(ModelConstants.T_IDXS)]
|
||||
model.modelV2.position = position
|
||||
velocity = log.XYZTData.new_message()
|
||||
velocity.x = [float(x) for x in (self.speed + 0.5) * np.ones_like(ModelConstants.T_IDXS)]
|
||||
model.modelV2.velocity = velocity
|
||||
acceleration = log.XYZTData.new_message()
|
||||
acceleration.x = [float(x) for x in np.zeros_like(ModelConstants.T_IDXS)]
|
||||
model.modelV2.acceleration = acceleration
|
||||
|
||||
control.controlsState.longControlState = LongCtrlState.pid if self.enabled else LongCtrlState.off
|
||||
control.controlsState.vCruise = float(v_cruise * 3.6)
|
||||
control.controlsState.experimentalMode = self.e2e
|
||||
control.controlsState.forceDecel = self.force_decel
|
||||
car_state.carState.vEgo = float(self.speed)
|
||||
car_state.carState.standstill = self.speed < 0.01
|
||||
|
||||
# ******** get controlsState messages for plotting ***
|
||||
sm = {'radarState': radar.radarState,
|
||||
'carState': car_state.carState,
|
||||
'controlsState': control.controlsState,
|
||||
'modelV2': model.modelV2}
|
||||
self.planner.update(sm)
|
||||
self.speed = self.planner.v_desired_filter.x
|
||||
self.acceleration = self.planner.a_desired
|
||||
self.speeds = self.planner.v_desired_trajectory.tolist()
|
||||
fcw = self.planner.fcw
|
||||
self.distance_lead = self.distance_lead + v_lead * self.ts
|
||||
|
||||
# ******** run the car ********
|
||||
#print(self.distance, speed)
|
||||
if self.speed <= 0:
|
||||
self.speed = 0
|
||||
self.acceleration = 0
|
||||
self.distance = self.distance + self.speed * self.ts
|
||||
|
||||
# *** radar model ***
|
||||
if self.lead_relevancy:
|
||||
d_rel = np.maximum(0., self.distance_lead - self.distance)
|
||||
v_rel = v_lead - self.speed
|
||||
else:
|
||||
d_rel = 200.
|
||||
v_rel = 0.
|
||||
|
||||
# print at 5hz
|
||||
# if (self.rk.frame % (self.rate // 5)) == 0:
|
||||
# print("%2.2f sec %6.2f m %6.2f m/s %6.2f m/s2 lead_rel: %6.2f m %6.2f m/s"
|
||||
# % (self.current_time, self.distance, self.speed, self.acceleration, d_rel, v_rel))
|
||||
|
||||
|
||||
# ******** update prevs ********
|
||||
self.rk.monitor_time()
|
||||
|
||||
return {
|
||||
"distance": self.distance,
|
||||
"speed": self.speed,
|
||||
"acceleration": self.acceleration,
|
||||
"speeds": self.speeds,
|
||||
"distance_lead": self.distance_lead,
|
||||
"fcw": fcw,
|
||||
}
|
||||
|
||||
# simple engage in standalone mode
|
||||
def plant_thread():
|
||||
plant = Plant()
|
||||
while 1:
|
||||
plant.step()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
plant_thread()
|
||||
160
selfdrive/test/longitudinal_maneuvers/test_longitudinal.py
Normal file
160
selfdrive/test/longitudinal_maneuvers/test_longitudinal.py
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
import itertools
|
||||
import unittest
|
||||
from parameterized import parameterized_class
|
||||
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE
|
||||
from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
|
||||
|
||||
|
||||
# TODO: make new FCW tests
|
||||
def create_maneuvers(kwargs):
|
||||
maneuvers = [
|
||||
Maneuver(
|
||||
'approach stopped car at 25m/s, initial distance: 120m',
|
||||
duration=20.,
|
||||
initial_speed=25.,
|
||||
lead_relevancy=True,
|
||||
initial_distance_lead=120.,
|
||||
speed_lead_values=[30., 0.],
|
||||
breakpoints=[0., 1.],
|
||||
**kwargs,
|
||||
),
|
||||
Maneuver(
|
||||
'approach stopped car at 20m/s, initial distance 90m',
|
||||
duration=20.,
|
||||
initial_speed=20.,
|
||||
lead_relevancy=True,
|
||||
initial_distance_lead=90.,
|
||||
speed_lead_values=[20., 0.],
|
||||
breakpoints=[0., 1.],
|
||||
**kwargs,
|
||||
),
|
||||
Maneuver(
|
||||
'steady state following a car at 20m/s, then lead decel to 0mph at 1m/s^2',
|
||||
duration=50.,
|
||||
initial_speed=20.,
|
||||
lead_relevancy=True,
|
||||
initial_distance_lead=35.,
|
||||
speed_lead_values=[20., 20., 0.],
|
||||
breakpoints=[0., 15., 35.0],
|
||||
**kwargs,
|
||||
),
|
||||
Maneuver(
|
||||
'steady state following a car at 20m/s, then lead decel to 0mph at 2m/s^2',
|
||||
duration=50.,
|
||||
initial_speed=20.,
|
||||
lead_relevancy=True,
|
||||
initial_distance_lead=35.,
|
||||
speed_lead_values=[20., 20., 0.],
|
||||
breakpoints=[0., 15., 25.0],
|
||||
**kwargs,
|
||||
),
|
||||
Maneuver(
|
||||
'steady state following a car at 20m/s, then lead decel to 0mph at 3m/s^2',
|
||||
duration=50.,
|
||||
initial_speed=20.,
|
||||
lead_relevancy=True,
|
||||
initial_distance_lead=35.,
|
||||
speed_lead_values=[20., 20., 0.],
|
||||
breakpoints=[0., 15., 21.66],
|
||||
**kwargs,
|
||||
),
|
||||
Maneuver(
|
||||
'steady state following a car at 20m/s, then lead decel to 0mph at 3+m/s^2',
|
||||
duration=40.,
|
||||
initial_speed=20.,
|
||||
lead_relevancy=True,
|
||||
initial_distance_lead=35.,
|
||||
speed_lead_values=[20., 20., 0.],
|
||||
prob_lead_values=[0., 1., 1.],
|
||||
cruise_values=[20., 20., 20.],
|
||||
breakpoints=[2., 2.01, 8.8],
|
||||
**kwargs,
|
||||
),
|
||||
Maneuver(
|
||||
"approach stopped car at 20m/s, with prob_lead_values",
|
||||
duration=30.,
|
||||
initial_speed=20.,
|
||||
lead_relevancy=True,
|
||||
initial_distance_lead=120.,
|
||||
speed_lead_values=[0.0, 0., 0.],
|
||||
prob_lead_values=[0.0, 0., 1.],
|
||||
cruise_values=[20., 20., 20.],
|
||||
breakpoints=[0.0, 2., 2.01],
|
||||
**kwargs,
|
||||
),
|
||||
Maneuver(
|
||||
"approach slower cut-in car at 20m/s",
|
||||
duration=20.,
|
||||
initial_speed=20.,
|
||||
lead_relevancy=True,
|
||||
initial_distance_lead=50.,
|
||||
speed_lead_values=[15., 15.],
|
||||
breakpoints=[1., 11.],
|
||||
only_lead2=True,
|
||||
**kwargs,
|
||||
),
|
||||
Maneuver(
|
||||
"stay stopped behind radar override lead",
|
||||
duration=20.,
|
||||
initial_speed=0.,
|
||||
lead_relevancy=True,
|
||||
initial_distance_lead=10.,
|
||||
speed_lead_values=[0., 0.],
|
||||
prob_lead_values=[0., 0.],
|
||||
breakpoints=[1., 11.],
|
||||
only_radar=True,
|
||||
**kwargs,
|
||||
),
|
||||
Maneuver(
|
||||
"NaN recovery",
|
||||
duration=30.,
|
||||
initial_speed=15.,
|
||||
lead_relevancy=True,
|
||||
initial_distance_lead=60.,
|
||||
speed_lead_values=[0., 0., 0.0],
|
||||
breakpoints=[1., 1.01, 11.],
|
||||
cruise_values=[float("nan"), 15., 15.],
|
||||
**kwargs,
|
||||
),
|
||||
Maneuver(
|
||||
'cruising at 25 m/s while disabled',
|
||||
duration=20.,
|
||||
initial_speed=25.,
|
||||
lead_relevancy=False,
|
||||
enabled=False,
|
||||
**kwargs,
|
||||
),
|
||||
]
|
||||
if not kwargs['force_decel']:
|
||||
# controls relies on planner commanding to move for stock-ACC resume spamming
|
||||
maneuvers.append(Maneuver(
|
||||
"resume from a stop",
|
||||
duration=20.,
|
||||
initial_speed=0.,
|
||||
lead_relevancy=True,
|
||||
initial_distance_lead=STOP_DISTANCE,
|
||||
speed_lead_values=[0., 0., 2.],
|
||||
breakpoints=[1., 10., 15.],
|
||||
ensure_start=True,
|
||||
**kwargs,
|
||||
))
|
||||
return maneuvers
|
||||
|
||||
|
||||
@parameterized_class(("e2e", "force_decel"), itertools.product([True, False], repeat=2))
|
||||
class LongitudinalControl(unittest.TestCase):
|
||||
e2e: bool
|
||||
force_decel: bool
|
||||
|
||||
def test_maneuver(self):
|
||||
for maneuver in create_maneuvers({"e2e": self.e2e, "force_decel": self.force_decel}):
|
||||
with self.subTest(title=maneuver.title, e2e=maneuver.e2e, force_decel=maneuver.force_decel):
|
||||
print(maneuver.title, f'in {"e2e" if maneuver.e2e else "acc"} mode')
|
||||
valid, _ = maneuver.evaluate()
|
||||
self.assertTrue(valid)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(failfast=True)
|
||||
Reference in New Issue
Block a user