mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
Complete Ford release long manual and multi-Panda support
This commit is contained in:
@@ -1,18 +1,43 @@
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.ford.values import CAR, FordFlags
|
||||
from opendbc.car.ford.values import CAR, FordFlags, FordSafetyFlags
|
||||
|
||||
AOL_EXPERIENCE = 32
|
||||
CLASSIC = frozenset((CAR.FORD_BRONCO_SPORT_MK1, CAR.FORD_ESCAPE_MK4, CAR.FORD_FOCUS_MK4,
|
||||
CAR.FORD_MAVERICK_MK1, CAR.FORD_EXPLORER_MK6))
|
||||
GENERIC_CANFD = frozenset((CAR.FORD_ESCAPE_MK4_5, CAR.FORD_EXPEDITION_MK4, CAR.FORD_F_150_MK14,
|
||||
CAR.FORD_F_150_LIGHTNING_MK1, CAR.FORD_RANGER_MK2))
|
||||
AOL_WORDS = frozenset((8, 9, 10, 11, 12, 13, 18, 19, 32, 33, 66, 67))
|
||||
MANUAL = int(FordSafetyFlags.MANUAL)
|
||||
BASE_WORDS = frozenset((8, 9, 10, 11, 12, 13, 18, 19, 32, 33, 66, 67))
|
||||
AOL_WORDS = BASE_WORDS | frozenset(word | MANUAL for word in BASE_WORDS)
|
||||
|
||||
|
||||
def aol_safety_config(cp):
|
||||
if len(cp.safetyConfigs) not in (1, 2):
|
||||
return None
|
||||
if len(cp.safetyConfigs) == 2:
|
||||
passive = cp.safetyConfigs[0]
|
||||
if passive.safetyModel != structs.CarParams.SafetyModel.noOutput or passive.safetyParam != 0:
|
||||
return None
|
||||
owner = cp.safetyConfigs[-1]
|
||||
return owner if owner.safetyModel == structs.CarParams.SafetyModel.ford else None
|
||||
|
||||
|
||||
def base_safety_param(cp):
|
||||
owner = aol_safety_config(cp)
|
||||
if owner is None:
|
||||
return -1
|
||||
manual = cp.transmissionType == structs.CarParams.TransmissionType.manual
|
||||
if cp.transmissionType not in (structs.CarParams.TransmissionType.automatic, structs.CarParams.TransmissionType.manual):
|
||||
return -1
|
||||
word = int(owner.safetyParam)
|
||||
if (word & MANUAL and not manual) or (manual and cp.alternativeExperience == AOL_EXPERIENCE and not word & MANUAL):
|
||||
return -1
|
||||
return word & ~MANUAL
|
||||
|
||||
|
||||
def qualified(cp, *, marked_only=False):
|
||||
if (cp.brand != 'ford' or cp.carFingerprint not in tuple(CAR) or cp.passive or cp.notCar or cp.dashcamOnly or
|
||||
cp.steerControlType != structs.CarParams.SteerControlType.angle or
|
||||
cp.transmissionType != structs.CarParams.TransmissionType.automatic or len(cp.safetyConfigs) != 1 or
|
||||
cp.alternativeExperience not in ((32,) if marked_only else (0, 32))):
|
||||
return False
|
||||
flags = int(cp.flags) & ~int(FordFlags.HAS_BSM)
|
||||
@@ -30,10 +55,9 @@ def qualified(cp, *, marked_only=False):
|
||||
base = 12
|
||||
else:
|
||||
return False
|
||||
safety = cp.safetyConfigs[0]
|
||||
return (cp.pcmCruise and
|
||||
safety.safetyModel == structs.CarParams.SafetyModel.ford and
|
||||
safety.safetyParam == (base | int(cp.openpilotLongitudinalControl)))
|
||||
safety = aol_safety_config(cp)
|
||||
return bool(cp.pcmCruise and safety is not None and
|
||||
base_safety_param(cp) == (base | int(cp.openpilotLongitudinalControl)))
|
||||
|
||||
|
||||
def temporary_restriction(cp, state):
|
||||
|
||||
@@ -215,6 +215,8 @@ class CarState(CarStateBase):
|
||||
gear = ("Gear_Shift_by_Wire_FD1" if CP.flags & FordFlags.CANFD else
|
||||
"TransGearData" if CP.flags & FordFlags.ALT_STEER_ANGLE else "PowertrainData_10")
|
||||
pt_messages.append((gear, 10))
|
||||
if CP.transmissionType == TransmissionType.manual:
|
||||
pt_messages.append(("BCM_Lamp_Stat_FD1", 10))
|
||||
if CP.flags & FordFlags.LKA_STEERING:
|
||||
pt_messages.append(("Lane_Assist_Data3_FD1", 30))
|
||||
cam_messages.append(("LateralMotionControl", 20))
|
||||
|
||||
@@ -6,6 +6,7 @@ CREDITS.md and THIRD_PARTY_NOTICES.md; this module only selects admitted callers
|
||||
"""
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.ford.aol import base_safety_param
|
||||
from opendbc.car.ford.explorer_lateral import ExplorerLateralController, bounded_command as bounded_command, qualified as explorer_qualified
|
||||
from opendbc.car.ford.lateral_strategy import FordLateralController
|
||||
from opendbc.car.ford.values import CAR, FordFlags, FordSafetyFlags
|
||||
@@ -16,8 +17,6 @@ CLASSIC_EXTENDED_CARS = GENERIC_CLASSIC_CARS | {CAR.FORD_EXPLORER_MK6}
|
||||
|
||||
|
||||
def qualified(cp):
|
||||
if cp.alternativeExperience == 32 and len(cp.safetyConfigs) != 1:
|
||||
return False
|
||||
if cp.carFingerprint == CAR.FORD_EXPLORER_MK6:
|
||||
return explorer_qualified(cp)
|
||||
if (cp.brand != "ford" or cp.carFingerprint not in GENERIC_CLASSIC_CARS or
|
||||
@@ -29,7 +28,7 @@ def qualified(cp):
|
||||
return False
|
||||
safety = cp.safetyConfigs[-1]
|
||||
expected = int(FordSafetyFlags.CLASSIC_EXTENDED | (FordSafetyFlags.LONG_CONTROL if cp.openpilotLongitudinalControl else 0))
|
||||
return safety.safetyModel == structs.CarParams.SafetyModel.ford and safety.safetyParam == expected
|
||||
return safety.safetyModel == structs.CarParams.SafetyModel.ford and base_safety_param(cp) == expected
|
||||
|
||||
|
||||
class GenericClassicLateralController(FordLateralController):
|
||||
|
||||
@@ -4,8 +4,8 @@ from opendbc.car.ford.values import CAR, FordFlags
|
||||
|
||||
def normal_bus0_queries():
|
||||
# Verify both configured and effective requests before omitting the OBD sweep.
|
||||
# All essential and as-built requests remain on bus 0. The bus-1 logging
|
||||
# request must be an exact duplicate of a bus-0 query, not unique evidence.
|
||||
# Essential queries use normal bus0 and its exact auxiliary Panda bus4 copy.
|
||||
# As-built remains bus0; bus1 logging cannot provide unique evidence.
|
||||
from opendbc.car import fw_versions
|
||||
configured = fw_versions.FW_QUERY_CONFIGS['ford'].requests
|
||||
effective = [request for brand, _, request in fw_versions.REQUESTS if brand == 'ford']
|
||||
@@ -16,8 +16,17 @@ def normal_bus0_queries():
|
||||
return (tuple(request.request), tuple(request.response), request.rx_offset, frozenset(request.whitelist_ecus))
|
||||
for requests in (configured, effective):
|
||||
normal = {signature(request) for request in requests if request.bus == 0}
|
||||
if not normal or any(request.bus != 0 and (not request.logging or signature(request) not in normal)
|
||||
for request in requests):
|
||||
essential = {signature(request) for request in requests if request.bus == 0 and not request.logging}
|
||||
auxiliary = {signature(request) for request in requests if request.bus == 4 and not request.logging}
|
||||
if not normal or not essential or auxiliary != essential:
|
||||
return False
|
||||
for request in requests:
|
||||
if request.bus == 0:
|
||||
continue
|
||||
if request.bus == 4 and not request.logging and signature(request) in essential:
|
||||
continue
|
||||
if request.bus == 1 and request.logging and signature(request) in normal:
|
||||
continue
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from typing import Protocol
|
||||
import numpy as np
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.ford.aol import base_safety_param
|
||||
from opendbc.car.ford.lateral_strategy import FordLateralController, FordLateralResult, FORD_CURVATURE_LIMITS, STEER_DT
|
||||
from opendbc.car.ford.values import CAR, CarControllerParams, FordFlags, FordSafetyFlags
|
||||
|
||||
@@ -34,8 +35,6 @@ class ExplorerLateralController(FordLateralController):
|
||||
|
||||
|
||||
def qualified(CP) -> bool:
|
||||
if CP.alternativeExperience == 32 and len(CP.safetyConfigs) != 1:
|
||||
return False
|
||||
if (CP.brand != "ford" or CP.carFingerprint != CAR.FORD_EXPLORER_MK6 or
|
||||
CP.flags & ~int(FordFlags.HAS_BSM) or CP.passive or CP.dashcamOnly or CP.notCar or
|
||||
CP.alternativeExperience not in (0, 32) or len(CP.safetyConfigs) not in (1, 2)):
|
||||
@@ -45,7 +44,7 @@ def qualified(CP) -> bool:
|
||||
return False
|
||||
safety = CP.safetyConfigs[-1]
|
||||
expected = int(FordSafetyFlags.EXPLORER_EXTENDED | (FordSafetyFlags.LONG_CONTROL if CP.openpilotLongitudinalControl else 0))
|
||||
return safety.safetyModel == structs.CarParams.SafetyModel.ford and safety.safetyParam == expected
|
||||
return safety.safetyModel == structs.CarParams.SafetyModel.ford and base_safety_param(CP) == expected
|
||||
|
||||
|
||||
class CurvatureOwner(Protocol):
|
||||
|
||||
@@ -4,6 +4,7 @@ The reused BluePilot-derived strategy retains its original attribution in
|
||||
lateral_strategy.py, CREDITS.md and THIRD_PARTY_NOTICES.md.
|
||||
"""
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.ford.aol import base_safety_param
|
||||
from opendbc.car.ford.explorer_lateral import bounded_command as shared_bound
|
||||
from opendbc.car.ford.lateral_strategy import FordLateralController
|
||||
from opendbc.car.ford.values import CAR, FordFlags, FordSafetyFlags
|
||||
@@ -14,8 +15,6 @@ SOURCE_ACCEL_CAP = 3.0 - 9.81 * .06
|
||||
|
||||
|
||||
def qualified(cp):
|
||||
if cp.alternativeExperience == 32 and len(cp.safetyConfigs) != 1:
|
||||
return False
|
||||
if (cp.brand != "ford" or cp.carFingerprint not in GENERIC_CANFD_CARS or
|
||||
not cp.flags & FordFlags.CANFD or cp.flags & ~int(FordFlags.CANFD | FordFlags.HAS_BSM) or
|
||||
cp.passive or cp.dashcamOnly or cp.notCar or cp.alternativeExperience not in (0, 32) or
|
||||
@@ -27,7 +26,7 @@ def qualified(cp):
|
||||
s = cp.safetyConfigs[-1]
|
||||
expected = int(FordSafetyFlags.GENERIC_CANFD_EXTENDED | FordSafetyFlags.CANFD |
|
||||
(FordSafetyFlags.LONG_CONTROL if cp.openpilotLongitudinalControl else 0))
|
||||
return s.safetyModel == structs.CarParams.SafetyModel.ford and s.safetyParam == expected
|
||||
return s.safetyModel == structs.CarParams.SafetyModel.ford and base_safety_param(cp) == expected
|
||||
|
||||
|
||||
class GenericCanfdLateralController(FordLateralController):
|
||||
|
||||
@@ -55,12 +55,12 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
new_port = bool(ret.flags & FordFlags.NEW_PORT)
|
||||
if candidate in GENERIC_CANFD_CARS:
|
||||
ret.alphaLongitudinalAvailable = not is_release
|
||||
ret.alphaLongitudinalAvailable = True
|
||||
ret.steerActuatorDelay = 0.22
|
||||
longitudinal_requested = bool(alpha_long and not is_release)
|
||||
longitudinal_requested = bool(alpha_long)
|
||||
elif candidate == CAR.FORD_MUSTANG_MACH_E_MK1:
|
||||
ret.alphaLongitudinalAvailable = not is_release
|
||||
longitudinal_requested = bool(alpha_long and not is_release)
|
||||
ret.alphaLongitudinalAvailable = True
|
||||
longitudinal_requested = bool(alpha_long)
|
||||
elif candidate in CLASSIC_EXTENDED_CARS:
|
||||
# Original shared classic ownership is explicit stock/OP-long selection.
|
||||
# Actuator delay is live; modern LongControl already supplies the original P=0.
|
||||
@@ -68,7 +68,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.steerActuatorDelay = 0.22
|
||||
longitudinal_requested = bool(alpha_long)
|
||||
else:
|
||||
ret.alphaLongitudinalAvailable = (not is_release if ret.flags & FordFlags.CANFD else True) if new_port else ret.radarUnavailable
|
||||
ret.alphaLongitudinalAvailable = True if new_port else ret.radarUnavailable
|
||||
longitudinal_requested = bool(alpha_long and ret.alphaLongitudinalAvailable) if new_port else alpha_long or not ret.radarUnavailable
|
||||
if longitudinal_requested:
|
||||
ret.safetyConfigs[-1].safetyParam |= FordSafetyFlags.LONG_CONTROL.value
|
||||
@@ -139,11 +139,6 @@ class CarInterface(CarInterfaceBase):
|
||||
and ret.safetyConfigs[-1].safetyParam in (2, 3)
|
||||
)
|
||||
if mach_e_extended:
|
||||
if is_release:
|
||||
# The native CANFD LONG profile is DEBUG-only; RELEASE stays stock.
|
||||
ret.alphaLongitudinalAvailable = False
|
||||
ret.openpilotLongitudinalControl = False
|
||||
ret.safetyConfigs[-1].safetyParam = FordSafetyFlags.CANFD.value
|
||||
ret.safetyConfigs[-1].safetyParam |= FordSafetyFlags.MACH_E_EXTENDED.value
|
||||
if (candidate in GENERIC_CANFD_CARS and ret.flags & FordFlags.CANFD and
|
||||
not ret.flags & ~int(FordFlags.CANFD | FordFlags.HAS_BSM) and
|
||||
|
||||
@@ -17,6 +17,7 @@ from dataclasses import replace
|
||||
import numpy as np
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.ford.aol import base_safety_param
|
||||
from opendbc.car.ford.values import CAR, FordFlags, FordSafetyFlags
|
||||
from opendbc.car.lateral import MAX_LATERAL_JERK
|
||||
from opendbc.car.ford.lateral_strategy import (
|
||||
@@ -99,8 +100,6 @@ class MachELateralController(FordLateralController):
|
||||
|
||||
|
||||
def qualified(CP) -> bool:
|
||||
if CP.alternativeExperience == 32 and len(CP.safetyConfigs) != 1:
|
||||
return False
|
||||
if (CP.brand != "ford" or CP.carFingerprint != CAR.FORD_MUSTANG_MACH_E_MK1 or
|
||||
not CP.flags & FordFlags.CANFD or CP.flags & ~int(FordFlags.CANFD | FordFlags.HAS_BSM) or
|
||||
CP.passive or CP.dashcamOnly or CP.notCar or CP.alternativeExperience not in (0, 32) or
|
||||
@@ -111,7 +110,7 @@ def qualified(CP) -> bool:
|
||||
return False
|
||||
safety = CP.safetyConfigs[-1]
|
||||
return (safety.safetyModel == structs.CarParams.SafetyModel.ford and
|
||||
safety.safetyParam == int(FordSafetyFlags.CANFD | FordSafetyFlags.MACH_E_EXTENDED |
|
||||
base_safety_param(CP) == int(FordSafetyFlags.CANFD | FordSafetyFlags.MACH_E_EXTENDED |
|
||||
(FordSafetyFlags.LONG_CONTROL if CP.openpilotLongitudinalControl else 0)))
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from dataclasses import replace
|
||||
import math
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.ford.aol import base_safety_param
|
||||
from opendbc.car.ford.explorer_lateral import bounded_command as shared_bound
|
||||
from opendbc.car.ford.lateral_strategy import FordLateralController, FordLateralResult
|
||||
from opendbc.car.ford.values import CAR, FordFlags
|
||||
@@ -22,7 +23,7 @@ def qualified(cp):
|
||||
if (profile is None or cp.brand != "ford" or cp.passive or cp.dashcamOnly or cp.notCar or
|
||||
cp.alternativeExperience not in (0, 32) or not cp.pcmCruise or
|
||||
cp.steerControlType != structs.CarParams.SteerControlType.angle or
|
||||
len(cp.safetyConfigs) not in (1, 2) or (cp.alternativeExperience == 32 and len(cp.safetyConfigs) != 1)):
|
||||
len(cp.safetyConfigs) not in (1, 2)):
|
||||
return False
|
||||
flags, word = profile
|
||||
if (int(cp.flags) & ~int(FordFlags.HAS_BSM)) != flags:
|
||||
@@ -31,7 +32,7 @@ def qualified(cp):
|
||||
cp.safetyConfigs[0].safetyParam != 0):
|
||||
return False
|
||||
safety = cp.safetyConfigs[-1]
|
||||
return safety.safetyModel == structs.CarParams.SafetyModel.ford and safety.safetyParam == word + int(cp.openpilotLongitudinalControl)
|
||||
return safety.safetyModel == structs.CarParams.SafetyModel.ford and base_safety_param(cp) == word + int(cp.openpilotLongitudinalControl)
|
||||
|
||||
|
||||
class NewPortCurvatureController(FordLateralController):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Ford physical cancel/resume switch context; no new cruise authority."""
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.ford.aol import base_safety_param
|
||||
from opendbc.car.ford.values import CAR, FordFlags, FordSafetyFlags
|
||||
from opendbc.car.ford.classic_lateral import CLASSIC_EXTENDED_CARS
|
||||
from opendbc.car.ford.generic_canfd_lateral import qualified as generic_canfd_qualified
|
||||
@@ -30,8 +31,6 @@ def qualified(CP) -> bool:
|
||||
from opendbc.bluepilot_lateral.hosts.starpilot import qualified as angle_qualified
|
||||
if angle_qualified(CP):
|
||||
return not CP.openpilotLongitudinalControl
|
||||
if CP.alternativeExperience == 32 and len(CP.safetyConfigs) != 1:
|
||||
return False
|
||||
"""Existing stock profiles with the paired physical-driver button contract."""
|
||||
if (CP.brand != "ford" or CP.carFingerprint not in tuple(CAR) or CP.openpilotLongitudinalControl or CP.alternativeExperience not in (0, 32) or
|
||||
CP.passive or CP.dashcamOnly or CP.notCar or len(CP.safetyConfigs) not in (1, 2) or
|
||||
@@ -41,17 +40,17 @@ def qualified(CP) -> bool:
|
||||
CP.safetyConfigs[0].safetyParam != 0):
|
||||
return False
|
||||
safety = CP.safetyConfigs[-1]
|
||||
if safety.safetyModel != structs.CarParams.SafetyModel.ford or safety.safetyParam not in (2, 8, 10, 12, 18, 32, 66):
|
||||
if safety.safetyModel != structs.CarParams.SafetyModel.ford or base_safety_param(CP) not in (2, 8, 10, 12, 18, 32, 66):
|
||||
return False
|
||||
if safety.safetyModel == structs.CarParams.SafetyModel.ford and safety.safetyParam == 32:
|
||||
if safety.safetyModel == structs.CarParams.SafetyModel.ford and base_safety_param(CP) == 32:
|
||||
return CP.carFingerprint in CLASSIC_EXTENDED_CARS and not CP.flags & ~int(FordFlags.HAS_BSM)
|
||||
if safety.safetyParam == 66:
|
||||
if base_safety_param(CP) == 66:
|
||||
return generic_canfd_qualified(CP)
|
||||
expected = (int(FordSafetyFlags.CANFD) if CP.flags & FordFlags.CANFD else 0)
|
||||
expected |= int(FordSafetyFlags.NEW_PORT) if CP.flags & FordFlags.NEW_PORT else 0
|
||||
expected |= int(FordSafetyFlags.LKA_STEERING) if CP.flags & FordFlags.LKA_STEERING else 0
|
||||
if safety.safetyParam == 18:
|
||||
if base_safety_param(CP) == 18:
|
||||
if CP.carFingerprint != CAR.FORD_MUSTANG_MACH_E_MK1 or expected != int(FordSafetyFlags.CANFD):
|
||||
return False
|
||||
expected |= int(FordSafetyFlags.MACH_E_EXTENDED)
|
||||
return safety.safetyParam == expected
|
||||
return base_safety_param(CP) == expected
|
||||
|
||||
@@ -20,18 +20,24 @@ PROFILES = (
|
||||
|
||||
@pytest.mark.parametrize('identity,base_word', PROFILES)
|
||||
@pytest.mark.parametrize('alpha', (False, True))
|
||||
def test_unmarked_profiles_keep_actual_ordinary_controls(identity, base_word, alpha, monkeypatch):
|
||||
@pytest.mark.parametrize('topology', ('automatic', 'manual', 'multi-panda', 'manual-multi'))
|
||||
def test_unmarked_profiles_keep_actual_ordinary_controls(identity, base_word, alpha, topology, monkeypatch):
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.controls.controlsd import Controls
|
||||
from openpilot.starpilot.lateral.tests.test_lane_runtime import feed
|
||||
from opendbc.car.ford.tests.test_three_ports import params
|
||||
|
||||
monkeypatch.setenv('REPLAY', '1')
|
||||
monkeypatch.setenv('AOL_REPLAY_RUNTIME', '0')
|
||||
cp = params(identity, alpha, False)
|
||||
offset = 4 if topology in ('multi-panda', 'manual-multi') else 0
|
||||
manual = topology in ('manual', 'manual-multi')
|
||||
fp = gen_empty_fingerprint()
|
||||
if not manual:
|
||||
fp[offset][0x5A] = 8
|
||||
fp[offset + 2].update({0x3D6: 8, 0x186: 8})
|
||||
cp = CarInterface.get_params(identity, fp, [], alpha, True, False)
|
||||
assert cp.alternativeExperience == 0
|
||||
assert cp.safetyConfigs[0].safetyParam == base_word | int(alpha)
|
||||
assert cp.safetyConfigs[-1].safetyParam == base_word | int(alpha)
|
||||
with OpenpilotPrefix():
|
||||
saved = Params()
|
||||
saved.put_bool('AlwaysOnLateral', False, block=True)
|
||||
@@ -67,15 +73,17 @@ def run_startup(identity, base_word, alpha, release, topology, monkeypatch):
|
||||
if topology != "manual":
|
||||
fingerprint[offset][0x5A] = 8
|
||||
fingerprint[offset + 2].update({0x3D6: 8, 0x186: 8})
|
||||
expected_long = alpha and not release
|
||||
expected_word = base_word | int(expected_long)
|
||||
expected_long = alpha
|
||||
initial_word = base_word | int(expected_long)
|
||||
expected_word = initial_word | (256 if topology == "manual" else 0)
|
||||
initial = CarInterface.get_params(identity, fingerprint, [], expected_long, release, False)
|
||||
initial.carVin = "0" * 17
|
||||
initial.carFw = []
|
||||
admitted = topology == "automatic"
|
||||
admitted = True
|
||||
expected = initial.to_dict()
|
||||
expected["alternativeExperience"] = 32 if admitted else 0
|
||||
assert initial.safetyConfigs[-1].safetyParam == expected_word
|
||||
expected["safetyConfigs"][-1]["safetyParam"] = expected_word
|
||||
assert initial.safetyConfigs[-1].safetyParam == initial_word
|
||||
assert initial.openpilotLongitudinalControl == expected_long
|
||||
assert not initial.passive and not initial.dashcamOnly
|
||||
with OpenpilotPrefix():
|
||||
@@ -145,7 +153,7 @@ def run_startup(identity, base_word, alpha, release, topology, monkeypatch):
|
||||
controls.publish(command, lateral_log)
|
||||
ci.apply(command.as_reader(), 1_000_000_000)
|
||||
if admitted:
|
||||
from openpilot.starpilot.aol.wire import SAFETY_SERVICE, SafetyState, encode_safety
|
||||
from openpilot.starpilot.aol.wire import SAFETY_SERVICE, PandaSlot, SafetyState, encode_safety
|
||||
|
||||
for tick, (acknowledged, stale) in enumerate(((False, False), (True, False), (True, True)), 1):
|
||||
now = 1_000_000_000 + tick * 10_000_000
|
||||
@@ -160,8 +168,11 @@ def run_startup(identity, base_word, alpha, release, topology, monkeypatch):
|
||||
axis.aolAxisState.nativeAcknowledged = acknowledged
|
||||
native = messaging.new_message(SAFETY_SERVICE, 0, valid=True, logMonoTime=now)
|
||||
native.aolSafetyWire = encode_safety(SafetyState(
|
||||
1, True, now, now + 200_000_000, int(card.CP.safetyConfigs[0].safetyModel.raw),
|
||||
card.CP.safetyConfigs[0].safetyParam, acknowledged, False, True, False, 'fixture-panda', 'ford-current'))
|
||||
1, True, now, now + 200_000_000, int(card.CP.safetyConfigs[-1].safetyModel.raw),
|
||||
card.CP.safetyConfigs[-1].safetyParam, acknowledged, False, True, False, 'fixture-panda', 'ford-current', sourcePandaStatesMonoTime=now,
|
||||
pandaInventory=tuple(PandaSlot(index, 'fixture-panda' if index == len(card.CP.safetyConfigs) - 1 else 'passive-panda',
|
||||
int(config.safetyModel.raw), config.safetyParam, 32, False, False)
|
||||
for index, config in enumerate(card.CP.safetyConfigs))))
|
||||
controls.sm.update_msgs(now / 1e9, [axis.as_reader(), native.as_reader()])
|
||||
active_command, _ = controls.state_control()
|
||||
assert active_command.enabled
|
||||
@@ -182,7 +193,7 @@ def test_actual_card_aol_publication_and_disabled_controls(identity, word, alpha
|
||||
|
||||
@pytest.mark.parametrize("identity,word", PROFILES)
|
||||
@pytest.mark.parametrize("topology", ("manual", "multi-panda"))
|
||||
def test_aol_denied_topology_preserves_actual_ordinary_startup(identity, word, topology, monkeypatch):
|
||||
def test_aol_manual_and_multi_panda_keep_actual_ordinary_startup(identity, word, topology, monkeypatch):
|
||||
run_startup(identity, word, False, False, topology, monkeypatch)
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ def test_generic_actual_card_publication_reaches_blend_settings(identity, monkey
|
||||
from openpilot.cereal import messaging
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.car.card import Car, alpha_long_requested
|
||||
from openpilot.selfdrive.car.card import Car
|
||||
from openpilot.selfdrive.controls.controlsd import Controls
|
||||
from openpilot.starpilot.controller_extensions import ManualTurnInputs
|
||||
from openpilot.starpilot.lateral.tests.test_lane_runtime import feed
|
||||
@@ -34,7 +34,7 @@ def test_generic_actual_card_publication_reaches_blend_settings(identity, monkey
|
||||
saved.put_bool('FordHumanTurnDetection', False, block=True)
|
||||
for key, value in (("FordCurvatureBlendLow", 0.25), ("FordCurvatureBlendHigh", 0.65), ("FordCurvatureLaneChangeFactor", 1.1)):
|
||||
saved.put(key, value, block=True)
|
||||
cp = params(identity, alpha_long_requested(saved, is_release=release), release)
|
||||
cp = params(identity, alpha, release)
|
||||
cp.carVin = '0' * 17
|
||||
cp.carFw = []
|
||||
expected = cp.to_dict()
|
||||
|
||||
@@ -10,14 +10,14 @@ from opendbc.car.ford.values import CAR, FordFlags
|
||||
|
||||
@pytest.mark.parametrize("car", sorted(GENERIC_CANFD_CARS))
|
||||
@pytest.mark.parametrize("alpha,release", ((False,False),(True,False),(False,True),(True,True)))
|
||||
def test_actual_profile_and_release_long_denial(car, alpha, release):
|
||||
def test_actual_profile_and_release_long_support(car, alpha, release):
|
||||
fp = gen_empty_fingerprint()
|
||||
fp[0][0x5A] = 8
|
||||
fp[2].update({0x3D6:8,0x186:8})
|
||||
cp = CarInterface.get_params(car, fp, [], alpha, release, False)
|
||||
assert cp.alphaLongitudinalAvailable == (not release)
|
||||
assert cp.openpilotLongitudinalControl == (alpha and not release)
|
||||
assert cp.safetyConfigs[-1].safetyParam == (67 if alpha and not release else 66)
|
||||
assert cp.alphaLongitudinalAvailable
|
||||
assert cp.openpilotLongitudinalControl == alpha
|
||||
assert cp.safetyConfigs[-1].safetyParam == (67 if alpha else 66)
|
||||
assert cp.steerActuatorDelay == pytest.approx(.22)
|
||||
assert qualified(cp)
|
||||
|
||||
@@ -212,8 +212,8 @@ def test_mache_camera_source_preserves_exact_factory_words(alpha, release, bsm):
|
||||
if bsm:
|
||||
fp[0].update({0x3A6: 8, 0x3A7: 8})
|
||||
cp = CarInterface.get_params(CAR.FORD_MUSTANG_MACH_E_MK1, fp, [], alpha, release, False)
|
||||
assert cp.safetyConfigs[-1].safetyParam == (19 if alpha and not release else 18)
|
||||
assert cp.openpilotLongitudinalControl == (alpha and not release)
|
||||
assert cp.safetyConfigs[-1].safetyParam == (19 if alpha else 18)
|
||||
assert cp.openpilotLongitudinalControl == alpha
|
||||
assert not cp.radarUnavailable
|
||||
radar = RadarInterface(cp)
|
||||
assert radar.rcp.bus == 2 and radar.generic_canfd_lead
|
||||
|
||||
@@ -14,15 +14,15 @@ def fingerprint(offset=0, secoc=False):
|
||||
|
||||
|
||||
class TestMachEProfile(unittest.TestCase):
|
||||
def test_actual_params_stock_debug_long_and_release_stock(self):
|
||||
for alpha, release, word in ((False, False, 18), (True, False, 19), (False, True, 18), (True, True, 18)):
|
||||
def test_actual_params_stock_debug_long_and_release_long(self):
|
||||
for alpha, release, word in ((False, False, 18), (True, False, 19), (False, True, 18), (True, True, 19)):
|
||||
with self.subTest(alpha=alpha, release=release):
|
||||
cp = CarInterface.get_params(CAR.FORD_MUSTANG_MACH_E_MK1, fingerprint(), [], alpha, release, False)
|
||||
self.assertEqual(cp.safetyConfigs[-1].safetyParam, word)
|
||||
self.assertEqual(cp.openpilotLongitudinalControl, word == 19)
|
||||
self.assertEqual(cp.alternativeExperience, 0)
|
||||
if release:
|
||||
self.assertFalse(cp.alphaLongitudinalAvailable)
|
||||
self.assertTrue(cp.alphaLongitudinalAvailable)
|
||||
|
||||
def test_secoc_unrelated_flags_and_ae_do_not_select_extension(self):
|
||||
cp = CarInterface.get_params(CAR.FORD_MUSTANG_MACH_E_MK1, fingerprint(secoc=True), [], False, False, False)
|
||||
|
||||
@@ -28,7 +28,7 @@ class TestNewPortCurvature(unittest.TestCase):
|
||||
for identity in PROFILES:
|
||||
for alpha, release in ((False, False), (True, False), (False, True), (True, True)):
|
||||
cp = factory(identity, alpha, release)
|
||||
expected_long = bool(alpha and (identity == CAR.FORD_EDGE_MK2 or not release))
|
||||
expected_long = bool(alpha)
|
||||
self.assertTrue(cp.pcmCruise)
|
||||
self.assertEqual(cp.openpilotLongitudinalControl, expected_long)
|
||||
self.assertEqual(cp.safetyConfigs[-1].safetyParam, (8 if identity == CAR.FORD_EDGE_MK2 else 10) + int(expected_long))
|
||||
@@ -156,6 +156,7 @@ class TestNewPortCurvature(unittest.TestCase):
|
||||
command, controller_log = controls.state_control()
|
||||
self.assertTrue(command.enabled)
|
||||
control_events = []
|
||||
|
||||
def capture_control(service, event, collector=control_events):
|
||||
if service == "carControl":
|
||||
collector.append(messaging.log_from_bytes(event.to_bytes()))
|
||||
|
||||
@@ -20,7 +20,7 @@ def test_actual_card_publication_reaches_ford_extensions(identity, alpha, releas
|
||||
from openpilot.cereal import messaging
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.car.card import Car, alpha_long_requested
|
||||
from openpilot.selfdrive.car.card import Car
|
||||
from openpilot.selfdrive.controls.controlsd import Controls
|
||||
from openpilot.starpilot.controller_extensions import ManualTurnInputs
|
||||
from openpilot.starpilot.lateral.tests.test_lane_runtime import feed
|
||||
@@ -36,7 +36,7 @@ def test_actual_card_publication_reaches_ford_extensions(identity, alpha, releas
|
||||
saved.put_bool('IsReleaseBranch', release, block=True)
|
||||
saved.put_bool('DisableOpenpilotLongitudinal', disable, block=True)
|
||||
saved.put_bool('FordHumanTurnDetection', False, block=True)
|
||||
cp = params(identity, alpha_long_requested(saved, is_release=release), release)
|
||||
cp = params(identity, alpha, release)
|
||||
cp.carVin = '0' * 17
|
||||
cp.carFw = []
|
||||
expected = cp.to_dict()
|
||||
|
||||
@@ -62,7 +62,7 @@ class TestFordThreePorts(unittest.TestCase):
|
||||
self.assertTrue(requested.openpilotLongitudinalControl)
|
||||
self.assertTrue(requested.safetyConfigs[-1].safetyParam & FordSafetyFlags.LONG_CONTROL)
|
||||
if candidate == CAR.FORD_MONDEO_MK5:
|
||||
self.assertFalse(params(candidate, alpha=True, release=True).openpilotLongitudinalControl)
|
||||
self.assertTrue(params(candidate, alpha=True, release=True).openpilotLongitudinalControl)
|
||||
self.assertAlmostEqual(params(CAR.FORD_MUSTANG_MACH_E_MK1).steerActuatorDelay, 0.22, places=7)
|
||||
self.assertFalse(params(CAR.FORD_BRONCO_SPORT_MK1).openpilotLongitudinalControl)
|
||||
self.assertTrue(params(CAR.FORD_BRONCO_SPORT_MK1, alpha=True).openpilotLongitudinalControl)
|
||||
|
||||
@@ -48,6 +48,7 @@ class FordSafetyFlags(IntFlag):
|
||||
MACH_E_EXTENDED = 16
|
||||
CLASSIC_EXTENDED = 32
|
||||
EXPLORER_EXTENDED = CLASSIC_EXTENDED # Existing public profile spelling.
|
||||
MANUAL = 256
|
||||
BLUEPILOT_ANGLE = 128
|
||||
GENERIC_CANFD_EXTENDED = 64
|
||||
|
||||
@@ -328,12 +329,12 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
whitelist_ecus=[Ecu.abs, Ecu.debug, Ecu.engine, Ecu.eps, Ecu.fwdCamera, Ecu.fwdRadar, Ecu.shiftByWire],
|
||||
logging=True,
|
||||
),
|
||||
Request(
|
||||
*[Request(
|
||||
[StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST],
|
||||
[StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE],
|
||||
whitelist_ecus=[Ecu.abs, Ecu.debug, Ecu.engine, Ecu.eps, Ecu.fwdCamera, Ecu.fwdRadar, Ecu.shiftByWire],
|
||||
bus=0,
|
||||
),
|
||||
bus=bus,
|
||||
) for bus in (0, 4)],
|
||||
*[Request(
|
||||
[StdQueries.TESTER_PRESENT_REQUEST, ford_asbuilt_block_request(block_id)],
|
||||
[StdQueries.TESTER_PRESENT_RESPONSE, ford_asbuilt_block_response(block_id)],
|
||||
|
||||
@@ -501,7 +501,11 @@ static void ford_aol_bind_tx(safety_config *ret, CanMsg workspace[7]) {
|
||||
}
|
||||
}
|
||||
|
||||
static safety_config ford_init(uint16_t param) {
|
||||
static safety_config ford_init(uint16_t configured_param) {
|
||||
const uint16_t param = configured_param & ~256U;
|
||||
const bool manual = (configured_param & 256U) != 0U;
|
||||
// Manual variants are only defined for the exact registered Ford profiles.
|
||||
const bool manual_valid = !manual || ford_aol_param_valid(configured_param);
|
||||
// warning: quality flags are not yet checked in openpilot's CAN parser,
|
||||
// this may be the cause of blocked messages
|
||||
// Shared entries preserve all current checksum/counter/quality contracts.
|
||||
@@ -534,13 +538,11 @@ static safety_config ford_init(uint16_t param) {
|
||||
{FORD_Lane_Assist_Data1, 0, 8, .check_relay = true}, \
|
||||
{FORD_IPMA_Data, 0, 8, .check_relay = true}, \
|
||||
|
||||
#ifdef ALLOW_DEBUG
|
||||
static const CanMsg FORD_CANFD_LONG_TX_MSGS[] = {
|
||||
FORD_COMMON_TX_MSGS
|
||||
{FORD_ACCDATA, 0, 8, .check_relay = true},
|
||||
{FORD_LateralMotionControl2, 0, 8, .check_relay = true},
|
||||
};
|
||||
#endif
|
||||
|
||||
static const CanMsg FORD_CANFD_STOCK_TX_MSGS[] = {
|
||||
FORD_COMMON_TX_MSGS
|
||||
@@ -565,9 +567,7 @@ static safety_config ford_init(uint16_t param) {
|
||||
const uint16_t FORD_PARAM_GENERIC_CANFD_EXTENDED = 64U;
|
||||
const bool generic_canfd_namespace = GET_FLAG(param, FORD_PARAM_GENERIC_CANFD_EXTENDED);
|
||||
ford_generic_canfd_extended = (param == 66U) && (((unsigned int)alternative_experience == 0U) || (((unsigned int)alternative_experience == 32U) && ford_aol_param_valid(param)));
|
||||
#ifdef ALLOW_DEBUG
|
||||
ford_generic_canfd_extended |= (param == 67U) && (((unsigned int)alternative_experience == 0U) || (((unsigned int)alternative_experience == 32U) && ford_aol_param_valid(param)));
|
||||
#endif
|
||||
ford_generic_canfd_announced = false;
|
||||
const bool explorer_namespace = GET_FLAG(param, FORD_PARAM_EXPLORER_EXTENDED);
|
||||
ford_explorer_extended = ((param == 32U) || (param == 33U)) &&
|
||||
@@ -575,9 +575,7 @@ static safety_config ford_init(uint16_t param) {
|
||||
ford_explorer_announced = false;
|
||||
const bool mach_e_namespace = GET_FLAG(param, FORD_PARAM_MACH_E_EXTENDED);
|
||||
ford_mach_e_extended = (param == 18U) && (((unsigned int)alternative_experience == 0U) || (((unsigned int)alternative_experience == 32U) && ford_aol_param_valid(param)));
|
||||
#ifdef ALLOW_DEBUG
|
||||
ford_mach_e_extended |= (param == 19U) && (((unsigned int)alternative_experience == 0U) || (((unsigned int)alternative_experience == 32U) && ford_aol_param_valid(param)));
|
||||
#endif
|
||||
const bool bp_config_valid = ford_bp_configure(param);
|
||||
ford_stock_switch = ((((unsigned int)alternative_experience == 0U) || (((unsigned int)alternative_experience == 32U) && ford_aol_param_valid(param))) &&
|
||||
((param == 2U) || (param == 8U) || (param == 10U) || (param == 12U) ||
|
||||
@@ -605,12 +603,13 @@ static safety_config ford_init(uint16_t param) {
|
||||
safety_config ret;
|
||||
if (ford_canfd) {
|
||||
ret = BUILD_SAFETY_CFG(ford_rx_checks, FORD_CANFD_STOCK_TX_MSGS);
|
||||
bool long_profile = (param == 11U) || (param == 19U) || (param == 67U);
|
||||
#ifdef ALLOW_DEBUG
|
||||
const uint16_t FORD_PARAM_LONGITUDINAL = 1;
|
||||
if (GET_FLAG(param, FORD_PARAM_LONGITUDINAL)) {
|
||||
long_profile |= GET_FLAG(param, 1U);
|
||||
#endif
|
||||
if (long_profile) {
|
||||
ret = BUILD_SAFETY_CFG(ford_rx_checks, FORD_CANFD_LONG_TX_MSGS);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
ret = ford_new_port ? BUILD_SAFETY_CFG(ford_rx_checks, FORD_STOCK_TX_MSGS) :
|
||||
BUILD_SAFETY_CFG(ford_rx_checks, FORD_LONG_TX_MSGS);
|
||||
@@ -632,13 +631,13 @@ static safety_config ford_init(uint16_t param) {
|
||||
SET_RX_CHECKS(ford_stock_rx_checks, ret);
|
||||
}
|
||||
}
|
||||
if (!bp_config_valid || (mach_e_namespace && !ford_mach_e_extended) ||
|
||||
if (!manual_valid || !bp_config_valid || (mach_e_namespace && !ford_mach_e_extended) ||
|
||||
(explorer_namespace && !ford_explorer_extended) ||
|
||||
(generic_canfd_namespace && !ford_generic_canfd_extended)) {
|
||||
ret.tx_msgs = NULL;
|
||||
ret.tx_msgs_len = 0;
|
||||
}
|
||||
ford_aol_configure(param);
|
||||
ford_aol_configure(configured_param);
|
||||
if (ford_aol_enabled) {
|
||||
static const RxCheck ford_aol_default_rx[] = {
|
||||
FORD_COMMON_RX_CHECKS
|
||||
@@ -687,6 +686,10 @@ static safety_config ford_init(uint16_t param) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (i < selected_len) {
|
||||
ford_aol_rx_workspace[i] = selected_rx[i];
|
||||
const unsigned int addr = ford_aol_rx_workspace[i].msg[0].addr;
|
||||
if (manual && ((addr == 0x176U) || (addr == 0x230U) || (addr == 0x5AU))) {
|
||||
ford_aol_rx_workspace[i].msg[0].addr = 0x3C3U;
|
||||
}
|
||||
} else {
|
||||
ford_aol_rx_workspace[i] = (RxCheck){0};
|
||||
}
|
||||
@@ -698,9 +701,7 @@ static safety_config ford_init(uint16_t param) {
|
||||
_Static_assert(sizeof(FORD_CANFD_STOCK_TX_MSGS) <= sizeof(ford_aol_tx_msgs), "Ford CAN FD stock TX capacity");
|
||||
_Static_assert(sizeof(FORD_STOCK_TX_MSGS) <= sizeof(ford_aol_tx_msgs), "Ford stock TX capacity");
|
||||
_Static_assert(sizeof(FORD_LONG_TX_MSGS) <= sizeof(ford_aol_tx_msgs), "Ford long TX capacity");
|
||||
#ifdef ALLOW_DEBUG
|
||||
_Static_assert(sizeof(FORD_CANFD_LONG_TX_MSGS) <= sizeof(ford_aol_tx_msgs), "Ford CAN FD long TX capacity");
|
||||
#endif
|
||||
ford_aol_bind_tx(&ret, ford_aol_tx_msgs);
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
static bool ford_aol_manual = false;
|
||||
static bool ford_aol_enabled = false;
|
||||
static bool ford_aol_token = false;
|
||||
static bool ford_aol_main_available = false;
|
||||
@@ -151,7 +152,9 @@ static void ford_aol_rx(const CANPacket_t *msg) {
|
||||
ford_aol_lka_ready = ((ford_aol_param & 0xFFFEU) == 12U) ?
|
||||
((((msg->data[0] >> 4U) & 3U) == 3U) && ((msg->data[0] & 0x40U) == 0U)) :
|
||||
((state == 1U) || (state == 2U) || (state == 3U));
|
||||
} else {
|
||||
} else if (ford_aol_manual && msg_matches(msg, 0x3C3U, 0U, 8U)) {
|
||||
ford_aol_drive = !GET_BIT(msg, 9U);
|
||||
} else if (!ford_aol_manual) {
|
||||
const uint16_t base = ford_aol_param & 0xFFFEU;
|
||||
const uint16_t gear_addr = (base == 8U) ? 0x230U : ((base == 10U) ? 0x5AU : 0x176U);
|
||||
if (msg_matches(msg, gear_addr, 0U, 8U)) {
|
||||
@@ -159,6 +162,8 @@ static void ford_aol_rx(const CANPacket_t *msg) {
|
||||
((base == 10U) ? (msg->data[6] & 15U) : (msg->data[3] & 15U));
|
||||
ford_aol_drive = (gear == 3U) || (gear == 4U) || (gear == 5U);
|
||||
}
|
||||
} else {
|
||||
// Manual profiles consume only the dedicated reverse lamp source.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,15 +182,13 @@ static void ford_aol_full_engagement(bool cruise_rising) {
|
||||
|
||||
static bool ford_aol_param_valid(uint16_t param) {
|
||||
bool result = false;
|
||||
switch (param) {
|
||||
switch (param & ~256U) {
|
||||
case 8U: case 9U: case 10U: case 12U: case 13U: case 18U: case 32U: case 33U: case 66U:
|
||||
result = true;
|
||||
break;
|
||||
#ifdef ALLOW_DEBUG
|
||||
case 11U: case 19U: case 67U:
|
||||
result = true;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -195,7 +198,8 @@ static bool ford_aol_param_valid(uint16_t param) {
|
||||
static void ford_aol_configure(uint16_t param) {
|
||||
ford_aol_reset();
|
||||
ford_aol_enabled = ford_aol_param_valid(param) && ((unsigned int)alternative_experience == 32U);
|
||||
ford_aol_param = param;
|
||||
ford_aol_manual = (param & 256U) != 0U;
|
||||
ford_aol_param = param & ~256U;
|
||||
if (ford_aol_enabled) {
|
||||
static const AolSafetyPolicy policy = {
|
||||
.reset = ford_aol_reset,
|
||||
|
||||
@@ -8,11 +8,11 @@ from opendbc.safety.tests.libsafety import libsafety_py
|
||||
libsafety_py.ffi.cdef('extern safety_config current_safety_config;')
|
||||
|
||||
STOCK_CANFD_TX = frozenset(((0x83, 0, 8), (0x83, 2, 8), (0x18A, 0, 8), (0x3CA, 0, 8), (0x3D8, 0, 8), (0x3D6, 0, 8)))
|
||||
RELEASE_LEDGER = {3: (6, STOCK_CANFD_TX), 11: (6, STOCK_CANFD_TX), 19: (6, frozenset()), 67: (6, frozenset())}
|
||||
RELEASE_LEDGER = {3: (6, STOCK_CANFD_TX)}
|
||||
|
||||
|
||||
def assert_release_denial(native, word):
|
||||
"""Prove absent AOL/LONG, preserving the existing stock fallback of3/11.
|
||||
"""Prove absent AOL/LONG, preserving the existing stock fallback of legacy parameter3.
|
||||
|
||||
A physical PCM edge can still set the ordinary boolean even with an empty TX
|
||||
table. No assertion equates that boolean or hook return code with authority.
|
||||
|
||||
@@ -16,6 +16,17 @@ class TestFordAolAdmission(unittest.TestCase):
|
||||
self.safety.set_alternative_experience(0)
|
||||
self.safety.set_safety_hooks(structs.CarParams.SafetyModel.noOutput, 0)
|
||||
|
||||
def test_angle_profiles_reject_malformed_lateral_frames_without_healthy_sources(self):
|
||||
for word in (128, 129, 130, 131):
|
||||
with self.subTest(word=word):
|
||||
self.safety.init_tests()
|
||||
self.safety.set_alternative_experience(0)
|
||||
self.assertEqual(self.safety.set_safety_hooks(structs.CarParams.SafetyModel.ford, word), 0)
|
||||
self.safety.set_controls_allowed(False)
|
||||
for address in (0x3CA, 0x3D3 if word & 2 else 0x3D6):
|
||||
self.assertFalse(self.safety.safety_tx_hook(libsafety_py.make_CANPacket(address, 0, bytes(8))))
|
||||
self.assertFalse(self.safety.get_controls_allowed())
|
||||
|
||||
def test_no_frames_no_lateral_or_longitudinal_authority(self):
|
||||
for word in (8, 9, 10, 11, 12, 13, 18, 19, 32, 33, 66, 67):
|
||||
for ae in (0, 32, 33):
|
||||
@@ -31,7 +42,8 @@ class TestFordAolAdmission(unittest.TestCase):
|
||||
|
||||
|
||||
class TestFordAolDriverIntent(unittest.TestCase):
|
||||
PROFILES = (32, 8, 10, 12, 18, 66)
|
||||
BASE_PROFILES = (8, 9, 10, 11, 12, 13, 18, 19, 32, 33, 66, 67)
|
||||
PROFILES = BASE_PROFILES + tuple(word | 256 for word in BASE_PROFILES)
|
||||
|
||||
def setUp(self):
|
||||
from opendbc.can import CANPacker
|
||||
@@ -56,8 +68,12 @@ class TestFordAolDriverIntent(unittest.TestCase):
|
||||
self.assertTrue(self.safety.safety_rx_hook(libsafety_py.make_CANPacket(address, bus, data)))
|
||||
|
||||
def pump(self, count, *, main=3, tja=False, gear=3, eps=0, mask=0, heartbeat=True, speed=10., curvature=0.):
|
||||
gear_name, gear_signal = ('TransGearData', 'GearLvrPos_D_Actl') if self.word == 8 else (
|
||||
('Gear_Shift_by_Wire_FD1', 'TrnRng_D_RqGsm') if self.word == 10 else ('PowertrainData_10', 'TrnRng_D_Rq'))
|
||||
base = self.word & ~257
|
||||
gear_name, gear_signal = ('TransGearData', 'GearLvrPos_D_Actl') if base == 8 else (
|
||||
('Gear_Shift_by_Wire_FD1', 'TrnRng_D_RqGsm') if base == 10 else ('PowertrainData_10', 'TrnRng_D_Rq'))
|
||||
if self.word & 256:
|
||||
gear_name, gear_signal = 'BCM_Lamp_Stat_FD1', 'RvrseLghtOn_B_Stat'
|
||||
gear = int(gear == 1)
|
||||
for _ in range(count):
|
||||
self.tick += 1
|
||||
self.safety.set_timer(1_000_000 + self.tick * 10_000)
|
||||
@@ -76,7 +92,7 @@ class TestFordAolDriverIntent(unittest.TestCase):
|
||||
for name, hz, fields in values:
|
||||
if self.tick * hz // 100 != (self.tick - 1) * hz // 100:
|
||||
self.rx(name, fields)
|
||||
if (self.word & ~1) in (10, 12, 18, 66) and self.tick * 30 // 100 != (self.tick - 1) * 30 // 100:
|
||||
if base in (10, 12, 18, 66) and self.tick * 30 // 100 != (self.tick - 1) * 30 // 100:
|
||||
self.rx('Lane_Assist_Data3_FD1', {'LatCtlSte_D_Stat': 1, 'LaActAvail_D_Actl': 3, 'LaActDeny_B_Actl': 0})
|
||||
self.safety.aol_set_host_request(mask)
|
||||
self.safety.aol_get_permission_mask()
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
"""Exact manual profiles consume a fresh physical reverse signal on local bus0."""
|
||||
import pytest
|
||||
from opendbc.car import structs
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
from opendbc.safety.tests.test_ford_full_engagement import ford_native as ford_native, select
|
||||
from opendbc.safety.tests.test_ford_alpha_aol import tx
|
||||
|
||||
WORDS = (8, 9, 10, 11, 12, 13, 18, 19, 32, 33, 66, 67)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('base', WORDS)
|
||||
def test_manual_activation_brake_reverse_fault_recovery_and_long_bounds(ford_native, base):
|
||||
n = ford_native
|
||||
select(n, base | 256)
|
||||
n.pump(40, main=3)
|
||||
n.pump(10, main=4, mask=3)
|
||||
assert n.safety.aol_get_permission_mask() == (3 if base & 1 else 1)
|
||||
assert tx(n, active=True) == bool(base & 1)
|
||||
n.rx('EngBrakeData', {'BpedDrvAppl_D_Actl': 2, 'CcStat_D_Actl': 3})
|
||||
assert n.safety.aol_get_permission_mask() == 1
|
||||
assert not tx(n, active=True)
|
||||
n.pump(20, gear=1, mask=3)
|
||||
assert n.safety.aol_get_permission_mask() == 0
|
||||
n.pump(20, gear=3, mask=3)
|
||||
assert n.safety.aol_get_permission_mask() == 1
|
||||
n.pump(20, eps=1, mask=3)
|
||||
assert n.safety.aol_get_permission_mask() == 0
|
||||
n.pump(20, eps=0, mask=3)
|
||||
assert n.safety.aol_get_permission_mask() == 1
|
||||
n.pump(20, eps=2, mask=3)
|
||||
assert n.safety.aol_get_permission_mask() == 0
|
||||
n.pump(20, eps=0, mask=3)
|
||||
assert n.safety.aol_get_permission_mask() == 0
|
||||
assert not n.safety.get_controls_allowed() and not tx(n, active=True)
|
||||
n.pump(20, main=0, mask=3)
|
||||
assert n.safety.aol_get_permission_mask() == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize('base', WORDS)
|
||||
@pytest.mark.parametrize('failure', ['missing', 'wrong-bus', 'wrong-length', 'automatic-gear'])
|
||||
def test_manual_reverse_source_cannot_be_substituted(ford_native, base, failure):
|
||||
n = ford_native
|
||||
select(n, base | 256)
|
||||
real_rx = n.rx
|
||||
|
||||
def filtered(name, values):
|
||||
if name != 'BCM_Lamp_Stat_FD1':
|
||||
return real_rx(name, values)
|
||||
if failure == 'missing':
|
||||
return None
|
||||
if failure == 'automatic-gear':
|
||||
return real_rx('PowertrainData_10', {'TrnRng_D_Rq': 3})
|
||||
addr, data, _ = n.packer.make_can_msg(name, 0, values)
|
||||
n.safety.safety_rx_hook(libsafety_py.make_CANPacket(addr, 1 if failure == 'wrong-bus' else 0, data if failure == 'wrong-bus' else data[:-1]))
|
||||
n.rx = filtered
|
||||
n.pump(40, main=3)
|
||||
n.pump(10, main=4, mask=3)
|
||||
assert not n.safety.safety_config_valid()
|
||||
assert n.safety.aol_get_permission_mask() == 0
|
||||
assert not tx(n, active=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('base', WORDS)
|
||||
def test_manual_reverse_source_expiry_withdraws_axes(ford_native, base):
|
||||
n = ford_native
|
||||
select(n, base | 256)
|
||||
n.pump(40, main=3)
|
||||
n.pump(10, main=4, mask=3)
|
||||
assert n.safety.aol_get_permission_mask() & 1
|
||||
original = n.rx
|
||||
n.rx = lambda name, fields: None if name == 'BCM_Lamp_Stat_FD1' else original(name, fields)
|
||||
for _ in range(120):
|
||||
n.pump(1, main=4, mask=3)
|
||||
n.safety.safety_tick()
|
||||
assert not n.safety.safety_config_valid()
|
||||
assert n.safety.aol_get_permission_mask() == 0
|
||||
assert not tx(n, active=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('base', WORDS)
|
||||
def test_manual_unmarked_profile_preserves_ordinary_physical_long_and_brake(ford_native, base):
|
||||
n = ford_native
|
||||
select(n, base, ae=0)
|
||||
n.word |= 256 # Emit real manual sources; ordinary safety keeps its original namespace.
|
||||
n.pump(40, main=3)
|
||||
assert not n.safety.get_controls_allowed()
|
||||
assert n.safety.aol_get_permission_mask() == 0
|
||||
assert not tx(n, active=True)
|
||||
n.pump(10, main=4)
|
||||
assert n.safety.get_controls_allowed() and n.safety.safety_config_valid()
|
||||
assert n.safety.aol_get_permission_mask() == 0
|
||||
assert tx(n, active=True) == bool(base & 1)
|
||||
n.rx('EngBrakeData', {'BpedDrvAppl_D_Actl': 2, 'CcStat_D_Actl': 3})
|
||||
assert not n.safety.get_controls_allowed() and not tx(n, active=True)
|
||||
|
||||
|
||||
def test_release_keeps_unregistered_legacy_canfd_long_parameter_denied(ford_native):
|
||||
n = ford_native
|
||||
if n.safety.set_safety_hooks(structs.CarParams.SafetyModel.allOutput, 0) == 0:
|
||||
pytest.skip('Legacy parameter3 remains DEBUG-only; RELEASE exercises the denial')
|
||||
from opendbc.safety.tests.ford_release_contract import assert_release_denial
|
||||
select(n, 3)
|
||||
assert_release_denial(n, 3)
|
||||
@@ -3,7 +3,6 @@
|
||||
import pytest
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.safety.tests.ford_release_contract import assert_release_denial
|
||||
from opendbc.safety.tests.test_ford_aol import TestFordAolDriverIntent as _FordNative
|
||||
|
||||
WORDS = (8, 9, 10, 11, 12, 13, 18, 19, 32, 33, 66, 67)
|
||||
@@ -21,14 +20,10 @@ def ford_native():
|
||||
|
||||
def select(native, word, *, ae=32):
|
||||
safety = native.safety
|
||||
debug = safety.set_safety_hooks(structs.CarParams.SafetyModel.allOutput, 0) == 0
|
||||
safety.init_tests()
|
||||
safety.set_alternative_experience(ae)
|
||||
result = safety.set_safety_hooks(structs.CarParams.SafetyModel.ford, word)
|
||||
assert result == 0
|
||||
if not debug and word in (11, 19, 67):
|
||||
assert_release_denial(native, word)
|
||||
pytest.skip('RELEASE configuration/physical RX/AOL and TX denial proved; positive vector requires DEBUG')
|
||||
native.word, native.tick = word & ~1, 0
|
||||
safety.set_aol_test_heartbeat(True)
|
||||
|
||||
|
||||
@@ -41,14 +41,14 @@ class TestFordGenericCanfdExtended(unittest.TestCase):
|
||||
self._set_prev_desired_angle(0.)
|
||||
self.safety.set_controls_allowed(True)
|
||||
|
||||
def test_exact_namespace_and_release_long_denial(self):
|
||||
def test_exact_namespace_and_release_long_support(self):
|
||||
for word in (66,67):
|
||||
self.select(word)
|
||||
admitted = word == 66 or self.debug
|
||||
admitted = True
|
||||
self.assertEqual(admitted, self.announce())
|
||||
self.prepare()
|
||||
self.assertEqual(admitted, self._tx(self._lat_ctl_msg(True,0,0,0,.0002)))
|
||||
self.assertEqual(word == 67 and self.debug,
|
||||
self.assertEqual(word == 67,
|
||||
self._tx(self._acc_command_msg(self.INACTIVE_GAS,self.INACTIVE_ACCEL,False)))
|
||||
for word, ae in ((64,0),(65,0),(68,0),(70,0),(74,0),(82,0),(98,0),(194,0),(66,33),(67,33)):
|
||||
self.select(word,ae)
|
||||
@@ -56,7 +56,7 @@ class TestFordGenericCanfdExtended(unittest.TestCase):
|
||||
self.assertFalse(self._tx(self._lat_ctl_msg(False,0,0,0,0)))
|
||||
for word in (66, 67):
|
||||
self.select(word, 32)
|
||||
self.assertEqual(word == 66 or self.debug, self.announce())
|
||||
self.assertTrue(self.announce())
|
||||
self.assertFalse(self._tx(self._lat_ctl_msg(True, 0, 0, .0002, 0)))
|
||||
|
||||
def test_announcement_path_and_inactive_contract(self):
|
||||
|
||||
@@ -169,27 +169,11 @@ class TestFordMachEDebugLongExtended(TestFordMachEExtended):
|
||||
REQUIRES_DEBUG = True
|
||||
|
||||
|
||||
class TestFordMachELongReleaseDenied(unittest.TestCase):
|
||||
class TestFordMachELongReleaseExtended(TestFordMachEExtended):
|
||||
SAFETY_PARAM = 19
|
||||
REQUIRES_DEBUG = False
|
||||
|
||||
def setUp(self):
|
||||
safety = libsafety_py.libsafety
|
||||
debug = safety.set_safety_hooks(CarParams.SafetyModel.allOutput, 0) == 0
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.noOutput, 0)
|
||||
safety.set_alternative_experience(0)
|
||||
if self.REQUIRES_DEBUG is not None and debug != self.REQUIRES_DEBUG:
|
||||
self.skipTest("This case requires " + ("DEBUG" if self.REQUIRES_DEBUG else "RELEASE") + " safety")
|
||||
self._message_fixture = ford.TestFordCANFDStockSafety()
|
||||
self._message_fixture.SAFETY_PARAM = self.SAFETY_PARAM
|
||||
self._message_fixture.setUp()
|
||||
|
||||
def __getattr__(self, name):
|
||||
fixture = self.__dict__.get("_message_fixture")
|
||||
if fixture is None:
|
||||
raise AttributeError(name)
|
||||
return getattr(fixture, name)
|
||||
|
||||
def test_long_profile_no_transmit_authority_in_release(self):
|
||||
self.safety.set_controls_allowed(True)
|
||||
self.assertFalse(self._tx(self._lat_ctl_msg(True, 0.0, 0.0, 0.0, 0.0)))
|
||||
# Compatibility for existing CI selector names: this runs the promoted profile's
|
||||
# complete positive RELEASE contract, including the unchanged actuation bounds.
|
||||
TestFordMachELongReleaseDenied = TestFordMachELongReleaseExtended
|
||||
|
||||
@@ -295,6 +295,8 @@ class Car:
|
||||
cp, long_cp, enabled=eligible, is_release=is_release)
|
||||
self.ioniq6_long_selected = self.ioniq6_long_pending
|
||||
cp = startup_preferences.prepare(cp, fingerprints=fingerprints)
|
||||
from openpilot.starpilot.car.ford.longitudinal_preferences import prepare_release_long as prepare_ford_release_long
|
||||
cp = prepare_ford_release_long(cp, self.params, is_release=is_release, enabled=openpilot_enabled_toggle)
|
||||
return self.vehicle_startup.prepare(cp, interfaces[candidate], self.can_callbacks,
|
||||
requested=alpha_long_allowed and not is_release and openpilot_enabled_toggle,
|
||||
admission=self.startup_diagnostic_admission)
|
||||
@@ -387,8 +389,12 @@ class Car:
|
||||
self.aol_sequence = 0
|
||||
self.aol_process_fault_context = AolProcessFaultContext() if self.aol_replay else None
|
||||
if self.aol_qualified:
|
||||
config = (volkswagen_aol_safety_config(self.CP) if self.CP.brand == 'volkswagen' else
|
||||
(hyundai_aol_safety_config(self.CP) if self.CP.brand == 'hyundai' else self.CP.safetyConfigs[0]))
|
||||
if self.CP.brand == 'ford':
|
||||
from opendbc.car.ford.aol import aol_safety_config as ford_aol_safety_config
|
||||
config = ford_aol_safety_config(self.CP)
|
||||
else:
|
||||
config = (volkswagen_aol_safety_config(self.CP) if self.CP.brand == 'volkswagen' else
|
||||
(hyundai_aol_safety_config(self.CP) if self.CP.brand == 'hyundai' else self.CP.safetyConfigs[0]))
|
||||
if config is None:
|
||||
raise RuntimeError('AOL requires the exact vehicle safety configuration')
|
||||
config.safetyParam |= aol_policy.safety_param_addition
|
||||
|
||||
@@ -19,14 +19,15 @@ VIN = '3FMTK1SS0MMA00001'
|
||||
|
||||
class FordEcus:
|
||||
"""Transport only: the real ISO-TP layer assembles VIN and firmware replies."""
|
||||
def __init__(self, *, firmware=None, vin=VIN, background=None):
|
||||
def __init__(self, *, firmware=None, vin=VIN, background=None, offset=0):
|
||||
self.offset = offset
|
||||
self.clock = 100.
|
||||
self.vin = vin
|
||||
self.versions = firmware if firmware is not None else {(0x706, b'\x22\xf1\x88'): b'RJ6T-14H102-BAE',
|
||||
(0x730, b'\x22\xf1\x88'): b'LJ9C-14D003-GA', (0x760, b'\x22\xf1\x88'): b'LK9C-2D053-CN',
|
||||
(0x7E0, b'\x22\xf1\x88'): b'MJ98-14C204-BYG'}
|
||||
self.background = background or [CanData(0x5A, bytes(8), 0), CanData(0x3D6, bytes(8), 2),
|
||||
CanData(0x186, bytes(8), 2)]
|
||||
self.background = background or [CanData(0x5A, bytes(8), offset), CanData(0x3D6, bytes(8), offset + 2),
|
||||
CanData(0x186, bytes(8), offset + 2)]
|
||||
self.queued = []
|
||||
self.pending = {}
|
||||
self.mux_calls = []
|
||||
@@ -39,30 +40,29 @@ class FordEcus:
|
||||
self.mux_calls.append(enabled)
|
||||
assert not enabled, 'Ford discovery switched CAN2 onto the OBD network'
|
||||
|
||||
def reply(self, address, payload):
|
||||
def reply(self, address, payload, *, bus=None):
|
||||
bus = self.offset if bus is None else bus
|
||||
if len(payload) < 8:
|
||||
frames = [bytes([len(payload)]) + payload]
|
||||
else:
|
||||
frames = [bytes([0x10 | (len(payload) >> 8), len(payload) & 0xFF]) + payload[:6]]
|
||||
frames += [bytes([0x20 | (index & 15)]) + payload[offset:offset + 7]
|
||||
for index, offset in enumerate(range(6, len(payload), 7), 1)]
|
||||
self.queued.append(CanData(address + 8, frames[0].ljust(8, b'\0'), 0))
|
||||
self.pending[address] = [CanData(address + 8, data.ljust(8, b'\0'), 0) for data in frames[1:]]
|
||||
self.queued.append(CanData(address + 8, frames[0].ljust(8, b'\0'), bus))
|
||||
self.pending[address, bus] = [CanData(address + 8, data.ljust(8, b'\0'), bus) for data in frames[1:]]
|
||||
|
||||
def send(self, frames):
|
||||
self.sent.extend(frames)
|
||||
for address, data, bus in frames:
|
||||
if bus != 0:
|
||||
continue
|
||||
if data[0] >> 4 == 3:
|
||||
self.queued.extend(self.pending.pop(address, []))
|
||||
self.queued.extend(self.pending.pop((address, bus), []))
|
||||
elif data[0] >> 4 == 0:
|
||||
request = bytes(data[1:1 + data[0]])
|
||||
if address == 0x7DF and request == b'\x22\xf1\x90':
|
||||
self.reply(0x7E0, b'\x62\xf1\x90' + self.vin.encode())
|
||||
elif request == b'\x3e\x00' and any(tx == address for tx, _ in self.versions):
|
||||
if bus == 0 and address == 0x7DF and request == b'\x22\xf1\x90':
|
||||
self.reply(0x7E0, b'\x62\xf1\x90' + self.vin.encode(), bus=0)
|
||||
elif bus == self.offset and request == b'\x3e\x00' and any(tx == address for tx, _ in self.versions):
|
||||
self.reply(address, b'\x7e\x00')
|
||||
elif (address, request) in self.versions:
|
||||
elif bus == self.offset and (address, request) in self.versions:
|
||||
self.reply(address, bytes([request[0] + 0x40]) + request[1:] + self.versions[address, request])
|
||||
|
||||
def recv(self, wait_for_one=False):
|
||||
@@ -85,8 +85,9 @@ def discover(transport, candidate, *, cached=None):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('candidate', CANFD_CARS)
|
||||
def test_registered_ford_uses_real_normal_bus_vin_fw_can_and_factory(candidate):
|
||||
transport = FordEcus()
|
||||
@pytest.mark.parametrize('offset', (0, 4))
|
||||
def test_registered_ford_uses_real_normal_bus_vin_fw_can_and_factory(candidate, offset):
|
||||
transport = FordEcus(offset=offset)
|
||||
ci = discover(transport, candidate)
|
||||
assert transport.mux_calls and set(transport.mux_calls) == {False}
|
||||
assert ci.CP.carFingerprint == candidate and ci.CP.carVin == VIN
|
||||
@@ -95,9 +96,13 @@ def test_registered_ford_uses_real_normal_bus_vin_fw_can_and_factory(candidate):
|
||||
assert ci.CP.safetyConfigs[-1].safetyModel == structs.CarParams.SafetyModel.ford
|
||||
assert len(ci.CP.carFw) == len(transport.versions)
|
||||
for fw in ci.CP.carFw:
|
||||
assert fw.brand == 'ford' and fw.bus == 0
|
||||
assert fw.brand == 'ford' and fw.bus == offset
|
||||
assert fw.fwVersion == transport.versions[fw.address, b'\x22\xf1\x88']
|
||||
assert all(frame.src == 0 for frame in transport.sent)
|
||||
assert all(frame.src in (0, 4) for frame in transport.sent)
|
||||
assert len(ci.CP.safetyConfigs) == (2 if offset else 1)
|
||||
if offset:
|
||||
assert ci.CP.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.noOutput
|
||||
assert ci.CP.safetyConfigs[0].safetyParam == 0
|
||||
assert any(frame.dat[0] >> 4 == 3 for frame in transport.sent), 'Real ISO-TP flow control was not exercised'
|
||||
|
||||
|
||||
@@ -118,7 +123,10 @@ def test_wrong_brand_cache_cannot_replace_live_ford_responses():
|
||||
assert transport.sent and ci.CP.carFw and all(f.brand == 'ford' for f in ci.CP.carFw)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('drift', ['essential_bus1', 'unique_logging_bus1', 'empty', 'effective_only'])
|
||||
@pytest.mark.parametrize('drift', [
|
||||
'essential_bus1', 'unique_logging_bus1', 'empty', 'effective_only', 'foreign_auxiliary',
|
||||
'unique_auxiliary', 'missing_auxiliary', 'effective_missing_auxiliary',
|
||||
])
|
||||
def test_query_topology_drift_refuses_direct_scoped_discovery_before_io(drift):
|
||||
original = fw_versions.FW_QUERY_CONFIGS['ford']
|
||||
changed = list(original.requests)
|
||||
@@ -128,7 +136,15 @@ def test_query_topology_drift_refuses_direct_scoped_discovery_before_io(drift):
|
||||
changed.append(replace(changed[0], request=[changed[0].request[0], b'\x22\xf1\xff']))
|
||||
elif drift == 'empty':
|
||||
changed = []
|
||||
elif drift == 'missing_auxiliary':
|
||||
changed = [request for request in changed if request.bus != 4]
|
||||
elif drift == 'foreign_auxiliary':
|
||||
changed.append(replace(changed[1], bus=8))
|
||||
elif drift == 'unique_auxiliary':
|
||||
changed.append(replace(changed[1], bus=4, request=[changed[1].request[0], b'\x22\xf1\xff']))
|
||||
effective = [('ford', original, replace(original.requests[1], bus=1))] if drift == 'effective_only' else fw_versions.REQUESTS
|
||||
if drift == 'effective_missing_auxiliary':
|
||||
effective = [(brand, owner, request) for brand, owner, request in effective if brand != 'ford' or request.bus != 4]
|
||||
config = replace(original, requests=changed, extra_ecus=[])
|
||||
with mock.patch.dict(fw_versions.FW_QUERY_CONFIGS, {'ford': config}), mock.patch.object(fw_versions, 'REQUESTS', effective):
|
||||
assert not normal_bus0_queries()
|
||||
|
||||
@@ -251,7 +251,7 @@ int main() {
|
||||
constexpr uint16_t ford_words[] = {8U, 9U, 10U, 11U, 12U, 13U, 18U, 19U, 32U, 33U, 66U, 67U};
|
||||
for (uint32_t word = 0U; word <= 65535U; ++word) {
|
||||
bool expected = false;
|
||||
for (const auto exact : ford_words) expected |= word == exact;
|
||||
for (const auto exact : ford_words) expected |= (word == exact) || (word == (exact | 256U));
|
||||
auto ford = status();
|
||||
ford.safety_mode = 6U;
|
||||
ford.safety_param = static_cast<uint16_t>(word);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <cstring>
|
||||
#include "openpilot/starpilot/car/ford/aol_policy.h"
|
||||
#include <unistd.h>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
@@ -44,7 +45,7 @@ public:
|
||||
OwnerPanda(std::unique_ptr<OwnerHandle> h, uint32_t offset) : Panda(std::move(h), cereal::PandaState::PandaType::RED_PANDA, offset) {}
|
||||
};
|
||||
|
||||
void test_actual_config_slots_and_owner_handle() {
|
||||
void test_actual_config_slots_and_owner_handle(cereal::CarParams::SafetyModel mode, uint16_t word) {
|
||||
using Mode = cereal::CarParams::SafetyModel;
|
||||
const std::string prefix = "test-panda-owner-" + std::to_string(getpid());
|
||||
const char *old = getenv("OPENPILOT_PREFIX");
|
||||
@@ -72,15 +73,15 @@ void test_actual_config_slots_and_owner_handle() {
|
||||
cp.setAlternativeExperience(32);
|
||||
auto slots = cp.initSafetyConfigs(2);
|
||||
slots[0].setSafetyModel(Mode::NO_OUTPUT); slots[0].setSafetyParam(0);
|
||||
slots[1].setSafetyModel(Mode::HYUNDAI_CANFD); slots[1].setSafetyParam(0x809);
|
||||
slots[1].setSafetyModel(mode); slots[1].setSafetyParam(word);
|
||||
auto words = capnp::messageToFlatArray(message);
|
||||
auto bytes = words.asBytes();
|
||||
params.put("CarParams", reinterpret_cast<const char *>(bytes.begin()), bytes.size());
|
||||
params.putBool("FirmwareQueryDone", true); params.putBool("ControlsReady", true);
|
||||
safety.configureSafetyMode(true);
|
||||
CHECK(aux->health.safety_mode_pkt == (uint8_t)Mode::NO_OUTPUT);
|
||||
CHECK(owner->health.safety_mode_pkt == (uint8_t)Mode::HYUNDAI_CANFD);
|
||||
CHECK(owner->health.safety_param_pkt == 0x809);
|
||||
CHECK(owner->health.safety_mode_pkt == (uint8_t)mode);
|
||||
CHECK(owner->health.safety_param_pkt == word);
|
||||
CHECK(extra->health.safety_mode_pkt == (uint8_t)Mode::SILENT);
|
||||
CHECK(aux->health.alternative_experience_pkt == 32 && owner->health.alternative_experience_pkt == 32);
|
||||
CHECK(safety.aolOwner() == nullptr);
|
||||
@@ -96,7 +97,7 @@ void test_actual_config_slots_and_owner_handle() {
|
||||
peer->health.flags_pkt |= HEALTH_FLAG_CONTROLS_ALLOWED;
|
||||
CHECK(owner_safety.aolOwner() == nullptr);
|
||||
peer->health.flags_pkt = 0;
|
||||
peer->health.safety_param_pkt = 0x809;
|
||||
peer->health.safety_param_pkt = word;
|
||||
CHECK(owner_safety.aolOwner() == nullptr);
|
||||
peer->health.safety_param_pkt = 0;
|
||||
peer->comms_healthy = false;
|
||||
@@ -106,8 +107,8 @@ void test_actual_config_slots_and_owner_handle() {
|
||||
CHECK(owner_safety.aolOwner() == nullptr);
|
||||
peer->connected = true;
|
||||
}
|
||||
owner->health.safety_param_pkt = 0x808; CHECK(owner_safety.aolOwner() == nullptr);
|
||||
owner->health.safety_param_pkt = 0x809;
|
||||
owner->health.safety_param_pkt = (word ^ 1U); CHECK(owner_safety.aolOwner() == nullptr);
|
||||
owner->health.safety_param_pkt = word;
|
||||
owner->health.alternative_experience_pkt = 0; CHECK(owner_safety.aolOwner() == nullptr);
|
||||
owner->health.alternative_experience_pkt = 32;
|
||||
slots[0].setSafetyParam(1);
|
||||
@@ -121,8 +122,8 @@ void test_actual_config_slots_and_owner_handle() {
|
||||
params.put("CarParams", reinterpret_cast<const char *>(data.begin()), data.size()); }
|
||||
owner_safety.configureSafetyMode(false); owner_safety.configureSafetyMode(true);
|
||||
CHECK(owner_safety.aolOwner() == &primary);
|
||||
const AolSafetyProfile profiles[] = {{(uint8_t)Mode::HYUNDAI_CANFD, [](uint16_t p) { return p == 0x809; }, true}};
|
||||
AolProfileRegistry registry{profiles, 1}; AolAxisNegotiator negotiator(registry);
|
||||
const AolSafetyProfile profiles[] = {{(uint8_t)Mode::HYUNDAI_CANFD, [](uint16_t p) { return p == 0x809; }, true}, FORD_AOL_PROFILE};
|
||||
AolProfileRegistry registry{profiles, 2}; AolAxisNegotiator negotiator(registry);
|
||||
owner->aol.capability_flags = 1;
|
||||
aux->aol = owner->aol; aux->aol.permission_mask = 3; aux->aol.request_mask = 3;
|
||||
AolAxisInput axis{true, true, "session", 1, 200000001, 1, true, false};
|
||||
@@ -142,4 +143,12 @@ void test_actual_config_slots_and_owner_handle() {
|
||||
PandaSafety single({&primary}); CHECK(single.aolOwner() == &primary);
|
||||
}
|
||||
|
||||
int main() { return run_native_test(test_actual_config_slots_and_owner_handle); }
|
||||
void test_registered_owners() {
|
||||
test_actual_config_slots_and_owner_handle(cereal::CarParams::SafetyModel::HYUNDAI_CANFD, 0x809);
|
||||
for (uint16_t word : {8U, 9U, 10U, 11U, 12U, 13U, 18U, 19U, 32U, 33U, 66U, 67U}) {
|
||||
test_actual_config_slots_and_owner_handle(cereal::CarParams::SafetyModel::FORD, word);
|
||||
test_actual_config_slots_and_owner_handle(cereal::CarParams::SafetyModel::FORD, word | 256U);
|
||||
}
|
||||
}
|
||||
|
||||
int main() { return run_native_test(test_registered_owners); }
|
||||
|
||||
@@ -2,6 +2,7 @@ from collections.abc import Callable
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.starpilot.car.hyundai.aol import ioniq6_settings_capable
|
||||
from openpilot.starpilot.car.ford.longitudinal_preferences import release_long_available as ford_release_long_available
|
||||
from opendbc.car.volkswagen.meb_modes import MEB_CARS, settings_capable as meb_settings_capable
|
||||
from openpilot.starpilot.car.volkswagen.preferences import stock_long_requested
|
||||
from openpilot.selfdrive.ui.widgets.ssh_key import ssh_key_item
|
||||
@@ -144,7 +145,7 @@ class DeveloperLayout(Widget):
|
||||
if ui_state.CP is not None:
|
||||
alpha_avail = (meb_settings_capable(ui_state.CP) if ui_state.CP.brand == 'volkswagen' and ui_state.CP.carFingerprint in MEB_CARS
|
||||
else ui_state.CP.alphaLongitudinalAvailable or ioniq6_settings_capable(ui_state.CP))
|
||||
if not alpha_avail or (self._is_release and not meb_settings_capable(ui_state.CP)):
|
||||
if not alpha_avail or (self._is_release and not (meb_settings_capable(ui_state.CP) or ford_release_long_available(ui_state.CP))):
|
||||
self._alpha_long_toggle.set_visible(False)
|
||||
else:
|
||||
self._alpha_long_toggle.set_visible(True)
|
||||
@@ -215,7 +216,8 @@ class DeveloperLayout(Widget):
|
||||
if result == DialogResult.CONFIRM:
|
||||
if (not ui_state.is_offroad() or ui_state.CP is None or
|
||||
not (meb_settings_capable(ui_state.CP) if ui_state.CP.brand == 'volkswagen' and ui_state.CP.carFingerprint in MEB_CARS
|
||||
else ui_state.CP.alphaLongitudinalAvailable or ioniq6_settings_capable(ui_state.CP))):
|
||||
else ui_state.CP.alphaLongitudinalAvailable or ioniq6_settings_capable(ui_state.CP)) or
|
||||
self._is_release and not (meb_settings_capable(ui_state.CP) or ford_release_long_available(ui_state.CP))):
|
||||
self._alpha_long_toggle.action_item.set_state(False)
|
||||
return
|
||||
self._params.put_bool("AlphaLongitudinalEnabled", True, block=True)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from openpilot.starpilot.car.ford.longitudinal_preferences import release_long_available as ford_release_long_available
|
||||
from openpilot.cereal import log
|
||||
from openpilot.common.params import Params, UnknownKeyName
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
@@ -188,7 +189,7 @@ class TogglesLayout(Widget):
|
||||
|
||||
long_desc = unavailable + " " + tr("openpilot longitudinal control may come in a future update.")
|
||||
if ui_state.CP.alphaLongitudinalAvailable:
|
||||
if self._is_release:
|
||||
if self._is_release and not ford_release_long_available(ui_state.CP):
|
||||
long_desc = unavailable + " " + tr("An alpha version of openpilot longitudinal control can be tested, along with " +
|
||||
"Experimental mode, on non-release branches.")
|
||||
else:
|
||||
|
||||
@@ -8,6 +8,7 @@ from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.starpilot.car.hyundai.aol import ioniq6_settings_capable
|
||||
from openpilot.starpilot.car.ford.longitudinal_preferences import release_long_available as ford_release_long_available
|
||||
from opendbc.car.volkswagen.meb_modes import MEB_CARS, settings_capable as meb_settings_capable
|
||||
from openpilot.starpilot.car.volkswagen.preferences import stock_long_requested
|
||||
from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyFetcher
|
||||
@@ -171,7 +172,7 @@ class DeveloperLayoutMici(NavScroller):
|
||||
if ui_state.CP is not None:
|
||||
alpha_avail = (meb_settings_capable(ui_state.CP) if ui_state.CP.brand == 'volkswagen' and ui_state.CP.carFingerprint in MEB_CARS
|
||||
else ui_state.CP.alphaLongitudinalAvailable or ioniq6_settings_capable(ui_state.CP))
|
||||
if not alpha_avail or (ui_state.is_release and not meb_settings_capable(ui_state.CP)):
|
||||
if not alpha_avail or (ui_state.is_release and not (meb_settings_capable(ui_state.CP) or ford_release_long_available(ui_state.CP))):
|
||||
self._alpha_long_toggle.set_visible(False)
|
||||
else:
|
||||
self._alpha_long_toggle.set_visible(True)
|
||||
@@ -194,7 +195,8 @@ class DeveloperLayoutMici(NavScroller):
|
||||
def do_toggle(_state: bool):
|
||||
if (not ui_state.is_offroad() or (_state and (ui_state.CP is None or
|
||||
not (meb_settings_capable(ui_state.CP) if ui_state.CP.brand == 'volkswagen' and ui_state.CP.carFingerprint in MEB_CARS
|
||||
else ui_state.CP.alphaLongitudinalAvailable or ioniq6_settings_capable(ui_state.CP))))):
|
||||
else ui_state.CP.alphaLongitudinalAvailable or ioniq6_settings_capable(ui_state.CP)) or
|
||||
ui_state.is_release and not (meb_settings_capable(ui_state.CP) or ford_release_long_available(ui_state.CP))))):
|
||||
self._update_toggles()
|
||||
return
|
||||
ui_state.params.put_bool("AlphaLongitudinalEnabled", _state, block=True)
|
||||
|
||||
@@ -82,6 +82,10 @@ def native_matches_cp(CP, model: int, param: int) -> bool:
|
||||
config = hyundai.aol_safety_config(CP)
|
||||
return bool(config is not None and hyundai.native_accepts_cp(CP, model, param) and
|
||||
model == int(config.safetyModel.raw) and param == int(config.safetyParam))
|
||||
if CP.brand == 'ford':
|
||||
config = ford.aol_safety_config(CP)
|
||||
return bool(config is not None and ford.native_accepts_cp(CP, model, param) and
|
||||
model == int(config.safetyModel.raw) and param == int(config.safetyParam))
|
||||
if CP.brand == 'honda' and len(CP.safetyConfigs) == 2:
|
||||
config = honda.aol_safety_config(CP)
|
||||
return bool(config is not None and honda.native_accepts_cp(CP, model, param) and
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from dataclasses import replace
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.ford.aol import qualified, AOL_WORDS, temporary_restriction
|
||||
from opendbc.car.ford.aol import qualified, AOL_WORDS, MANUAL, temporary_restriction, aol_safety_config
|
||||
from openpilot.starpilot.aol.intent import AolCardIntent, AOL_TOGGLE
|
||||
from openpilot.starpilot.aol.policy import AolVehiclePolicy
|
||||
|
||||
@@ -9,7 +9,8 @@ def policy_for(cp):
|
||||
if qualified(cp):
|
||||
return AolVehiclePolicy(intent_supported=True, settings_supported=True, runtime_supported=True,
|
||||
normal_runtime_supported=True, ordinary_axis_ack_required=qualified(cp, marked_only=True),
|
||||
explicit_latch=True, alternative_experience_addition=32)
|
||||
explicit_latch=True, alternative_experience_addition=32,
|
||||
safety_param_addition=MANUAL if cp.transmissionType == structs.CarParams.TransmissionType.manual else 0)
|
||||
return AolVehiclePolicy()
|
||||
|
||||
|
||||
@@ -18,7 +19,7 @@ def native_profile_supported(model, param):
|
||||
|
||||
|
||||
def native_accepts_cp(cp, model, param):
|
||||
return qualified(cp, marked_only=True) and native_profile_supported(model, param) and param == cp.safetyConfigs[0].safetyParam
|
||||
return qualified(cp, marked_only=True) and native_profile_supported(model, param) and param == aol_safety_config(cp).safetyParam
|
||||
|
||||
|
||||
def native_latch_rejected(cp, native):
|
||||
@@ -134,9 +135,9 @@ def create_intent(cp, settings):
|
||||
|
||||
|
||||
def temporary_eps_restriction(cp, state):
|
||||
"""Exact stock Ford physical EPS pause; intent only, no native permission."""
|
||||
"""Ford physical EPS pause after LONG is off; intent only, no native permission."""
|
||||
try:
|
||||
return bool(qualified(cp, marked_only=True) and not cp.openpilotLongitudinalControl and
|
||||
return bool(qualified(cp, marked_only=True) and
|
||||
state.steerFaultTemporary and not state.steerFaultPermanent and state.canValid and not state.canTimeout and
|
||||
state.cruiseState.available and not state.cruiseState.enabled and
|
||||
state.gearShifter == structs.CarState.GearShifter.drive and
|
||||
@@ -148,9 +149,9 @@ def temporary_eps_restriction(cp, state):
|
||||
|
||||
|
||||
def temporary_eps_brake_recovery(cp, state):
|
||||
"""Stock LAT intent may outlive EPS loss while the brake remains held."""
|
||||
"""Lateral-only intent may outlive EPS loss while the brake remains held."""
|
||||
try:
|
||||
return bool(qualified(cp, marked_only=True) and not cp.openpilotLongitudinalControl and
|
||||
return bool(qualified(cp, marked_only=True) and
|
||||
state.brakePressed and not state.steerFaultTemporary and not state.steerFaultPermanent and
|
||||
state.canValid and not state.canTimeout and state.cruiseState.available and not state.cruiseState.enabled and
|
||||
state.gearShifter == structs.CarState.GearShifter.drive and not state.regenBraking and not state.gasPressed and
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
static inline bool ford_aol_param(uint16_t param) {
|
||||
bool result = false;
|
||||
switch (param) {
|
||||
switch (param & ~256U) {
|
||||
case 8U: case 9U: case 10U: case 11U: case 12U: case 13U:
|
||||
case 18U: case 19U: case 32U: case 33U: case 66U: case 67U:
|
||||
result = true;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Keep stock-Ford intent during a short command lease withdrawal; no grant."""
|
||||
"""Keep Ford lateral-only intent during a short command lease withdrawal; no grant."""
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.ford.aol import qualified
|
||||
from openpilot.cereal import log
|
||||
@@ -9,7 +9,7 @@ from openpilot.starpilot.aol.transport_pause import cp_identity
|
||||
def retain_command_pause_intent(feedback, sm, cp, cs, axis, now_ns, *, latched, producer):
|
||||
"""Retain only the original fresh LAT-only proof, without renewing any lease."""
|
||||
try:
|
||||
if (not qualified(cp, marked_only=True) or cp.openpilotLongitudinalControl or not latched or
|
||||
if (not qualified(cp, marked_only=True) or not latched or
|
||||
feedback.cp != cp_identity(cp) or feedback.failed or feedback.paused or feedback.pending_event or
|
||||
feedback.awaiting or feedback.retention_started_ns or feedback.retention_pending or feedback.retention_qualified or
|
||||
feedback.source_retention_started_ns or not feedback.lateral_only_baseline or not feedback.lateral_inventory or
|
||||
|
||||
@@ -113,8 +113,10 @@ def retained_gas_lateral_native(feedback, sm, cp, cs, axis, now_ns, *, latched,
|
||||
or native.longitudinalAllowed
|
||||
or native.observedMonoTime < feedback.lateral_native_ns
|
||||
or (native.pandaSerial, tuple(p.hardwareSerial for p in native.pandaInventory)) != feedback.lateral_inventory
|
||||
or not all(p.controlsAllowed for p in native.pandaInventory)
|
||||
or not all(p.controlsAllowed for p in sm['pandaStates'])
|
||||
or not all(bool(p.controlsAllowed) == (p.safetyModel == int(structs.CarParams.SafetyModel.ford))
|
||||
for p in native.pandaInventory)
|
||||
or not all(bool(p.controlsAllowed) == (c.safetyModel == structs.CarParams.SafetyModel.ford)
|
||||
for p, c in zip(sm['pandaStates'], cp.safetyConfigs, strict=True))
|
||||
):
|
||||
return None
|
||||
return native
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Exact Ford release opt-in before parser/controller construction."""
|
||||
from opendbc.car.ford.aol import qualified, aol_safety_config
|
||||
from openpilot.starpilot.saved_source import read_saved
|
||||
|
||||
|
||||
def prepare_release_long(cp, params, *, is_release, enabled):
|
||||
if not is_release or not enabled or cp.brand != 'ford':
|
||||
return cp
|
||||
try:
|
||||
if (cp.alternativeExperience != 0 or not qualified(cp) or not cp.alphaLongitudinalAvailable or
|
||||
cp.openpilotLongitudinalControl or read_saved(params, 'AlphaLongitudinalEnabled', 8) != (b'1', True) or
|
||||
read_saved(params, 'SafeMode', 8) not in ((None, True), (b'0', True))):
|
||||
return cp
|
||||
candidate = cp.as_reader().as_builder()
|
||||
aol_safety_config(candidate).safetyParam |= 1
|
||||
candidate.openpilotLongitudinalControl = True
|
||||
return candidate if qualified(candidate) else cp
|
||||
except (AttributeError, OSError, TypeError, ValueError, OverflowError, IndexError):
|
||||
return cp
|
||||
|
||||
|
||||
def release_long_available(cp):
|
||||
try:
|
||||
return bool(cp is not None and cp.brand == 'ford' and cp.alphaLongitudinalAvailable and qualified(cp))
|
||||
except (AttributeError, TypeError, ValueError, OverflowError, IndexError):
|
||||
return False
|
||||
@@ -113,7 +113,8 @@ def retain_physical_pause_intent(feedback, sm, cp, cs, axis, now_ns, *, latched,
|
||||
or bool(native.longitudinalAllowed) != (eps and long_active)
|
||||
or native.observedMonoTime < feedback.healthy_native_ns
|
||||
or (native.pandaSerial, tuple(p.hardwareSerial for p in native.pandaInventory)) != feedback.healthy_inventory
|
||||
or any(bool(p.controlsAllowed) != ordinary for p in native.pandaInventory)
|
||||
or any(bool(p.controlsAllowed) != (ordinary and p.safetyModel == int(structs.CarParams.SafetyModel.ford))
|
||||
for p in native.pandaInventory)
|
||||
or not sm.seen['pandaStates']
|
||||
or not sm.valid['pandaStates']
|
||||
or not sm.alive['pandaStates']
|
||||
@@ -125,7 +126,7 @@ def retain_physical_pause_intent(feedback, sm, cp, cs, axis, now_ns, *, latched,
|
||||
int(p.safetyModel.raw) == int(c.safetyModel.raw)
|
||||
and p.safetyParam == c.safetyParam
|
||||
and p.alternativeExperience == cp.alternativeExperience
|
||||
and bool(p.controlsAllowed) == ordinary
|
||||
and bool(p.controlsAllowed) == (ordinary and c.safetyModel == structs.CarParams.SafetyModel.ford)
|
||||
and not p.safetyRxChecksInvalid
|
||||
and not p.heartbeatLost
|
||||
and not p.faults
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Ford qualification for the shared physical Reverse/native join."""
|
||||
|
||||
from opendbc.car.ford.aol import qualified
|
||||
from openpilot.cereal import log
|
||||
from openpilot.starpilot.aol.reverse_intent import retain_reverse_intent as retain_qualified_reverse_intent
|
||||
|
||||
|
||||
@@ -10,4 +11,7 @@ def retain_reverse_intent(feedback, sm, cp, cs, axis, now_ns, *, latched, produc
|
||||
except (AttributeError, KeyError, IndexError, TypeError, ValueError, OverflowError):
|
||||
return False
|
||||
return retain_qualified_reverse_intent(feedback, sm, cp, cs, axis, now_ns, latched=latched, producer=producer,
|
||||
qualified_cp=qualified_cp)
|
||||
qualified_cp=qualified_cp,
|
||||
# The preceding brake USER_DISABLE can arrive with the first Reverse frame.
|
||||
# Retain only the existing fresh LAT-only proof; Reverse still denies axes.
|
||||
extra_user_events=(log.OnroadEvent.EventName.pedalPressed,))
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
"""Ford release opt-in reaches Galaxy's real capability and confirmation owner."""
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from opendbc.car import gen_empty_fingerprint
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.starpilot.aol.vehicle import policy_for
|
||||
from openpilot.starpilot.galaxy.settings import AuthorityContext, ContextSource, SettingsChanged, SettingsGateway
|
||||
from openpilot.starpilot.tests.ford_lifecycle_helpers import FORD_FACTORIES
|
||||
|
||||
|
||||
def fixture(identity, *, manual, offset, marked=False):
|
||||
params = Params()
|
||||
params.put_bool('IsReleaseBranch', True, block=True)
|
||||
fp = gen_empty_fingerprint()
|
||||
fp[offset + 2].update({0x3D6: 8, 0x186: 8})
|
||||
if not manual:
|
||||
fp[offset][0x5A] = 8
|
||||
cp = interfaces[identity].get_params(identity, fp, [], False, False, True)
|
||||
if marked:
|
||||
policy = policy_for(cp)
|
||||
cp.alternativeExperience |= policy.alternative_experience_addition
|
||||
cp.safetyConfigs[-1].safetyParam |= policy.safety_param_addition
|
||||
state = SimpleNamespace(parked=True, cp=cp, raw=cp.to_bytes())
|
||||
gateway = SettingsGateway(params, Mock(spec=ContextSource, sample=lambda: AuthorityContext(state.parked, state.cp, state.raw)), clock=lambda: 10)
|
||||
return params, state, gateway
|
||||
|
||||
|
||||
def row(gateway):
|
||||
page = gateway.page('vehicle', 'session', b'generation')
|
||||
index = next(i for i, item in enumerate(page['rows']) if item['label'] == 'Alpha Longitudinal')
|
||||
return page, index
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity', [identity for identity, _ in FORD_FACTORIES])
|
||||
@pytest.mark.parametrize('manual,offset,marked', [(False, 0, False), (True, 0, True), (False, 4, True), (True, 4, False)])
|
||||
def test_release_ford_exact_configs_reach_real_galaxy_capability(identity, manual, offset, marked):
|
||||
with OpenpilotPrefix():
|
||||
params, state, gateway = fixture(identity, manual=manual, offset=offset, marked=marked)
|
||||
try:
|
||||
original = state.cp.to_dict()
|
||||
page, index = row(gateway)
|
||||
assert page['rows'][index]['available'] and not params.get_bool('AlphaLongitudinalEnabled')
|
||||
assert state.cp.to_dict() == original
|
||||
finally:
|
||||
gateway.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('manual,offset', [(False, 0), (True, 0), (False, 4), (True, 4)])
|
||||
def test_release_ford_real_preview_confirm_on_off_and_parked_revocation(manual, offset):
|
||||
with OpenpilotPrefix():
|
||||
params, state, gateway = fixture('FORD_MUSTANG_MACH_E_MK1', manual=manual, offset=offset)
|
||||
try:
|
||||
original = state.cp.to_dict()
|
||||
for choice in ('On', 'Off'):
|
||||
page, index = row(gateway)
|
||||
intent = gateway.preview(page['view'], index, 0, 'session', b'generation', value=choice)
|
||||
assert gateway.confirm(intent['intent'], 'session', b'generation')
|
||||
assert params.get_bool('AlphaLongitudinalEnabled') == (choice == 'On')
|
||||
assert state.cp.to_dict() == original
|
||||
page, index = row(gateway)
|
||||
intent = gateway.preview(page['view'], index, 0, 'session', b'generation', value='On')
|
||||
state.parked = False
|
||||
with pytest.raises(SettingsChanged):
|
||||
gateway.confirm(intent['intent'], 'session', b'generation')
|
||||
assert not params.get_bool('AlphaLongitudinalEnabled')
|
||||
finally:
|
||||
gateway.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('failure', ['legacy', 'wrong-owner', 'automatic-manual-marker', 'passive', 'dashcam', 'not-car'])
|
||||
def test_release_ford_capability_rejects_invalid_native_owner_and_passive_config(failure):
|
||||
with OpenpilotPrefix():
|
||||
params, state, gateway = fixture('FORD_MUSTANG_MACH_E_MK1', manual=False, offset=0)
|
||||
try:
|
||||
if failure == 'legacy':
|
||||
state.cp.safetyConfigs[-1].safetyParam = 3
|
||||
elif failure == 'wrong-owner':
|
||||
state.cp.safetyConfigs[-1].safetyParam = 66
|
||||
elif failure == 'automatic-manual-marker':
|
||||
state.cp.safetyConfigs[-1].safetyParam |= 256
|
||||
else:
|
||||
setattr(state.cp, {'not-car': 'notCar', 'dashcam': 'dashcamOnly'}.get(failure, failure), True)
|
||||
state.raw = state.cp.to_bytes()
|
||||
page = gateway.page('vehicle', 'session', b'generation')
|
||||
assert not any(item['label'] == 'Alpha Longitudinal' for item in page['rows'])
|
||||
assert not params.get_bool('AlphaLongitudinalEnabled')
|
||||
finally:
|
||||
gateway.close()
|
||||
@@ -95,13 +95,17 @@ def ford_joined(identity, alpha, hands_free, monkeypatch, record_property):
|
||||
|
||||
for key, value in (('FordLateralMode', b'1'), ('PauseAOLOnBrake', b'0.0'), ('CancelButtonControl', b'0'), ('MainCruiseButtonControl', b'0')):
|
||||
Path(saved.get_param_path(key)).write_bytes(value)
|
||||
from pathlib import Path
|
||||
|
||||
Path(saved.get_param_path("FordHandsFreeCluster")).write_bytes(b"1" if hands_free else b"0")
|
||||
fp = gen_empty_fingerprint()
|
||||
fp[0][0x5A] = 8
|
||||
fp[0].update({0x3A6: 8, 0x3A7: 8})
|
||||
fp[2].update({0x3D6: 8, 0x186: 8})
|
||||
topology = getattr(monkeypatch, 'ford_topology', 'automatic')
|
||||
offset = 4 if topology == 'multi-panda' else 0
|
||||
manual = topology == 'manual' or topology == 'manual-multi'
|
||||
if topology == 'manual-multi':
|
||||
offset = 4
|
||||
if not manual:
|
||||
fp[offset][0x5A] = 8
|
||||
fp[offset].update({0x3A6: 8, 0x3A7: 8})
|
||||
fp[offset + 2].update({0x3D6: 8, 0x186: 8})
|
||||
name, base = identity
|
||||
observed = (name, fp, '0' * 17, [], structs.CarParams.FingerprintSource.can, True)
|
||||
try:
|
||||
@@ -112,15 +116,15 @@ def ford_joined(identity, alpha, hands_free, monkeypatch, record_property):
|
||||
host = card_module.Car()
|
||||
assert host.CP.alternativeExperience == 32 and host.aol_card_intent is not None
|
||||
assert str(host.CP.carFingerprint) == str(name)
|
||||
expected_long = bool(alpha and not release)
|
||||
expected_long = bool(alpha)
|
||||
assert host.CP.openpilotLongitudinalControl == expected_long
|
||||
assert host.CP.safetyConfigs[0].safetyParam == base | int(expected_long)
|
||||
assert host.CP.safetyConfigs[-1].safetyParam == base | int(expected_long) | (256 if manual else 0)
|
||||
record_property('ford_factory_identity', str(name))
|
||||
record_property('ford_saved_alpha', alpha)
|
||||
record_property('ford_release', release)
|
||||
record_property('ford_longitudinal', expected_long)
|
||||
record_property('ford_safety_word', int(host.CP.safetyConfigs[0].safetyParam))
|
||||
native.reset(host.CP.safetyConfigs[0].safetyParam)
|
||||
record_property('ford_safety_word', int(host.CP.safetyConfigs[-1].safetyParam))
|
||||
native.reset(host.CP.safetyConfigs[-1].safetyParam)
|
||||
native.word &= ~1
|
||||
# Real empty update registers lazily accessed parser messages before RX.
|
||||
# Card.controls_update owns CI initialization; no flag is assigned here.
|
||||
@@ -152,12 +156,14 @@ def ford_joined(identity, alpha, hands_free, monkeypatch, record_property):
|
||||
extras += [('SteeringPinion_Data_Alt', 0, {'StePinRelInit_An_Sns': 0}), ('ParkAid_Data', 0, {'ExtSteeringAngleReq2': 0})]
|
||||
if host.CP.flags & FordFlags.LKA_STEERING:
|
||||
extras += [('LateralMotionControl', 2, {})]
|
||||
extras += [('Side_Detect_L_Stat', 2, {}), ('Side_Detect_R_Stat', 2, {})]
|
||||
extras += [('Side_Detect_L_Stat', 2 if host.CP.flags & FordFlags.CANFD else 0, {}),
|
||||
('Side_Detect_R_Stat', 2 if host.CP.flags & FordFlags.CANFD else 0, {})]
|
||||
preframes += [native.packer.make_can_msg(message, bus, fields) for message, bus, fields in extras]
|
||||
state = host.CI.update([(10_000_000_000 + native.tick * 10_000_000, preframes)])
|
||||
state = host.CI.update([(10_000_000_000 + native.tick * 10_000_000, [(addr, data, bus + offset) for addr, data, bus in preframes])])
|
||||
assert not native.safety.get_controls_allowed() and native.safety.aol_get_permission_mask() == 0
|
||||
assert state.canValid and not state.canTimeout
|
||||
yield SimpleNamespace(host=host, native=native, safety=native.safety, initial_cp=host.CP.to_dict(), expected_long=expected_long, hands_free=hands_free)
|
||||
yield SimpleNamespace(host=host, native=native, safety=native.safety, initial_cp=host.CP.to_dict(),
|
||||
expected_long=expected_long, hands_free=hands_free, offset=offset)
|
||||
finally:
|
||||
native.tearDown()
|
||||
if host is not None:
|
||||
@@ -176,6 +182,7 @@ def run_actual_producer(ford_joined, monkeypatch, *, scenario='reverse', reengag
|
||||
steps = start_tick = native.tick
|
||||
warmup_waits = qualified_samples = 0
|
||||
active_lat_frames = active_long_frames = 0
|
||||
transition_trace = []
|
||||
long_parser = CANParser(DBC[host.CP.carFingerprint][Bus.pt], [('ACCDATA', 50)], 0)
|
||||
|
||||
class ClockMappedSafety:
|
||||
@@ -292,14 +299,17 @@ def run_actual_producer(ford_joined, monkeypatch, *, scenario='reverse', reengag
|
||||
extras.append(('ParkAid_Data', 0, {'ExtSteeringAngleReq2': 0}))
|
||||
if host.CP.flags & FordFlags.LKA_STEERING and steps % 5 == 0:
|
||||
extras.append(('LateralMotionControl', 2, {}))
|
||||
extras += [('Side_Detect_L_Stat', 2, {}), ('Side_Detect_R_Stat', 2, {})]
|
||||
extras += [('Side_Detect_L_Stat', 2 if host.CP.flags & FordFlags.CANFD else 0, {}),
|
||||
('Side_Detect_R_Stat', 2 if host.CP.flags & FordFlags.CANFD else 0, {})]
|
||||
frames += [native.packer.make_can_msg(name, bus, fields) for name, bus, fields in extras]
|
||||
frames = [(addr, data, bus + joined.offset) for addr, data, bus in frames]
|
||||
batch = messaging.log_from_bytes(card_module.can_list_to_can_capnp(frames)).as_builder()
|
||||
batch.logMonoTime = now[0]
|
||||
queued[:] = [batch.to_bytes()]
|
||||
config = host.CP.safetyConfigs[0]
|
||||
config = host.CP.safetyConfigs[-1]
|
||||
slot = PandaSlot(
|
||||
0, 'authority-panda', config.safetyModel.raw, config.safetyParam, 32, bool(safety.get_controls_allowed()), not bool(safety.safety_config_valid())
|
||||
len(host.CP.safetyConfigs) - 1, 'authority-panda', config.safetyModel.raw, config.safetyParam, 32,
|
||||
bool(safety.get_controls_allowed()), not bool(safety.safety_config_valid())
|
||||
)
|
||||
native_state = SafetyState(
|
||||
1,
|
||||
@@ -315,16 +325,20 @@ def run_actual_producer(ford_joined, monkeypatch, *, scenario='reverse', reengag
|
||||
'authority-panda',
|
||||
str(prior_axis.aolAxisState.sessionId) if prior_axis is not None else host.aol_axis_session(),
|
||||
sourcePandaStatesMonoTime=now[0],
|
||||
pandaInventory=(slot,),
|
||||
pandaInventory=(slot,) if len(host.CP.safetyConfigs) == 1 else (
|
||||
PandaSlot(0, 'silent-panda-0', host.CP.safetyConfigs[0].safetyModel.raw, 0, 32, False, False), slot),
|
||||
)
|
||||
wire = messaging.new_message('aolSafetyWire', 0, valid=True, logMonoTime=now[0])
|
||||
wire.aolSafetyWire = encode_safety(native_state)
|
||||
panda = messaging.new_message('pandaStates', 1, valid=True, logMonoTime=now[0])
|
||||
panda.pandaStates[0].safetyModel = config.safetyModel
|
||||
panda.pandaStates[0].safetyParam = config.safetyParam
|
||||
panda.pandaStates[0].alternativeExperience = 32
|
||||
panda.pandaStates[0].controlsAllowed = slot.controlsAllowed
|
||||
panda.pandaStates[0].safetyRxChecksInvalid = slot.safetyRxChecksInvalid
|
||||
panda = messaging.new_message('pandaStates', len(host.CP.safetyConfigs), valid=True, logMonoTime=now[0])
|
||||
panda.pandaStates[-1].safetyModel = config.safetyModel
|
||||
panda.pandaStates[-1].safetyParam = config.safetyParam
|
||||
panda.pandaStates[-1].alternativeExperience = 32
|
||||
panda.pandaStates[-1].controlsAllowed = slot.controlsAllowed
|
||||
panda.pandaStates[-1].safetyRxChecksInvalid = slot.safetyRxChecksInvalid
|
||||
if len(host.CP.safetyConfigs) == 2:
|
||||
panda.pandaStates[0].safetyModel = host.CP.safetyConfigs[0].safetyModel
|
||||
panda.pandaStates[0].alternativeExperience = 32
|
||||
device = messaging.new_message('deviceState', valid=True, logMonoTime=now[0])
|
||||
device.deviceState.started, device.deviceState.startedMonoTime = True, 9_000_000_000
|
||||
events = [wire.as_reader(), panda.as_reader(), device.as_reader()]
|
||||
@@ -364,6 +378,8 @@ def run_actual_producer(ford_joined, monkeypatch, *, scenario='reverse', reengag
|
||||
native_request_long=bool(native_request & 2),
|
||||
panda_controls_allowed=bool(safety.get_controls_allowed()),
|
||||
panda_fault=slot.safetyRxChecksInvalid,
|
||||
panda_state_message=panda,
|
||||
safety_message=wire,
|
||||
)
|
||||
assert state.to_dict() == parsed_before_producer, 'Authority harness changed actual parsed CarState'
|
||||
previous_sd.update(sd.pm.messages)
|
||||
@@ -380,7 +396,7 @@ def run_actual_producer(ford_joined, monkeypatch, *, scenario='reverse', reengag
|
||||
for frame in published['sendcan'].sendcan:
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
|
||||
assert safety.safety_tx_hook(libsafety_py.make_CANPacket(frame.address, frame.src, frame.dat))
|
||||
assert safety.safety_tx_hook(libsafety_py.make_CANPacket(frame.address, frame.src - joined.offset, frame.dat))
|
||||
data, address = frame.dat, frame.address
|
||||
if address in (0x3CA, 0x3D3, 0x3D6):
|
||||
active = ((data[4] >> 2) & 7) != 0 if address == 0x3D3 else ((data[0] >> 4) & 7) != 0 if address == 0x3D6 else (data[0] >> 5) in (2, 4)
|
||||
@@ -388,7 +404,7 @@ def run_actual_producer(ford_joined, monkeypatch, *, scenario='reverse', reengag
|
||||
assert command.latActive and permission & 1
|
||||
active_lat_frames += 1
|
||||
elif address == 0x186:
|
||||
long_parser.update([(source_ns, [(address, bytes(data), frame.src)])])
|
||||
long_parser.update([(source_ns, [(address, bytes(data), frame.src - joined.offset)])])
|
||||
active = bool(long_parser.vl['ACCDATA']['Cmbb_B_Enbl'])
|
||||
assert active == bool(command.longActive)
|
||||
active_long_frames += int(active)
|
||||
@@ -417,6 +433,11 @@ def run_actual_producer(ford_joined, monkeypatch, *, scenario='reverse', reengag
|
||||
assert not axis.longitudinalActive and not command.longActive
|
||||
elif command.longActive:
|
||||
assert permission & 2 and safety.get_controls_allowed()
|
||||
transition_trace.append((steps, str(state.gearShifter), bool(state.brakePressed), bool(host.aol_card_intent.allowed_latch),
|
||||
bool(command.latActive), bool(command.longActive), permission, native_request,
|
||||
str(axis.faultReason), bool(host.aol_transport_feedback.failed),
|
||||
str(host.aol_transport_feedback.pending_event),
|
||||
tuple(str(event.name) for event in host.sm['onroadEvents'])))
|
||||
return state, axis, sd_state, command
|
||||
|
||||
def joined_packet(frame):
|
||||
@@ -429,6 +450,38 @@ def run_actual_producer(ford_joined, monkeypatch, *, scenario='reverse', reengag
|
||||
assert 0 <= warmup_waits < 120 and qualified_samples >= 100
|
||||
assert host.ci_initialized # Read-only witness: real controls_update completed init.
|
||||
assert not host.aol_card_intent.allowed_latch
|
||||
if scenario == 'configuration-parity':
|
||||
for _ in range(40):
|
||||
state, axis, sd_state, command = step(engaged=True)
|
||||
assert command.latActive and host.aol_card_intent.allowed_latch
|
||||
assert bool(command.longActive) == joined.expected_long
|
||||
for _ in range(30):
|
||||
state, axis, sd_state, command = step(engaged=True, fault='gas')
|
||||
assert command.latActive and not command.longActive and host.aol_card_intent.allowed_latch
|
||||
for _ in range(30):
|
||||
state, axis, sd_state, command = step(engaged=True)
|
||||
assert command.latActive and bool(command.longActive) == joined.expected_long
|
||||
for _ in range(30):
|
||||
state, axis, sd_state, command = step(brake=True)
|
||||
assert host.aol_card_intent.allowed_latch and command.latActive and not command.longActive
|
||||
for _ in range(30):
|
||||
state, axis, sd_state, command = step(reverse=True)
|
||||
assert host.aol_card_intent.allowed_latch and not command.latActive and not command.longActive, transition_trace[-40:]
|
||||
for _ in range(40):
|
||||
state, axis, sd_state, command = step()
|
||||
assert command.latActive and not command.longActive
|
||||
for _ in range(40):
|
||||
state, axis, sd_state, command = step(fault='temporary-eps')
|
||||
assert host.aol_card_intent.allowed_latch and not command.latActive, transition_trace[-40:]
|
||||
for _ in range(40):
|
||||
state, axis, sd_state, command = step()
|
||||
assert command.latActive and not command.longActive
|
||||
for _ in range(30):
|
||||
state, axis, sd_state, command = step(main_off=True)
|
||||
assert not host.aol_card_intent.allowed_latch and not command.latActive and not command.longActive
|
||||
assert active_lat_frames > 0
|
||||
assert bool(active_long_frames) == joined.expected_long
|
||||
return
|
||||
assert scenario in ('route24', 'route24-mixed-eps', 'route2a', 'route2a-main', 'route2a-main-fault')
|
||||
if scenario in ('route24', 'route24-mixed-eps', 'route2a', 'route2a-main', 'route2a-main-fault'):
|
||||
mixed_eps = scenario in ('route24-mixed-eps', 'route2a', 'route2a-main', 'route2a-main-fault')
|
||||
|
||||
@@ -117,7 +117,7 @@ class TestFordAngleStartup(unittest.TestCase):
|
||||
observed[0][0x5A] = 8
|
||||
observed[2].update({0x3D6: 8, 0x186: 8})
|
||||
cp = CarInterface.get_params(identity, observed, [], alpha, release, False)
|
||||
long = alpha and (identity not in CANFD or not release)
|
||||
long = alpha
|
||||
self.assertEqual(cp.openpilotLongitudinalControl, long)
|
||||
self.assertTrue(cp.pcmCruise)
|
||||
self.assertEqual(cp.alternativeExperience, 0)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Qualified Ford configurations remain collected by both CI lanes."""
|
||||
import ast
|
||||
from tools.ci import run_vehicle_tests as runner
|
||||
|
||||
|
||||
FAMILY_TARGETS = (
|
||||
'openpilot/starpilot/tests/test_ford_configuration_parity.py',
|
||||
'openpilot/starpilot/tests/test_ford_configuration_admission.py',
|
||||
'openpilot/starpilot/tests/test_ford_release_long_preferences.py',
|
||||
'openpilot/selfdrive/car/tests/test_registered_ford_discovery.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_ford_configuration_parity.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_ford_alpha_aol.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_ford_full_engagement.py',
|
||||
)
|
||||
|
||||
|
||||
def test_both_actual_ci_selectors_collect_all_qualified_ford_configuration_contracts():
|
||||
for suite in ('aol-lifecycle', 'safety-release'):
|
||||
selected = runner.targets_for(suite)
|
||||
for path in FAMILY_TARGETS:
|
||||
assert path in selected
|
||||
assert (runner.ROOT / path).is_file()
|
||||
release = runner.targets_for('safety-release')
|
||||
prefix = 'opendbc_repo/opendbc/safety/tests/test_ford_mach_e_extended.py::'
|
||||
assert prefix + 'TestFordMachELongReleaseExtended' in release
|
||||
assert prefix + 'TestFordMachELongReleaseDenied' not in release
|
||||
source = ast.parse((runner.ROOT / prefix.split('::', 1)[0]).read_text())
|
||||
assert any(isinstance(node, ast.ClassDef) and node.name == 'TestFordMachELongReleaseExtended' for node in source.body)
|
||||
|
||||
|
||||
def test_actual_host_collector_discovers_release_ui_guards(tmp_path):
|
||||
from tools.ci.run_host_tests import pytest_files
|
||||
name = 'openpilot/starpilot/ui/tests/test_ford_release_alpha.py'
|
||||
target = tmp_path / name
|
||||
target.parent.mkdir(parents=True)
|
||||
target.write_bytes((runner.ROOT / name).read_bytes())
|
||||
assert name in pytest_files(tmp_path)
|
||||
@@ -0,0 +1,65 @@
|
||||
"""Ford dual-Panda and transmission admission remains exact and fail-closed."""
|
||||
from dataclasses import replace
|
||||
import pytest
|
||||
from opendbc.car import gen_empty_fingerprint, structs
|
||||
from opendbc.car.ford.interface import CarInterface
|
||||
from opendbc.car.ford.aol import qualified
|
||||
from openpilot.starpilot.aol.vehicle import native_matches_cp, policy_for
|
||||
from openpilot.starpilot.aol.runtime import native_inventory_matches_cp
|
||||
from openpilot.starpilot.aol.wire import PandaSlot, SafetyState
|
||||
from openpilot.starpilot.tests.ford_lifecycle_helpers import FORD_FACTORIES
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity,base', FORD_FACTORIES)
|
||||
@pytest.mark.parametrize('alpha', [False, True])
|
||||
@pytest.mark.parametrize('manual', [False, True])
|
||||
def test_exact_dual_owner_and_manual_identity(identity, base, alpha, manual):
|
||||
fp = gen_empty_fingerprint()
|
||||
if not manual:
|
||||
fp[4][0x5A] = 8
|
||||
fp[6].update({0x3D6: 8, 0x186: 8})
|
||||
cp = CarInterface.get_params(identity, fp, [], alpha, True, False)
|
||||
policy = policy_for(cp)
|
||||
cp.safetyConfigs[-1].safetyParam |= policy.safety_param_addition
|
||||
cp.alternativeExperience |= policy.alternative_experience_addition
|
||||
word = base | int(alpha) | (256 if manual else 0)
|
||||
assert cp.safetyConfigs[-1].safetyParam == word
|
||||
assert qualified(cp, marked_only=True) and native_matches_cp(cp, 6, word)
|
||||
for field, value in [('safetyParam', word ^ 256), ('safetyParam', word | 512), ('safetyModel', structs.CarParams.SafetyModel.noOutput)]:
|
||||
bad = cp.as_reader().as_builder()
|
||||
setattr(bad.safetyConfigs[-1], field, value)
|
||||
assert not qualified(bad) and not native_matches_cp(bad, 6, word)
|
||||
for field, value in [('safetyParam', 1), ('safetyModel', structs.CarParams.SafetyModel.ford)]:
|
||||
bad = cp.as_reader().as_builder()
|
||||
setattr(bad.safetyConfigs[0], field, value)
|
||||
assert not qualified(bad) and not native_matches_cp(bad, 6, word)
|
||||
bad = cp.as_reader().as_builder()
|
||||
bad.safetyConfigs = [cp.safetyConfigs[0], cp.safetyConfigs[0], cp.safetyConfigs[-1]]
|
||||
assert not qualified(bad)
|
||||
slots = (PandaSlot(0, 'passive', int(cp.safetyConfigs[0].safetyModel.raw), 0, 32, False, False), PandaSlot(1, 'ford', 6, word, 32, True, False))
|
||||
state = SafetyState(1, True, 100, 200, 6, word, True, bool(alpha), True, bool(alpha), 'ford', 'session',
|
||||
sourcePandaStatesMonoTime=100, pandaInventory=slots)
|
||||
assert native_inventory_matches_cp(state, cp)
|
||||
assert not native_inventory_matches_cp(replace(state, sourcePandaStatesMonoTime=0, pandaInventory=()), cp)
|
||||
for index, changes in [(0, {'controlsAllowed': True}), (0, {'safetyParam': 1}), (0, {'hardwareSerial': 'ford'}),
|
||||
(0, {'heartbeatLost': True}), (1, {'safetyRxChecksInvalid': True}),
|
||||
(1, {'slotIndex': 0}), (1, {'safetyParam': word ^ 1}), (1, {'alternativeExperience': 0})]:
|
||||
invalid = list(slots)
|
||||
invalid[index] = replace(invalid[index], **changes)
|
||||
assert not native_inventory_matches_cp(replace(state, pandaInventory=tuple(invalid)), cp)
|
||||
|
||||
|
||||
def test_marked_manual_policy_survives_cards_alternative_experience_reset():
|
||||
fp = gen_empty_fingerprint()
|
||||
cp = CarInterface.get_params(FORD_FACTORIES[0][0], fp, [], False, False, False)
|
||||
initial = policy_for(cp)
|
||||
assert cp.safetyConfigs[-1].safetyParam == 32 and initial.safety_param_addition == 256
|
||||
cp.safetyConfigs[-1].safetyParam |= initial.safety_param_addition
|
||||
cp.alternativeExperience |= initial.alternative_experience_addition
|
||||
assert native_matches_cp(cp, 6, 288)
|
||||
cp.alternativeExperience = 0 # Car resets this before reapplying saved settings.
|
||||
recovered = policy_for(cp)
|
||||
assert recovered.intent_supported and recovered.safety_param_addition == 256
|
||||
cp.safetyConfigs[-1].safetyParam |= recovered.safety_param_addition
|
||||
cp.alternativeExperience |= recovered.alternative_experience_addition
|
||||
assert native_matches_cp(cp, 6, 288)
|
||||
@@ -0,0 +1,19 @@
|
||||
"""Actual Ford factory, parser, Card, SelfdriveD, Controls and native matrix.
|
||||
|
||||
CAN, perception, USB and clocks are synthetic; no physical acceptance claim.
|
||||
"""
|
||||
import pytest
|
||||
from openpilot.starpilot.tests.ford_lifecycle_helpers import FORD_FACTORIES, ford_joined as ford_joined, run_actual_producer
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def configuration_topology(monkeypatch, topology):
|
||||
monkeypatch.ford_topology = topology
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity', FORD_FACTORIES)
|
||||
@pytest.mark.parametrize('alpha', [False, True])
|
||||
@pytest.mark.parametrize('hands_free', [False])
|
||||
@pytest.mark.parametrize('topology', ['automatic', 'manual', 'multi-panda', 'manual-multi'])
|
||||
def test_actual_configuration_lifecycle(ford_joined, monkeypatch):
|
||||
run_actual_producer(ford_joined, monkeypatch, scenario='configuration-parity')
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Manual AOL marking cannot replace existing ordinary/angle configuration words."""
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
import pytest
|
||||
from opendbc.car import car_helpers, gen_empty_fingerprint, structs
|
||||
from opendbc.car.ford.values import CAR
|
||||
from openpilot.cereal import messaging
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.car import card as card_module
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity,fd', [(CAR.FORD_ESCAPE_MK4, False), (CAR.FORD_MUSTANG_MACH_E_MK1, True)])
|
||||
@pytest.mark.parametrize('alpha', [False, True])
|
||||
@pytest.mark.parametrize('release', [False, True])
|
||||
@pytest.mark.parametrize('offset', [0, 4])
|
||||
def test_actual_manual_angle_startup_preserves_original_unmarked_owner(identity, fd, alpha, release, offset, monkeypatch):
|
||||
monkeypatch.setenv('SIMULATION', '1')
|
||||
monkeypatch.setenv('REPLAY', '1')
|
||||
monkeypatch.setenv('AOL_REPLAY_RUNTIME', '0')
|
||||
fp = gen_empty_fingerprint()
|
||||
fp[offset + 2].update({0x3D6: 8, 0x186: 8})
|
||||
observed = (identity, fp, '0' * 17, [], structs.CarParams.FingerprintSource.can, True)
|
||||
with OpenpilotPrefix():
|
||||
saved = Params()
|
||||
for key, value in [('OpenpilotEnabledToggle', True), ('AlwaysOnLateral', False), ('SafeMode', False),
|
||||
('AlphaLongitudinalEnabled', alpha), ('IsReleaseBranch', release), ('FordHumanTurnDetection', False)]:
|
||||
saved.put_bool(key, value, block=True)
|
||||
Path(saved.get_param_path('FordLateralMode')).write_bytes(b'1')
|
||||
with patch.object(car_helpers, 'fingerprint', return_value=observed), \
|
||||
patch.object(card_module, 'can_comm_callbacks', return_value=(lambda *args: [], lambda frames: None)), \
|
||||
patch.object(card_module.messaging, 'recv_one_retry', return_value=messaging.new_message('can', 1)):
|
||||
host = card_module.Car()
|
||||
try:
|
||||
assert host.CP.transmissionType == structs.CarParams.TransmissionType.manual
|
||||
assert host.CP.alternativeExperience == 0 and host.aol_card_intent is None
|
||||
assert host.CP.minEnableSpeed == pytest.approx(20 * .44704)
|
||||
assert host.CP.safetyConfigs[-1].safetyParam == 128 + (2 if fd else 0) + int(alpha and not release)
|
||||
assert host.CP.openpilotLongitudinalControl == bool(alpha and not release)
|
||||
assert host.CI.CC.bp_lat is not None
|
||||
if offset:
|
||||
assert host.CP.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.noOutput
|
||||
assert host.CP.safetyConfigs[0].safetyParam == 0
|
||||
assert saved.get_bool('AlphaLongitudinalEnabled') == alpha
|
||||
finally:
|
||||
host.vehicle_startup.close()
|
||||
@@ -0,0 +1,62 @@
|
||||
"""Saved Ford release intent is consumed before construction, with exact admission."""
|
||||
from types import SimpleNamespace
|
||||
import pytest
|
||||
from opendbc.car import gen_empty_fingerprint
|
||||
from opendbc.car.ford.interface import CarInterface
|
||||
from opendbc.car.ford.values import CAR
|
||||
from openpilot.starpilot.car.ford.longitudinal_preferences import prepare_release_long
|
||||
from openpilot.starpilot.tests.ford_lifecycle_helpers import FORD_FACTORIES
|
||||
|
||||
|
||||
def saved(tmp_path, alpha=b'1', safe=b'0'):
|
||||
for key, value in [('AlphaLongitudinalEnabled', alpha), ('SafeMode', safe)]:
|
||||
if value is not None:
|
||||
(tmp_path / key).write_bytes(value)
|
||||
return SimpleNamespace(get_param_path=lambda key: str(tmp_path / key))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity,base', FORD_FACTORIES)
|
||||
@pytest.mark.parametrize('manual', [False, True])
|
||||
@pytest.mark.parametrize('offset', [0, 4])
|
||||
def test_release_long_changes_only_selected_owner_and_long_selection(identity, base, manual, offset, tmp_path):
|
||||
fp = gen_empty_fingerprint()
|
||||
if not manual:
|
||||
fp[offset][0x5A] = 8
|
||||
fp[offset + 2].update({0x3D6: 8, 0x186: 8})
|
||||
stock = CarInterface.get_params(identity, fp, [], False, True, False)
|
||||
stock.carVin = '0' * 17
|
||||
stock.fuzzyFingerprint = True
|
||||
before = stock.to_dict()
|
||||
selected = prepare_release_long(stock, saved(tmp_path), is_release=True, enabled=True)
|
||||
expected = stock.as_reader().as_builder()
|
||||
expected.openpilotLongitudinalControl = True
|
||||
expected.safetyConfigs[-1].safetyParam = base | 1
|
||||
assert stock.to_dict() == before
|
||||
assert selected.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('alpha,safe,release,enabled', [
|
||||
(None, b'0', True, True), (b'0', b'0', True, True), (b'1junk', b'0', True, True),
|
||||
(b'1', b'1', True, True), (b'1', b'bad', True, True), (b'1', b'0', False, True),
|
||||
(b'1', b'0', True, False),
|
||||
])
|
||||
def test_release_opt_in_off_invalid_safe_mode_debug_and_disabled_stay_unchanged(alpha, safe, release, enabled, tmp_path):
|
||||
fp = gen_empty_fingerprint()
|
||||
fp[0][0x5A] = 8
|
||||
cp = CarInterface.get_params(CAR.FORD_MUSTANG_MACH_E_MK1, fp, [], False, True, False)
|
||||
assert prepare_release_long(cp, saved(tmp_path, alpha, safe), is_release=release, enabled=enabled) is cp
|
||||
|
||||
|
||||
@pytest.mark.parametrize('restriction', ['secoc', 'extra-panda', 'angle', 'foreign-brand'])
|
||||
def test_release_never_promotes_unowned_configuration(restriction, tmp_path):
|
||||
fp = gen_empty_fingerprint()
|
||||
fp[0][0x5A] = 8
|
||||
fp[2].update({0x3D6: 16 if restriction == 'secoc' else 8, 0x186: 16 if restriction == 'secoc' else 8})
|
||||
cp = CarInterface.get_params(CAR.FORD_MUSTANG_MACH_E_MK1, fp, [], False, True, False)
|
||||
if restriction == 'extra-panda':
|
||||
cp.safetyConfigs = [cp.safetyConfigs[0], cp.safetyConfigs[0]]
|
||||
elif restriction == 'angle':
|
||||
cp.safetyConfigs[-1].safetyParam = 130
|
||||
elif restriction == 'foreign-brand':
|
||||
cp.brand = 'hyundai'
|
||||
assert prepare_release_long(cp, saved(tmp_path), is_release=True, enabled=True) is cp
|
||||
@@ -340,3 +340,24 @@ def test_newer_active_proof_cannot_extend_original_healthy_source_lease():
|
||||
|
||||
def test_reverse_qualification_keeps_malformed_configuration_fail_closed():
|
||||
assert not retain_reverse_intent(None, None, None, None, None, NOW, latched=True, producer=PRODUCER)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('event,immediate,soft,expected', [
|
||||
('pedalPressed', False, False, True), ('pedalPressed', True, False, False),
|
||||
('pedalPressed', False, True, False), ('controlsMismatch', True, False, False),
|
||||
('steerUnavailable', True, False, False),
|
||||
])
|
||||
def test_brake_user_disable_at_reverse_join_retains_only_existing_lateral_intent(event, immediate, soft, expected):
|
||||
cp, cs, feedback = baseline()
|
||||
cs.gearShifter = structs.CarState.GearShifter.reverse
|
||||
sm, native = sample(cp, NOW + 10_000_000, NOW + 20_000_000, 2, allowed=False)
|
||||
change(sm, 'onroadEvents', lambda events: setattr(events[0], 'name', event))
|
||||
change(sm, 'onroadEvents', lambda events: setattr(events[0], 'immediateDisable', immediate))
|
||||
change(sm, 'onroadEvents', lambda events: setattr(events[0], 'softDisable', soft))
|
||||
before = (feedback.healthy_feedback_ns, feedback.healthy_native_ns, feedback.lateral_baseline_ns,
|
||||
feedback.lateral_source_ns, feedback.lateral_native_ns)
|
||||
assert retain_reverse_intent(feedback, sm, cp, cs, sm['aolAxisState'], NOW + 20_000_000,
|
||||
latched=True, producer=PRODUCER) == expected
|
||||
assert before == (feedback.healthy_feedback_ns, feedback.healthy_native_ns, feedback.lateral_baseline_ns,
|
||||
feedback.lateral_source_ns, feedback.lateral_native_ns)
|
||||
assert not native.lateralAllowed and not native.longitudinalAllowed
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""The HKG healthy-stop exception cannot widen a Ford native denial."""
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from opendbc.car import gen_empty_fingerprint
|
||||
from opendbc.car.ford.interface import CarInterface
|
||||
from openpilot.starpilot.aol.vehicle import policy_for
|
||||
from openpilot.starpilot.car.hyundai.aol import expected_stopped_lateral_denial
|
||||
from openpilot.starpilot.tests.ford_lifecycle_helpers import FORD_FACTORIES
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity,base', FORD_FACTORIES)
|
||||
@pytest.mark.parametrize('alpha', [False, True])
|
||||
@pytest.mark.parametrize('manual,offset', [(False, 0), (True, 0), (False, 4), (True, 4)])
|
||||
def test_hkg_stop_exception_never_qualifies_ford(identity, base, alpha, manual, offset, monkeypatch):
|
||||
fp = gen_empty_fingerprint()
|
||||
if not manual:
|
||||
fp[offset][0x5A] = 8
|
||||
fp[offset + 2].update({0x3D6: 8, 0x186: 8})
|
||||
cp = CarInterface.get_params(identity, fp, [], alpha, True, False)
|
||||
policy = policy_for(cp)
|
||||
cp.safetyConfigs[-1].safetyParam |= policy.safety_param_addition
|
||||
cp.alternativeExperience |= policy.alternative_experience_addition
|
||||
assert cp.safetyConfigs[-1].safetyParam == (base | int(alpha) | (256 if manual else 0))
|
||||
transport = Mock(side_effect=AssertionError('Ford must reject the HKG-only exception before native transport'))
|
||||
monkeypatch.setattr('openpilot.starpilot.aol.transport_pause._configured_transport_native', transport)
|
||||
assert not expected_stopped_lateral_denial(cp, SimpleNamespace(), SimpleNamespace(), sm=None, axis_session_id='ford', now_ns=100)
|
||||
transport.assert_not_called()
|
||||
from openpilot.starpilot.car.hyundai.aol import source_deadline_supported
|
||||
from opendbc.car.hyundai.canfd_stock_aol import qualified as hkg_stock_qualified
|
||||
assert not source_deadline_supported(cp)
|
||||
assert not hkg_stock_qualified(cp, marked_only=True)
|
||||
from openpilot.starpilot.car.volkswagen.aol import qualified as vw_qualified, temporary_eps_restriction
|
||||
assert not vw_qualified(cp, marked_only=True)
|
||||
assert not temporary_eps_restriction(cp, SimpleNamespace())
|
||||
|
||||
from openpilot.starpilot.car.subaru.smoothing import prepare, install, read_frozen, control_maximum, seconds, filter_action
|
||||
before = cp.to_dict()
|
||||
params = Mock()
|
||||
assert prepare(cp, object()).to_dict() == before
|
||||
assert install(cp, b'ford', object(), params) is None
|
||||
assert read_frozen(cp, params) == 0. and control_maximum(SimpleNamespace(CP=cp)) == 0.
|
||||
assert not params.mock_calls
|
||||
action = SimpleNamespace(desiredCurvature=.001, desiredAcceleration=.5)
|
||||
previous = SimpleNamespace(desiredCurvature=.002)
|
||||
for speed in (0., .3, 5., 20.):
|
||||
tau = seconds(speed, 0.)
|
||||
assert tau == 0.
|
||||
filter_action(action, previous, speed, tau)
|
||||
assert vars(action) == {'desiredCurvature': .001, 'desiredAcceleration': .5}
|
||||
@@ -198,11 +198,12 @@ class FeatureSettingsOwner:
|
||||
try:
|
||||
from opendbc.car.values import PLATFORMS
|
||||
from openpilot.starpilot.car.hyundai.aol import ioniq6_settings_capable
|
||||
from openpilot.starpilot.car.ford.longitudinal_preferences import release_long_available as ford_release_long_available
|
||||
platform = PLATFORMS.get(str(cp.carFingerprint))
|
||||
if (not self._galaxy() or platform is None or platform.__class__.__module__.split('.')[-2] != str(cp.brand) or
|
||||
not readable or release not in (None, b'0', b'1') or cp.notCar or cp.passive or cp.dashcamOnly or
|
||||
not (cp.alphaLongitudinalAvailable or ioniq6_settings_capable(cp)) or
|
||||
release == b'1'):
|
||||
(release == b'1' and not ford_release_long_available(cp))):
|
||||
return None
|
||||
return (str(cp.carFingerprint), str(cp.brand), int(cp.flags), int(cp.alternativeExperience),
|
||||
bool(cp.alphaLongitudinalAvailable), bool(cp.openpilotLongitudinalControl), bool(cp.pcmCruise),
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
"""Both actual Developer panels expose only qualified Ford release opt-in."""
|
||||
from tempfile import TemporaryDirectory
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
import pytest
|
||||
from opendbc.car import gen_empty_fingerprint
|
||||
from opendbc.car.ford.interface import CarInterface
|
||||
from opendbc.car.ford.values import CAR
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.starpilot.tests.ford_lifecycle_helpers import FORD_FACTORIES
|
||||
from openpilot.starpilot.ui.tests.test_ioniq6_alpha_request import Toggle
|
||||
from openpilot.selfdrive.ui.layouts.settings import developer as large
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings import developer as compact
|
||||
|
||||
|
||||
def panel_for(module, cp, params):
|
||||
ui = SimpleNamespace(CP=cp.as_reader(), params=params, is_release=True, engaged=False, has_longitudinal_control=False,
|
||||
is_offroad=lambda: True, update_params=lambda: None)
|
||||
layout = large.DeveloperLayout if module is large else compact.DeveloperLayoutMici
|
||||
panel = layout.__new__(layout)
|
||||
panel._alpha_long_toggle = Toggle()
|
||||
panel._alpha_long_toggle.title, panel._alpha_long_toggle.description = 'Alpha Long', 'Existing alpha confirmation'
|
||||
panel._long_maneuver_toggle = Toggle()
|
||||
panel._lat_maneuver_toggle = Toggle()
|
||||
if module is large:
|
||||
panel._params, panel._is_release = params, True
|
||||
for name in ('_joystick_toggle', '_adb_toggle', '_ssh_toggle', '_ui_debug_toggle', '_cellular_disabled_toggle'):
|
||||
setattr(panel, name, Toggle())
|
||||
else:
|
||||
panel._refresh_toggles = (('AlphaLongitudinalEnabled', panel._alpha_long_toggle),)
|
||||
return panel, ui
|
||||
|
||||
|
||||
def ford(identity, manual, offset):
|
||||
fp = gen_empty_fingerprint()
|
||||
if not manual:
|
||||
fp[offset][0x5A] = 8
|
||||
fp[offset + 2].update({0x3D6: 8, 0x186: 8})
|
||||
return CarInterface.get_params(identity, fp, [], False, True, False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('module', [large, compact])
|
||||
@pytest.mark.parametrize('identity,base', FORD_FACTORIES)
|
||||
@pytest.mark.parametrize('manual,offset', [(False, 0), (True, 0), (False, 4), (True, 4)])
|
||||
def test_actual_release_panels_show_registered_ford_and_preserve_request(module, identity, base, manual, offset):
|
||||
cp = ford(identity, manual, offset)
|
||||
before = cp.to_dict()
|
||||
with TemporaryDirectory() as directory:
|
||||
params = Params(directory)
|
||||
params.put_bool('AlphaLongitudinalEnabled', True, block=True)
|
||||
panel, ui = panel_for(module, cp, params)
|
||||
with patch.object(module, 'ui_state', ui):
|
||||
panel._update_toggles()
|
||||
assert panel._alpha_long_toggle.visible is True
|
||||
cp.passive = True
|
||||
panel._update_toggles()
|
||||
assert panel._alpha_long_toggle.visible is False
|
||||
assert params.get_bool('AlphaLongitudinalEnabled')
|
||||
cp.passive = False
|
||||
ui.CP = cp.as_reader().as_builder()
|
||||
ui.CP.brand = 'hyundai'
|
||||
panel._update_toggles()
|
||||
assert panel._alpha_long_toggle.visible is False
|
||||
assert cp.to_dict() == before
|
||||
|
||||
|
||||
@pytest.mark.parametrize('module', [large, compact])
|
||||
@pytest.mark.parametrize('confirmation_state', ['valid', 'onroad', 'foreign'])
|
||||
def test_actual_release_confirmation_rechecks_offroad_and_exact_ford_before_saving(module, confirmation_state):
|
||||
cp = ford(CAR.FORD_MUSTANG_MACH_E_MK1, False, 4)
|
||||
before = cp.to_dict()
|
||||
with TemporaryDirectory() as directory:
|
||||
params = Params(directory)
|
||||
panel, ui = panel_for(module, cp, params)
|
||||
callbacks = []
|
||||
def dialog(*args, **kwargs):
|
||||
callbacks.append(kwargs['callback'] if module is large else args[0])
|
||||
return object()
|
||||
confirmation = 'ConfirmDialog' if module is large else 'AlphaLongConfirmPage'
|
||||
with patch.object(module, 'ui_state', ui), patch.object(module, confirmation, side_effect=dialog), \
|
||||
patch.object(module.gui_app, 'push_widget'), patch.object(panel, '_update_toggles'):
|
||||
panel._on_alpha_long_enabled(True)
|
||||
assert len(callbacks) == 1 and not params.get_bool('AlphaLongitudinalEnabled')
|
||||
if confirmation_state == 'onroad':
|
||||
ui.is_offroad = lambda: False
|
||||
elif confirmation_state == 'foreign':
|
||||
ui.CP = cp.as_reader().as_builder()
|
||||
ui.CP.brand = 'hyundai'
|
||||
if module is large:
|
||||
callbacks[0](module.DialogResult.CONFIRM)
|
||||
else:
|
||||
with patch.object(module, 'restart_needed_callback'):
|
||||
callbacks[0]()
|
||||
assert params.get_bool('AlphaLongitudinalEnabled') == (confirmation_state == 'valid')
|
||||
assert cp.to_dict() == before
|
||||
@@ -24,6 +24,14 @@ from tools.ci import vehicle_pytest
|
||||
# Representative production callers plus their source/native refusal cases.
|
||||
# The emitted selected_test_ids record exact parametrized configurations.
|
||||
AOL_LIFECYCLE_TARGETS = (
|
||||
'openpilot/starpilot/tests/test_ford_configuration_parity.py',
|
||||
'openpilot/starpilot/tests/test_ford_configuration_admission.py',
|
||||
'openpilot/starpilot/tests/test_ford_release_long_preferences.py',
|
||||
'openpilot/selfdrive/car/tests/test_registered_ford_discovery.py',
|
||||
'openpilot/starpilot/tests/test_ford_ci_registration.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_ford_configuration_parity.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_ford_alpha_aol.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_ford_full_engagement.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_hkg_mode_parity.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_pe_stopped_callers.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_pe_combined_callers.py',
|
||||
@@ -122,6 +130,7 @@ AOL_LIFECYCLE_TARGETS = (
|
||||
)
|
||||
|
||||
AOL_LIFECYCLE_SCOPE = {
|
||||
"ford": "14 stock/Alpha identities, RELEASE CAN-FD Alpha, auto/manual, one/two Pandas; actual acquisition and caller/native lifecycle guards",
|
||||
"rivian": ("GEN1 base stock/DEBUG Alpha: actual factory/Card/SD/Controls/native engagement, brake LAT-only, gas, standstill zero output, " +
|
||||
"Reverse/Drive, temporary ToI recovery and physical UP_1 cancel; GEN2/unknown profiles refused"),
|
||||
"mazda": ("CX5 2022/CX9 2021 GEN1 stock AE32: engagement/brake/Cancel pause/toggle/Reverse/standstill/EPS recovery; " +
|
||||
@@ -158,6 +167,14 @@ DEBUG_SAFETY_CLASS_TARGETS = {
|
||||
|
||||
|
||||
RELEASE_TARGETS = (
|
||||
'openpilot/starpilot/tests/test_ford_configuration_parity.py',
|
||||
'openpilot/starpilot/tests/test_ford_configuration_admission.py',
|
||||
'openpilot/starpilot/tests/test_ford_release_long_preferences.py',
|
||||
'openpilot/selfdrive/car/tests/test_registered_ford_discovery.py',
|
||||
'openpilot/starpilot/tests/test_ford_ci_registration.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_ford_configuration_parity.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_ford_alpha_aol.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_ford_full_engagement.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_pe_combined_aol.py::TestPECombinedReleaseDenied',
|
||||
'openpilot/starpilot/aol/tests/test_pe_combined_profile_contract.py',
|
||||
'opendbc_repo/opendbc/safety/tests/test_palisade_stock_aol_status.py',
|
||||
@@ -267,7 +284,7 @@ RELEASE_TARGETS = (
|
||||
"openpilot/starpilot/tests/test_ford_aol_intent.py",
|
||||
"opendbc_repo/opendbc/safety/tests/test_ford_aol.py",
|
||||
"opendbc_repo/opendbc/safety/tests/test_ford_mach_e_extended.py::TestFordMachEExtended",
|
||||
"opendbc_repo/opendbc/safety/tests/test_ford_mach_e_extended.py::TestFordMachELongReleaseDenied",
|
||||
"opendbc_repo/opendbc/safety/tests/test_ford_mach_e_extended.py::TestFordMachELongReleaseExtended",
|
||||
"opendbc_repo/opendbc/car/ford/tests/test_added_firmware_identity.py",
|
||||
"opendbc_repo/opendbc/car/ford/tests/test_required_source_health.py",
|
||||
"opendbc_repo/opendbc/car/ford/tests/test_blend_settings.py",
|
||||
|
||||
Reference in New Issue
Block a user