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.
145 lines
7.2 KiB
145 lines
7.2 KiB
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import struct
|
|
from enum import IntEnum
|
|
from panda import Panda
|
|
from panda.python.uds import UdsClient, MessageTimeoutError, NegativeResponseError, SESSION_TYPE,\
|
|
DATA_IDENTIFIER_TYPE, ACCESS_TYPE
|
|
|
|
# TODO: extend UDS library to allow custom/vendor-defined data identifiers without ignoring type checks
|
|
class VOLKSWAGEN_DATA_IDENTIFIER_TYPE(IntEnum):
|
|
CODING = 0x0600
|
|
|
|
# TODO: extend UDS library security_access() to take an access level offset per ISO 14229-1:2020 10.4 and remove this
|
|
class ACCESS_TYPE_LEVEL_1(IntEnum):
|
|
REQUEST_SEED = ACCESS_TYPE.REQUEST_SEED + 2
|
|
SEND_KEY = ACCESS_TYPE.SEND_KEY + 2
|
|
|
|
MQB_EPS_CAN_ADDR = 0x712
|
|
RX_OFFSET = 0x6a
|
|
|
|
if __name__ == "__main__":
|
|
desc_text = "Shows Volkswagen EPS software and coding info, and enables or disables Heading Control Assist " + \
|
|
"(Lane Assist). Useful for enabling HCA on cars without factory Lane Assist that want to use " + \
|
|
"openpilot integrated at the CAN gateway (J533)."
|
|
epilog_text = "This tool is meant to run directly on a vehicle-installed comma three, with the " + \
|
|
"openpilot/tmux processes stopped. It should also work on a separate PC with a USB-attached comma " + \
|
|
"panda. Vehicle ignition must be on. Recommend engine not be running when making changes. Must " + \
|
|
"turn ignition off and on again for any changes to take effect."
|
|
parser = argparse.ArgumentParser(description=desc_text, epilog=epilog_text)
|
|
parser.add_argument("--debug", action="store_true", help="enable ISO-TP/UDS stack debugging output")
|
|
parser.add_argument("action", choices={"show", "enable", "disable"}, help="show or modify current EPS HCA config")
|
|
args = parser.parse_args()
|
|
|
|
panda = Panda()
|
|
panda.set_safety_mode(Panda.SAFETY_ELM327)
|
|
bus = 1 if panda.has_obd() else 0
|
|
uds_client = UdsClient(panda, MQB_EPS_CAN_ADDR, MQB_EPS_CAN_ADDR + RX_OFFSET, bus, timeout=0.2, debug=args.debug)
|
|
|
|
try:
|
|
uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC)
|
|
except MessageTimeoutError:
|
|
print("Timeout opening session with EPS")
|
|
quit()
|
|
|
|
odx_file, current_coding = None, None
|
|
try:
|
|
hw_pn = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_HARDWARE_NUMBER).decode("utf-8")
|
|
sw_pn = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_SPARE_PART_NUMBER).decode("utf-8")
|
|
sw_ver = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER).decode("utf-8")
|
|
component = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.SYSTEM_NAME_OR_ENGINE_TYPE).decode("utf-8")
|
|
odx_file = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.ODX_FILE).decode("utf-8")
|
|
current_coding = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING) # type: ignore
|
|
coding_text = current_coding.hex()
|
|
|
|
print("\nEPS diagnostic data\n")
|
|
print(f" Part No HW: {hw_pn}")
|
|
print(f" Part No SW: {sw_pn}")
|
|
print(f" SW Version: {sw_ver}")
|
|
print(f" Component: {component}")
|
|
print(f" Coding: {coding_text}")
|
|
print(f" ASAM Dataset: {odx_file}")
|
|
except NegativeResponseError:
|
|
print("Error fetching data from EPS")
|
|
quit()
|
|
except MessageTimeoutError:
|
|
print("Timeout fetching data from EPS")
|
|
quit()
|
|
|
|
coding_variant, current_coding_array = None, None
|
|
# EV_SteerAssisMQB covers the majority of MQB racks (EPS_MQB_ZFLS)
|
|
# APA racks (MQB_PP_APA) have a different coding layout, which should
|
|
# be easy to support once we identify the specific config bit
|
|
if odx_file == "EV_SteerAssisMQB\x00":
|
|
coding_variant = "ZF"
|
|
current_coding_array = struct.unpack("!4B", current_coding)
|
|
hca_enabled = (current_coding_array[0] & (1 << 4) != 0)
|
|
hca_text = ("DISABLED", "ENABLED")[hca_enabled]
|
|
print(f" Lane Assist: {hca_text}")
|
|
else:
|
|
print("Configuration changes not yet supported on this EPS!")
|
|
quit()
|
|
|
|
try:
|
|
params = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION).decode("utf-8")
|
|
param_version_system_params = params[1:3]
|
|
param_vehicle_type = params[3:5]
|
|
param_index_char_curve = params[5:7]
|
|
param_version_char_values = params[7:9]
|
|
param_version_memory_map = params[9:11]
|
|
print("\nEPS parameterization (per-vehicle calibration) data\n")
|
|
print(f" Version of system parameters: {param_version_system_params}")
|
|
print(f" Vehicle type: {param_vehicle_type}")
|
|
print(f" Index of characteristic curve: {param_index_char_curve}")
|
|
print(f" Version of characteristic values: {param_version_char_values}")
|
|
print(f" Version of memory map: {param_version_memory_map}")
|
|
except (NegativeResponseError, MessageTimeoutError):
|
|
print("Error fetching parameterization data from EPS!")
|
|
quit()
|
|
|
|
if args.action in ["enable", "disable"]:
|
|
print("\nAttempting configuration update")
|
|
|
|
assert(coding_variant == "ZF") # revisit when we have the APA rack coding bit
|
|
# ZF EPS config coding length can be anywhere from 1 to 4 bytes, but the
|
|
# bit we care about is always in the same place in the first byte
|
|
if args.action == "enable":
|
|
new_byte_0 = current_coding_array[0] | (1 << 4)
|
|
else:
|
|
new_byte_0 = current_coding_array[0] & ~(1 << 4)
|
|
new_coding = new_byte_0.to_bytes(1, "little") + current_coding[1:]
|
|
|
|
try:
|
|
seed = uds_client.security_access(ACCESS_TYPE_LEVEL_1.REQUEST_SEED) # type: ignore
|
|
key = struct.unpack("!I", seed)[0] + 28183 # yeah, it's like that
|
|
uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key)) # type: ignore
|
|
except (NegativeResponseError, MessageTimeoutError):
|
|
print("Security access failed!")
|
|
quit()
|
|
|
|
try:
|
|
# Programming date and tester number must be written before making
|
|
# a change, or write to CODING will fail with request sequence error
|
|
# Encoding on tester is unclear, it contains the workshop code in the
|
|
# last two bytes, but not the VZ/importer or tester serial number
|
|
# Can't seem to read it back, but we can read the calibration tester,
|
|
# so fib a little and say that same tester did the programming
|
|
# TODO: encode the actual current date
|
|
prog_date = b'\x22\x02\x08'
|
|
uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.PROGRAMMING_DATE, prog_date)
|
|
tester_num = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.CALIBRATION_REPAIR_SHOP_CODE_OR_CALIBRATION_EQUIPMENT_SERIAL_NUMBER)
|
|
uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.REPAIR_SHOP_CODE_OR_TESTER_SERIAL_NUMBER, tester_num)
|
|
uds_client.write_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING, new_coding) # type: ignore
|
|
except (NegativeResponseError, MessageTimeoutError):
|
|
print("Writing new configuration failed!")
|
|
quit()
|
|
|
|
try:
|
|
# Read back result just to make 100% sure everything worked
|
|
current_coding_text = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING).hex() # type: ignore
|
|
print(f" New coding: {current_coding_text}")
|
|
except (NegativeResponseError, MessageTimeoutError):
|
|
print("Reading back updated coding failed!")
|
|
quit()
|
|
print("EPS configuration successfully updated")
|
|
|