nopenpilot/selfdrive/debug/vw_mqb_config.py

146 lines
7.3 KiB
Python
Executable File

#!/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 two or 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")