mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-30 19:33:45 +08:00
c51b96879a
Move the fork-specific diagnostics out of the stock CarOutput schema and into StarPilot reserved messaging. Preserve the legacy saturation fallback when custom diagnostics are unavailable or stale. Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
167 lines
5.6 KiB
Python
167 lines
5.6 KiB
Python
import math
|
|
|
|
import cereal.messaging as messaging
|
|
import pytest
|
|
|
|
from cereal import car
|
|
from opendbc.car.tesla.interface import CarInterface
|
|
from opendbc.car.tesla.values import CAR, TeslaSafetyFlags
|
|
from openpilot.selfdrive.controls.lib.steering_saturation import is_angle_steering_limited
|
|
|
|
|
|
REQUESTED_ANGLE = -14.5
|
|
OUTPUT_ANGLE = -9.0
|
|
SAMPLE_TIME_NANOS = 1_000_000_000
|
|
FRESH_TIME_NANOS = SAMPLE_TIME_NANOS + 20_000_000
|
|
MAX_DIAGNOSTIC_AGE_NANOS = 100_000_000
|
|
NEXT_FLOAT32_AFTER_2_5 = 2.500000238418579
|
|
ERROR_FIELDS = (
|
|
"modelLimitErrorDeg",
|
|
"resumeLimitErrorDeg",
|
|
"cooperativeLimitErrorDeg",
|
|
"combinedLimitErrorDeg",
|
|
)
|
|
NUMERIC_FIELDS = ERROR_FIELDS + ("cooperativeOffsetDeg",)
|
|
|
|
|
|
def make_case(candidate=CAR.TESLA_MODEL_3, cooperative_enabled=True):
|
|
cp = CarInterface.get_non_essential_params(candidate)
|
|
for safety_config in cp.safetyConfigs:
|
|
if cooperative_enabled:
|
|
safety_config.safetyParam |= TeslaSafetyFlags.COOP_STEERING.value
|
|
else:
|
|
safety_config.safetyParam &= ~TeslaSafetyFlags.COOP_STEERING.value
|
|
|
|
output = car.CarOutput.new_message()
|
|
output.actuatorsOutput.steeringAngleDeg = OUTPUT_ANGLE
|
|
|
|
diagnostics = messaging.new_message("starpilotCarControl", valid=True)
|
|
info = diagnostics.starpilotCarControl.steeringLimitInfo
|
|
info.valid = True
|
|
info.monoTime = SAMPLE_TIME_NANOS
|
|
info.cooperativeOffsetDeg = 5.5
|
|
return cp, output, diagnostics
|
|
|
|
|
|
def detect(cp, output, diagnostics, requested_angle=REQUESTED_ANGLE,
|
|
output_healthy=True, now_nanos=FRESH_TIME_NANOS):
|
|
return is_angle_steering_limited(
|
|
cp.as_reader(), requested_angle, output.as_reader(), diagnostics.as_reader(), output_healthy, now_nanos,
|
|
)
|
|
|
|
|
|
def test_light_offset_does_not_count_as_limiting():
|
|
cp, output, diagnostics = make_case()
|
|
|
|
assert not detect(cp, output, diagnostics)
|
|
|
|
|
|
@pytest.mark.parametrize("field", ERROR_FIELDS)
|
|
def test_each_genuine_limit_is_visible_during_cooperation(field):
|
|
cp, output, diagnostics = make_case()
|
|
setattr(diagnostics.starpilotCarControl.steeringLimitInfo, field, 3.0)
|
|
|
|
assert detect(cp, output, diagnostics)
|
|
|
|
|
|
def test_combined_small_limits_remain_visible():
|
|
cp, output, diagnostics = make_case()
|
|
info = diagnostics.starpilotCarControl.steeringLimitInfo
|
|
info.modelLimitErrorDeg = 1.5
|
|
info.cooperativeLimitErrorDeg = 1.5
|
|
info.combinedLimitErrorDeg = 3.0
|
|
|
|
assert detect(cp, output, diagnostics)
|
|
|
|
|
|
@pytest.mark.parametrize(("error", "expected"), (
|
|
(2.5, False),
|
|
(NEXT_FLOAT32_AFTER_2_5, True),
|
|
))
|
|
def test_diagnostic_threshold_is_strictly_greater(error, expected):
|
|
cp, output, diagnostics = make_case()
|
|
diagnostics.starpilotCarControl.steeringLimitInfo.modelLimitErrorDeg = error
|
|
|
|
assert detect(cp, output, diagnostics) is expected
|
|
|
|
|
|
@pytest.mark.parametrize("age_nanos", (0, MAX_DIAGNOSTIC_AGE_NANOS))
|
|
def test_diagnostic_age_bounds_are_inclusive(age_nanos):
|
|
cp, output, diagnostics = make_case()
|
|
|
|
assert not detect(cp, output, diagnostics, now_nanos=SAMPLE_TIME_NANOS + age_nanos)
|
|
|
|
|
|
def test_signed_cooperative_offset_is_valid_data():
|
|
cp, output, diagnostics = make_case()
|
|
diagnostics.starpilotCarControl.steeringLimitInfo.cooperativeOffsetDeg = -5.5
|
|
|
|
assert not detect(cp, output, diagnostics)
|
|
|
|
|
|
@pytest.mark.parametrize("scenario", (
|
|
"default_message",
|
|
"invalid_flag",
|
|
"zero_timestamp",
|
|
"future_timestamp",
|
|
"stale_timestamp",
|
|
"unhealthy_output",
|
|
"unsupported_model",
|
|
"cooperative_mode_disabled",
|
|
))
|
|
def test_unusable_diagnostics_keep_legacy_warning(scenario):
|
|
cp, output, diagnostics = make_case()
|
|
output_healthy = True
|
|
now_nanos = FRESH_TIME_NANOS
|
|
|
|
if scenario == "default_message":
|
|
diagnostics = messaging.new_message("starpilotCarControl")
|
|
elif scenario == "invalid_flag":
|
|
diagnostics.starpilotCarControl.steeringLimitInfo.valid = False
|
|
elif scenario == "zero_timestamp":
|
|
diagnostics.starpilotCarControl.steeringLimitInfo.monoTime = 0
|
|
elif scenario == "future_timestamp":
|
|
diagnostics.starpilotCarControl.steeringLimitInfo.monoTime = now_nanos + 1
|
|
elif scenario == "stale_timestamp":
|
|
diagnostics.starpilotCarControl.steeringLimitInfo.monoTime = now_nanos - MAX_DIAGNOSTIC_AGE_NANOS - 1
|
|
elif scenario == "unhealthy_output":
|
|
output_healthy = False
|
|
elif scenario == "unsupported_model":
|
|
cp, output, diagnostics = make_case(CAR.TESLA_MODEL_Y)
|
|
elif scenario == "cooperative_mode_disabled":
|
|
cp, output, diagnostics = make_case(cooperative_enabled=False)
|
|
|
|
assert detect(cp, output, diagnostics, output_healthy=output_healthy, now_nanos=now_nanos)
|
|
|
|
|
|
@pytest.mark.parametrize(("requested_angle", "expected"), (
|
|
(OUTPUT_ANGLE - 2.5, False),
|
|
(OUTPUT_ANGLE - NEXT_FLOAT32_AFTER_2_5, True),
|
|
))
|
|
def test_fallback_preserves_legacy_strict_threshold(requested_angle, expected):
|
|
cp, output, diagnostics = make_case()
|
|
diagnostics.starpilotCarControl.steeringLimitInfo.valid = False
|
|
|
|
assert detect(cp, output, diagnostics, requested_angle=requested_angle) is expected
|
|
|
|
|
|
@pytest.mark.parametrize("field", NUMERIC_FIELDS)
|
|
@pytest.mark.parametrize(("bad_value", "requested_angle", "legacy_result"), (
|
|
(math.nan, REQUESTED_ANGLE, True),
|
|
(math.inf, OUTPUT_ANGLE, False),
|
|
(-math.inf, OUTPUT_ANGLE, False),
|
|
))
|
|
def test_nonfinite_diagnostic_values_use_legacy_fallback(field, bad_value, requested_angle, legacy_result):
|
|
cp, output, diagnostics = make_case()
|
|
setattr(diagnostics.starpilotCarControl.steeringLimitInfo, field, bad_value)
|
|
|
|
assert detect(cp, output, diagnostics, requested_angle=requested_angle) is legacy_result
|
|
|
|
|
|
@pytest.mark.parametrize("field", ERROR_FIELDS)
|
|
def test_negative_error_values_use_legacy_fallback(field):
|
|
cp, output, diagnostics = make_case()
|
|
setattr(diagnostics.starpilotCarControl.steeringLimitInfo, field, -0.1)
|
|
|
|
assert detect(cp, output, diagnostics)
|