mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
Keep automatic Ford discovery on the normal CAN harness
This commit is contained in:
@@ -11,7 +11,8 @@ from opendbc.car.fw_versions import (ObdCallback, get_fw_versions, get_fw_versio
|
||||
from opendbc.car.mock.values import CAR as MOCK
|
||||
from opendbc.car.gm.pedal_capability import automatic_bolt_candidate
|
||||
from opendbc.car.gm.discovery import automatic_volt_candidate
|
||||
from opendbc.car.ford.discovery import normal_bus0_selection as ford_normal_bus0_selection, normal_bus0_queries as ford_normal_bus0_queries
|
||||
from opendbc.car.ford.discovery import (normal_bus0_selection as ford_normal_bus0_selection,
|
||||
normal_bus0_queries as ford_normal_bus0_queries, normal_bus_query_brand)
|
||||
from opendbc.car.hyundai.discovery import normal_bus_topology
|
||||
from opendbc.car.values import BRANDS
|
||||
from opendbc.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN
|
||||
@@ -143,15 +144,16 @@ def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_mu
|
||||
car_fw = get_fw_versions(can_recv, can_send, set_obd_multiplexing,
|
||||
query_brand='ford', normal_bus_only=True)
|
||||
ecu_rx_addrs = {(fw.responseAddress, fw.subAddress or None, fw.bus) for fw in car_fw if fw.responseAddress > 0}
|
||||
elif normal_bus_topology(can_recv):
|
||||
elif (normal_brand := normal_bus_query_brand(can_recv, normal_bus_topology)) is not None:
|
||||
|
||||
set_obd_multiplexing(False)
|
||||
warmup_remaining = 0.1 - (time.monotonic() - start_time)
|
||||
if warmup_remaining > 0:
|
||||
time.sleep(warmup_remaining)
|
||||
vin_rx_addr, vin_rx_bus, vin = get_vin(can_recv, can_send, (0, 1))
|
||||
vin_buses = (0,) if normal_brand == 'ford' else (0, 1)
|
||||
vin_rx_addr, vin_rx_bus, vin = get_vin(can_recv, can_send, vin_buses)
|
||||
car_fw = get_fw_versions(can_recv, can_send, set_obd_multiplexing,
|
||||
query_brand='hyundai', normal_bus_only=True)
|
||||
query_brand=normal_brand, normal_bus_only=True)
|
||||
ecu_rx_addrs = {(fw.responseAddress, fw.subAddress or None, fw.bus) for fw in car_fw if fw.responseAddress > 0}
|
||||
else:
|
||||
# enable OBD multiplexing for VIN query
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
"""Normal-bus discovery for explicitly selected Ford CAN-FD vehicles."""
|
||||
import time
|
||||
|
||||
from opendbc.car.ford.values import CAR, FordFlags
|
||||
|
||||
|
||||
@@ -34,3 +36,42 @@ def normal_bus0_queries():
|
||||
def normal_bus0_selection(candidate):
|
||||
return bool(candidate in CAR and CAR(candidate).config.flags & FordFlags.CANFD and
|
||||
not CAR(candidate).config.flags & FordFlags.LKA_STEERING and normal_bus0_queries())
|
||||
|
||||
|
||||
# Normal harness PT/chassis and stock-camera messages. This selects only a
|
||||
# diagnostic query family; live VIN, ECU responses and fingerprinting still
|
||||
# determine the vehicle and its control configuration.
|
||||
NORMAL_BUS0_LAYOUT = {(0, 0x82): 8, (0, 0x165): 8, (0, 0x204): 8,
|
||||
(0, 0x217): 8, (2, 0x3CA): 8, (2, 0x3D6): 8}
|
||||
|
||||
|
||||
def normal_bus_query_brand(can_recv, hyundai_probe, *, clock=time.monotonic):
|
||||
# Share the existing bounded passive HKG read, rather than consuming a second
|
||||
# startup window or enabling the OBD mux to find out whether this is a Ford.
|
||||
received = {}
|
||||
malformed = False
|
||||
reads = 0
|
||||
deadline = clock() + .12
|
||||
|
||||
def observe(**kwargs):
|
||||
nonlocal malformed, reads
|
||||
reads += 1
|
||||
packets = can_recv(**kwargs)
|
||||
for packet in packets:
|
||||
for frame in packet:
|
||||
key = (frame.src, frame.address)
|
||||
if key in NORMAL_BUS0_LAYOUT:
|
||||
length = len(frame.dat)
|
||||
malformed |= length != NORMAL_BUS0_LAYOUT[key]
|
||||
received[key] = length
|
||||
return packets
|
||||
|
||||
if hyundai_probe(observe):
|
||||
return 'hyundai'
|
||||
# A Ford CAN1 frame can invalidate an HKG-only layout on the first read.
|
||||
# Complete Ford evidence within the original time/read budget, not a new probe.
|
||||
while not malformed and received != NORMAL_BUS0_LAYOUT and reads < 24 and clock() < deadline:
|
||||
observe(wait_for_one=True)
|
||||
if not malformed and received == NORMAL_BUS0_LAYOUT and normal_bus0_queries():
|
||||
return 'ford'
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
"""Cold automatic Ford discovery must not put CAN2 on the unrelated OBD bus."""
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car import car_helpers, structs
|
||||
from opendbc.car.can_definitions import CanData
|
||||
from opendbc.car.ford.discovery import normal_bus_query_brand
|
||||
from opendbc.car.ford.values import CAR
|
||||
from opendbc.car.hyundai.discovery import NORMAL_BUS_LAYOUT, normal_bus_topology
|
||||
from openpilot.selfdrive.car.tests.test_registered_ford_discovery import FordEcus, VIN
|
||||
|
||||
# Six original bus/address/DLC/payload samples from bfef.../00000003.
|
||||
RECORDED = (
|
||||
(0, 130, '770814008b004000'),
|
||||
(0, 357, '50c0000000000000'),
|
||||
(0, 516, 'd1267d0000d10000'),
|
||||
(0, 535, '268f268b26a226aa'),
|
||||
(2, 970, '1e7d17f200000000'),
|
||||
(2, 982, '051f7b8f9a067ffc'),
|
||||
)
|
||||
|
||||
|
||||
def recorded_frames():
|
||||
return [CanData(address, bytes.fromhex(data), bus) for bus, address, data in RECORDED]
|
||||
|
||||
|
||||
def brand(frames):
|
||||
return normal_bus_query_brand(lambda **kwargs: [frames], normal_bus_topology)
|
||||
|
||||
|
||||
def test_recorded_normal_harness_selects_ford_queries_only():
|
||||
assert brand(recorded_frames()) == 'ford'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('missing', range(6))
|
||||
def test_partial_layout_does_not_choose_ford(missing):
|
||||
frames = recorded_frames()
|
||||
del frames[missing]
|
||||
assert brand(frames) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('bad', range(6))
|
||||
def test_wrong_bus_dlc_and_returned_frames_do_not_choose_ford(bad):
|
||||
for bus_delta, size in ((1, 8), (128, 8), (0, 7), (0, 12)):
|
||||
frames = recorded_frames()
|
||||
original = frames[bad]
|
||||
frames[bad] = CanData(original.address, bytes(size), original.src + bus_delta)
|
||||
assert brand(frames) is None
|
||||
|
||||
|
||||
def test_conflicting_dlc_cannot_be_overwritten_by_a_good_copy():
|
||||
frames = recorded_frames()
|
||||
assert brand([CanData(frames[0].address, bytes(7), frames[0].src), *frames]) is None
|
||||
|
||||
|
||||
def test_existing_hyundai_and_unknown_layouts_keep_their_paths():
|
||||
frames = [CanData(address, bytes(length), bus) for (bus, address), length in NORMAL_BUS_LAYOUT.items()]
|
||||
assert brand(frames) == 'hyundai'
|
||||
assert brand([]) is None
|
||||
assert brand([*frames, *recorded_frames()]) == 'hyundai'
|
||||
|
||||
|
||||
def test_query_configuration_must_still_allow_normal_ford_bus():
|
||||
with mock.patch('opendbc.car.ford.discovery.normal_bus0_queries', return_value=False):
|
||||
assert brand(recorded_frames()) is None
|
||||
|
||||
|
||||
def test_shared_probe_uses_existing_read_bound():
|
||||
calls = []
|
||||
def recv(**kwargs):
|
||||
calls.append(kwargs)
|
||||
return []
|
||||
assert normal_bus_query_brand(recv, lambda recv: normal_bus_topology(recv, clock=lambda: 1.)) is None
|
||||
assert len(calls) == 24
|
||||
assert all(call == {'wait_for_one': True} for call in calls)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('firmware_known', [False, True])
|
||||
def test_cold_automatic_uses_real_vin_isotp_fw_and_identity_matching(firmware_known):
|
||||
firmware = {(0x706, b'\x22\xf1\x88'): b'RJ6T-14H102-BAE'.ljust(24, b'\0'),
|
||||
(0x730, b'\x22\xf1\x88'): b'LJ9C-14D003-FA'.ljust(24, b'\0'),
|
||||
(0x760, b'\x22\xf1\x88'): b'LK9C-2D053-CN'.ljust(24, b'\0'),
|
||||
(0x764, b'\x22\xf1\x88'): b'ML3T-14D049-AL'.ljust(24, b'\0')}
|
||||
if not firmware_known:
|
||||
firmware = dict.fromkeys(firmware, b'unknown')
|
||||
transport = FordEcus(firmware=firmware, background=recorded_frames())
|
||||
with mock.patch.dict(car_helpers.os.environ, {'FINGERPRINT': '', 'SKIP_FW_QUERY': '', 'DISABLE_FW_CACHE': ''}), \
|
||||
mock.patch.object(car_helpers.time, 'monotonic', side_effect=lambda: transport.clock), \
|
||||
mock.patch.object(car_helpers.time, 'sleep', side_effect=transport.sleep):
|
||||
ci = car_helpers.get_car(transport.recv, transport.send, transport.mux, False, False)
|
||||
assert transport.mux_calls and set(transport.mux_calls) == {False}
|
||||
assert transport.sent and all(frame.src in (0, 4) for frame in transport.sent)
|
||||
assert any(frame.dat[0] >> 4 == 3 for frame in transport.sent)
|
||||
if firmware_known:
|
||||
assert ci.CP.carFingerprint == CAR.FORD_MUSTANG_MACH_E_MK1
|
||||
assert ci.CP.fingerprintSource == structs.CarParams.FingerprintSource.fw
|
||||
assert ci.CP.carVin == VIN
|
||||
assert {(fw.address, fw.fwVersion) for fw in ci.CP.carFw} == {(address, data) for (address, _), data in firmware.items()}
|
||||
else:
|
||||
assert ci.CP.carFingerprint != CAR.FORD_MUSTANG_MACH_E_MK1
|
||||
assert not ci.CP.safetyConfigs or ci.CP.safetyConfigs[-1].safetyModel != structs.CarParams.SafetyModel.ford
|
||||
|
||||
|
||||
def test_hyundai_mismatch_does_not_abort_remaining_ford_window():
|
||||
packets = iter([[CanData(0x1A0, bytes(8), 1)], recorded_frames()])
|
||||
def recv(**kwargs):
|
||||
return [next(packets, [])]
|
||||
assert normal_bus_query_brand(recv, normal_bus_topology) == 'ford'
|
||||
|
||||
|
||||
def test_hyundai_early_exit_does_not_extend_outer_deadline():
|
||||
calls = []
|
||||
times = iter((1., 1.13))
|
||||
def recv(**kwargs):
|
||||
calls.append(kwargs)
|
||||
return []
|
||||
assert normal_bus_query_brand(recv, lambda recv: False, clock=lambda: next(times)) is None
|
||||
assert not calls
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "d97d73289304727972ae534f32902cf5d207ea61af92426673df8a2fd4b6ed60", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "10eb839743b1c431389b7e65d78621ddc6794ba3d1b158b77d542b571423727f", "schema_version": 1}
|
||||
|
||||
Reference in New Issue
Block a user