You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
5.8 KiB
193 lines
5.8 KiB
5 years ago
|
#!/usr/bin/env python3
|
||
|
import traceback
|
||
|
import struct
|
||
|
from tqdm import tqdm
|
||
|
|
||
|
from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
|
||
|
from selfdrive.swaglog import cloudlog
|
||
|
from selfdrive.car.fingerprints import FW_VERSIONS
|
||
|
import panda.python.uds as uds
|
||
|
|
||
|
from cereal import car
|
||
|
Ecu = car.CarParams.Ecu
|
||
|
|
||
|
def p16(val):
|
||
|
return struct.pack("!H", val)
|
||
|
|
||
|
TESTER_PRESENT_REQUEST = bytes([uds.SERVICE_TYPE.TESTER_PRESENT, 0x0])
|
||
|
TESTER_PRESENT_RESPONSE = bytes([uds.SERVICE_TYPE.TESTER_PRESENT + 0x40, 0x0])
|
||
|
|
||
|
SHORT_TESTER_PRESENT_REQUEST = bytes([uds.SERVICE_TYPE.TESTER_PRESENT])
|
||
|
SHORT_TESTER_PRESENT_RESPONSE = bytes([uds.SERVICE_TYPE.TESTER_PRESENT + 0x40])
|
||
|
|
||
|
DEFAULT_DIAGNOSTIC_REQUEST = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL,
|
||
|
uds.SESSION_TYPE.DEFAULT])
|
||
|
DEFAULT_DIAGNOSTIC_RESPONSE = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40,
|
||
|
uds.SESSION_TYPE.DEFAULT, 0x0, 0x32, 0x1, 0xf4])
|
||
|
|
||
|
EXTENDED_DIAGNOSTIC_REQUEST = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL,
|
||
|
uds.SESSION_TYPE.EXTENDED_DIAGNOSTIC])
|
||
|
EXTENDED_DIAGNOSTIC_RESPONSE = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40,
|
||
|
uds.SESSION_TYPE.EXTENDED_DIAGNOSTIC, 0x0, 0x32, 0x1, 0xf4])
|
||
|
|
||
|
UDS_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
|
||
|
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)
|
||
|
UDS_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
|
||
|
p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)
|
||
|
|
||
|
TOYOTA_VERSION_REQUEST = b'\x1a\x88\x01'
|
||
|
TOYOTA_VERSION_RESPONSE = b'\x5a\x88\x01'
|
||
|
|
||
|
OBD_VERSION_REQUEST = b'\x09\x04'
|
||
|
OBD_VERSION_RESPONSE = b'\x49\x04'
|
||
|
|
||
|
|
||
|
REQUESTS = [
|
||
|
# Honda
|
||
|
(
|
||
|
[UDS_VERSION_REQUEST],
|
||
|
[UDS_VERSION_RESPONSE]
|
||
|
),
|
||
|
# Toyota
|
||
|
(
|
||
|
[SHORT_TESTER_PRESENT_REQUEST, TOYOTA_VERSION_REQUEST],
|
||
|
[SHORT_TESTER_PRESENT_RESPONSE, TOYOTA_VERSION_RESPONSE]
|
||
|
),
|
||
|
(
|
||
|
[SHORT_TESTER_PRESENT_REQUEST, OBD_VERSION_REQUEST],
|
||
|
[SHORT_TESTER_PRESENT_RESPONSE, OBD_VERSION_RESPONSE]
|
||
|
),
|
||
|
(
|
||
|
[TESTER_PRESENT_REQUEST, DEFAULT_DIAGNOSTIC_REQUEST, EXTENDED_DIAGNOSTIC_REQUEST, UDS_VERSION_REQUEST],
|
||
|
[TESTER_PRESENT_RESPONSE, DEFAULT_DIAGNOSTIC_RESPONSE, EXTENDED_DIAGNOSTIC_RESPONSE, UDS_VERSION_RESPONSE]
|
||
|
)
|
||
|
]
|
||
|
|
||
|
def chunks(l, n=128):
|
||
|
for i in range(0, len(l), n):
|
||
|
yield l[i:i + n]
|
||
|
|
||
|
def match_fw_to_car(fw_versions):
|
||
|
candidates = FW_VERSIONS
|
||
|
invalid = []
|
||
|
|
||
|
for candidate, fws in candidates.items():
|
||
|
for ecu, expected_versions in fws.items():
|
||
|
ecu_type = ecu[0]
|
||
|
addr = ecu[1:]
|
||
|
|
||
|
found_version = fw_versions.get(addr, None)
|
||
|
|
||
|
# Allow DSU not being present
|
||
|
if ecu_type in [Ecu.unknown, Ecu.dsu] and found_version is None:
|
||
|
continue
|
||
|
|
||
|
if found_version not in expected_versions:
|
||
|
invalid.append(candidate)
|
||
|
break
|
||
|
|
||
|
return set(candidates.keys()) - set(invalid)
|
||
|
|
||
|
|
||
|
def get_fw_versions(logcan, sendcan, bus, extra=None, timeout=0.1, debug=False, progress=False):
|
||
|
ecu_types = {}
|
||
|
|
||
|
# Extract ECU adresses to query from fingerprints
|
||
|
# ECUs using a subadress need be queried one by one, the rest can be done in parallel
|
||
|
addrs = []
|
||
|
parallel_addrs = []
|
||
|
|
||
|
versions = FW_VERSIONS
|
||
|
if extra is not None:
|
||
|
versions.update(extra)
|
||
|
|
||
|
for c in versions.values():
|
||
|
for ecu_type, addr, sub_addr in c.keys():
|
||
|
a = (addr, sub_addr)
|
||
|
if a not in ecu_types:
|
||
|
ecu_types[a] = ecu_type
|
||
|
|
||
|
if sub_addr is None:
|
||
|
parallel_addrs.append(a)
|
||
|
else:
|
||
|
addrs.append([a])
|
||
|
addrs.insert(0, parallel_addrs)
|
||
|
|
||
|
fw_versions = {}
|
||
|
for i, addr in enumerate(tqdm(addrs, disable=not progress)):
|
||
|
for addr_chunk in chunks(addr):
|
||
|
for request, response in REQUESTS:
|
||
|
try:
|
||
|
query = IsoTpParallelQuery(sendcan, logcan, bus, addr_chunk, request, response, debug=debug)
|
||
|
t = 2 * timeout if i == 0 else timeout
|
||
|
fw_versions.update(query.get_data(t))
|
||
|
except Exception:
|
||
|
cloudlog.warning(f"FW query exception: {traceback.format_exc()}")
|
||
|
|
||
|
# Build capnp list to put into CarParams
|
||
|
car_fw = []
|
||
|
for addr, version in fw_versions.items():
|
||
|
f = car.CarParams.CarFw.new_message()
|
||
|
|
||
|
f.ecu = ecu_types[addr]
|
||
|
f.fwVersion = version
|
||
|
f.address = addr[0]
|
||
|
|
||
|
if addr[1] is not None:
|
||
|
f.subAddress = addr[1]
|
||
|
|
||
|
car_fw.append(f)
|
||
|
|
||
|
candidates = match_fw_to_car(fw_versions)
|
||
|
return candidates, car_fw
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
import time
|
||
|
import argparse
|
||
|
import cereal.messaging as messaging
|
||
|
from selfdrive.car.vin import get_vin
|
||
|
|
||
|
|
||
|
parser = argparse.ArgumentParser(description='Get firmware version of ECUs')
|
||
|
parser.add_argument('--scan', action='store_true')
|
||
|
parser.add_argument('--debug', action='store_true')
|
||
|
args = parser.parse_args()
|
||
|
|
||
|
logcan = messaging.sub_sock('can')
|
||
|
sendcan = messaging.pub_sock('sendcan')
|
||
|
|
||
|
extra = None
|
||
|
if args.scan:
|
||
|
extra = {"DEBUG": {}}
|
||
|
# Honda
|
||
|
for i in range(256):
|
||
|
extra["DEBUG"][(Ecu.unknown, 0x18da00f1 + (i << 8), None)] = []
|
||
|
extra["DEBUG"][(Ecu.unknown, 0x700 + i, None)] = []
|
||
|
extra["DEBUG"][(Ecu.unknown, 0x750, i)] = []
|
||
|
|
||
|
time.sleep(1.)
|
||
|
|
||
|
t = time.time()
|
||
|
print("Getting vin...")
|
||
|
addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug)
|
||
|
print(f"VIN: {vin}")
|
||
|
print("Getting VIN took %.3f s" % (time.time() - t))
|
||
|
print()
|
||
|
|
||
|
t = time.time()
|
||
|
candidates, fw_vers = get_fw_versions(logcan, sendcan, 1, extra=extra, debug=args.debug, progress=True)
|
||
|
|
||
|
print()
|
||
|
print("Found FW versions")
|
||
|
print("{")
|
||
|
for version in fw_vers:
|
||
|
subaddr = None if version.subAddress == 0 else hex(version.subAddress)
|
||
|
print(f" (Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}]")
|
||
|
print("}")
|
||
|
|
||
|
|
||
|
print()
|
||
|
print("Possible matches:", candidates)
|
||
|
print("Getting fw took %.3f s" % (time.time() - t))
|