mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
79c8945a41
Integrate torque feedback, lane-change smoothing and low-speed Turn Assist through dedicated lateral owners. Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com> Co-authored-by: pharmacomaniac <blittle65@gmail.com>
129 lines
6.1 KiB
Python
129 lines
6.1 KiB
Python
"""Explicit torque values exercised against the current native controller."""
|
|
|
|
import math
|
|
import unittest
|
|
|
|
from openpilot.cereal import log
|
|
from opendbc.car.structs import car
|
|
from opendbc.car.car_helpers import interfaces
|
|
from opendbc.car.toyota.values import CAR as TOYOTA
|
|
from opendbc.car.vehicle_model import VehicleModel
|
|
from openpilot.common.realtime import DT_CTRL
|
|
from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
|
|
from openpilot.starpilot.lateral.torque_tuning import TorqueSource, TorqueTuning, from_record
|
|
|
|
|
|
PLATFORM = TOYOTA.TOYOTA_RAV4_TSS2
|
|
|
|
|
|
def native_controller():
|
|
interface = interfaces[PLATFORM]
|
|
cp = interface.get_non_essential_params(PLATFORM)
|
|
controller = LatControlTorque(cp.as_reader(), interface(cp), DT_CTRL)
|
|
return cp, controller, VehicleModel(cp)
|
|
|
|
|
|
def record(cp, source="vehicle"):
|
|
tune = cp.lateralTuning.torque
|
|
return {"source": source, "vehicle": cp.carFingerprint, "latAccelFactor": tune.latAccelFactor,
|
|
"latAccelOffset": tune.latAccelOffset, "friction": tune.friction}
|
|
|
|
|
|
def frame(controller, vm, *, active=True, curvature=0.0, speed=20.0, steering_angle=0.0,
|
|
steer_limited=False, steering_pressed=False):
|
|
cs = car.CarState.new_message()
|
|
cs.vEgo = speed
|
|
cs.steeringAngleDeg = steering_angle
|
|
cs.steeringPressed = steering_pressed
|
|
return controller.update(active, cs, vm, log.VehicleParameters.new_message(),
|
|
steer_limited, curvature, False, 0.2)
|
|
|
|
|
|
class TestTorqueTuning(unittest.TestCase):
|
|
def test_stock_values_leave_native_limits_unchanged(self):
|
|
cp, controller, _ = native_controller()
|
|
stock = from_record(record(cp))
|
|
self.assertIs(stock.source, TorqueSource.VEHICLE)
|
|
self.assertEqual(stock.vehicle, cp.carFingerprint)
|
|
for selected, actual in zip(stock.upstream_update(), (controller.torque_params.latAccelFactor,
|
|
controller.torque_params.latAccelOffset,
|
|
controller.torque_params.friction), strict=True):
|
|
self.assertAlmostEqual(selected, actual)
|
|
original_limits = controller.pid.neg_limit, controller.pid.pos_limit
|
|
controller.update_torque_parameters(*stock.upstream_update())
|
|
for selected, actual in zip((controller.pid.neg_limit, controller.pid.pos_limit), original_limits, strict=True):
|
|
self.assertAlmostEqual(selected, actual)
|
|
|
|
def test_selected_complete_values_change_native_parameters_and_limits(self):
|
|
cp, controller, _ = native_controller()
|
|
selected = record(cp, "user")
|
|
selected["latAccelFactor"] *= 1.2
|
|
selected["latAccelOffset"] = 0.07
|
|
selected["friction"] += 0.02
|
|
tuning = from_record(selected)
|
|
self.assertIs(tuning.source, TorqueSource.USER)
|
|
initial_limit = controller.pid.pos_limit
|
|
controller.update_torque_parameters(*tuning.upstream_update())
|
|
self.assertAlmostEqual(controller.pid.pos_limit, initial_limit * 1.2, delta=1e-6)
|
|
self.assertAlmostEqual(controller.pid.neg_limit, -controller.pid.pos_limit)
|
|
self.assertAlmostEqual(controller.torque_params.latAccelOffset, 0.07)
|
|
self.assertAlmostEqual(controller.torque_params.friction, selected["friction"])
|
|
|
|
def test_inactive_nonzero_command_and_reengagement_use_native_buffer(self):
|
|
cp, controller, vm = native_controller()
|
|
learned = record(cp, "learned")
|
|
learned["latAccelFactor"] *= 1.05
|
|
controller.update_torque_parameters(*from_record(learned).upstream_update())
|
|
for _ in range(controller.lat_accel_request_buffer_len):
|
|
output, _, state = frame(controller, vm, active=False, curvature=0.001)
|
|
self.assertEqual(output, 0.0)
|
|
self.assertFalse(state.active)
|
|
self.assertTrue(all(math.isclose(value, 0.4, abs_tol=1e-6) for value in controller.lat_accel_request_buffer))
|
|
output, _, state = frame(controller, vm, curvature=0.001)
|
|
self.assertTrue(state.active)
|
|
self.assertAlmostEqual(state.desiredLateralAccel, 0.4)
|
|
self.assertTrue(math.isfinite(output))
|
|
self.assertNotEqual(output, 0.0)
|
|
|
|
def test_native_saturation_sets_then_clears_under_bounded_inputs(self):
|
|
_, controller, vm = native_controller()
|
|
for _ in range(100):
|
|
output, _, state = frame(controller, vm, curvature=0.02)
|
|
self.assertLessEqual(abs(output), controller.steer_max + 1e-6)
|
|
self.assertTrue(state.saturated)
|
|
self.assertAlmostEqual(controller.sat_time, controller.sat_limit)
|
|
for _ in range(controller.lat_accel_request_buffer_len + 100):
|
|
output, _, state = frame(controller, vm, curvature=0.0)
|
|
self.assertLessEqual(abs(output), controller.steer_max + 1e-6)
|
|
self.assertFalse(state.saturated)
|
|
self.assertAlmostEqual(controller.sat_time, 0.0)
|
|
|
|
def test_invalid_numeric_inputs_are_rejected(self):
|
|
values = [("latAccelFactor", 0), ("latAccelFactor", -1), ("latAccelFactor", float("nan")),
|
|
("latAccelFactor", True), ("latAccelOffset", float("inf")), ("latAccelOffset", False),
|
|
("friction", -0.01), ("friction", float("nan")), ("friction", True), ("latAccelFactor", 10 ** 1000)]
|
|
for field, value in values:
|
|
with self.subTest(field=field, value=value):
|
|
source = {"source": "vehicle", "vehicle": "test-platform", "latAccelFactor": 2.0,
|
|
"latAccelOffset": 0.0, "friction": 0.1}
|
|
source[field] = value
|
|
with self.assertRaises(ValueError):
|
|
from_record(source)
|
|
|
|
def test_incomplete_unknown_and_legacy_fields_are_rejected(self):
|
|
source = {"source": "vehicle", "vehicle": "test-platform", "latAccelFactor": 2.0,
|
|
"latAccelOffset": 0.0, "friction": 0.1}
|
|
for extra in ("kp", "ki", "kd", "kf", "steeringAngleDeadzoneDeg", "unknown"):
|
|
with self.subTest(extra=extra), self.assertRaises(ValueError):
|
|
from_record(source | {extra: 1.0})
|
|
with self.assertRaises(ValueError):
|
|
from_record({key: value for key, value in source.items() if key != "friction"})
|
|
with self.assertRaises(ValueError):
|
|
from_record(source | {"source": "unrecognized"})
|
|
with self.assertRaises(ValueError):
|
|
TorqueTuning(TorqueSource.VEHICLE, "", 2.0, 0.0, 0.1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|