From c1c5b0356b77757d574d7b23b748ed36a4993617 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Sat, 17 Nov 2018 23:18:48 -0800 Subject: [PATCH 01/31] uds lib --- python/uds.py | 363 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 python/uds.py diff --git a/python/uds.py b/python/uds.py new file mode 100644 index 00000000000000..9e931d30caca9f --- /dev/null +++ b/python/uds.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python +import time +import struct +from enum import Enum +import threading + +# class Services(Enum): +# DiagnosticSessionControl = 0x10 +# ECUReset = 0x11 +# SecurityAccess = 0x27 +# CommunicationControl = 0x28 +# TesterPresent = 0x3E +# AccessTimingParameter = 0x83 +# SecuredDataTransmission = 0x84 +# ControlDTCSetting = 0x85 +# ResponseOnEvent = 0x86 +# LinkControl = 0x87 +# ReadDataByIdentifier = 0x22 +# ReadMemoryByAddress = 0x23 +# ReadScalingDataByIdentifier = 0x24 +# ReadDataByPeriodicIdentifier = 0x2A +# DynamicallyDefineDataIdentifier = 0x2C +# WriteDataByIdentifier = 0x2E +# WriteMemoryByAddress = 0x3D +# ClearDiagnosticInformation = 0x14 +# ReadDTCInformation = 0x19 +# InputOutputControlByIdentifier = 0x2F +# RoutineControl = 0x31 +# RequestDownload = 0x34 +# RequestUpload = 0x35 +# TransferData = 0x36 +# RequestTransferExit = 0x37 + +_negative_response_codes = { + '\x00': 'positive response', + '\x10': 'general reject', + '\x11': 'service not supported', + '\x12': 'sub-function not supported', + '\x13': 'incorrect message length or invalid format', + '\x14': 'response too long', + '\x21': 'busy repeat request', + '\x22': 'conditions not correct', + '\x24': 'request sequence error', + '\x25': 'no response from subnet component', + '\x26': 'failure prevents execution of requested action', + '\x31': 'request out of range', + '\x33': 'security access denied', + '\x35': 'invalid key', + '\x36': 'exceed numebr of attempts', + '\x37': 'required time delay not expired', + '\x70': 'upload download not accepted', + '\x71': 'transfer data suspended', + '\x72': 'general programming failure', + '\x73': 'wrong block sequence counter', + '\x78': 'request correctly received - response pending', + '\x7e': 'sub-function not supported in active session', + '\x7f': 'service not supported in active session', + '\x81': 'rpm too high', + '\x82': 'rpm too low', + '\x83': 'engine is running', + '\x84': 'engine is not running', + '\x85': 'engine run time too low', + '\x86': 'temperature too high', + '\x87': 'temperature too low', + '\x88': 'vehicle speed too high', + '\x89': 'vehicle speed too low', + '\x8a': 'throttle/pedal too high', + '\x8b': 'throttle/pedal too low', + '\x8c': 'transmission not in neutral', + '\x8d': 'transmission not in gear', + '\x8f': 'brake switch(es) not closed', + '\x90': 'shifter lever not in park', + '\x91': 'torque converter clutch locked', + '\x92': 'voltage too high', + '\x93': 'voltage too low', +} + +# generic uds request +def _request(address, service, subfunction, data=None): + # TODO: send request + # TODO: wait for response + + # raise exception on error + if resp[0] == '\x7f' + error_code = resp[2] + error_desc = _negative_response_codes[error_code] + raise Exception('[{}] {}'.format(hex(ord(error_code)), error_desc)) + + resp_sid = ord(resp[0]) if len(resp) > 0 else None + if service != resp_sid + 0x40: + resp_sid_hex = hex(resp_sid) if resp_sid is not None else None + raise Exception('invalid response service id: {}'.format(resp_sid_hex)) + + if subfunction is None: + resp_subf = ord(resp[1]) if len(resp) > 1 else None + if subfunction != resp_subf: + resp_subf_hex = hex(resp_subf) if resp_subf is not None else None + raise Exception('invalid response subfunction: {}'.format(hex(resp_subf))) + + # return data (exclude service id and sub-function id) + return resp[(1 if subfunction is None else 2):] + +# services +class DIAGNOSTIC_SESSION_CONTROL_TYPE(Enum): + DEFAULT = 1 + PROGRAMMING = 2 + EXTENDED_DIAGNOSTIC = 3 + SAFETY_SYSTEM_DIAGNOSTIC = 4 + +def diagnostic_session_control(address, session_type): + _request(address, service=0x10, subfunction=session_type) + +class ECU_RESET_TYPE(Enum): + HARD = 1 + KEY_OFF_ON = 2 + SOFT = 3 + ENABLE_RAPID_POWER_SHUTDOWN = 4 + DISABLE_RAPID_POWER_SHUTDOWN = 5 + +def ecu_reset(address, reset_type): + resp = _request(address, service=0x11, subfunction=reset_type) + power_down_time = None + if reset_type == RESET_TYPE.ENABLE_RAPID_POWER_SHUTDOWN + power_down_time = ord(resp[0]) + return {"power_down_time": power_down_time} + +class SECURITY_ACCESS_TYPE(Enum): + REQUEST_SEED = 1 + SEND_KEY = 2 + +def security_access(address, access_type, security_key=None): + request_seed = access_type % 2 == 0 + if request_seed and security_key is not None: + raise ValueError('security_key not allowed') + if not request_seed and security_key is None: + raise ValueError('security_key is missing') + resp = _request(address, service=0x27, subfunction=access_type, data=security_key) + if request_seed: + return {"security_seed": resp} + +class COMMUNICATION_CONTROL_TYPE(Enum): + ENABLE_RX_ENABLE_TX = 0 + ENABLE_RX_DISABLE_TX = 1 + DISABLE_RX_ENABLE_TX = 2 + DISABLE_RX_DISABLE_TX = 3 + +class COMMUNICATION_CONTROL_MESSAGE_TYPE(Enum): + NORMAL = 1 + NETWORK_MANAGEMENT = 2 + NORMAL_AND_NETWORK_MANAGEMENT = 3 + +def communication_control(address, control_type, message_type): + data = chr(message_type) + _request(address, service=0x28, subfunction=control_type, data=data) + +def tester_present(address): + _request(address, service=0x3E, subfunction=0x00) + +class ACCESS_TIMING_PARAMETER_TYPE(Enum): + READ_EXTENDED_SET = 1 + SET_TO_DEFAULT_VALUES = 2 + READ_CURRENTLY_ACTIVE = 3 + SET_TO_GIVEN_VALUES = 4 + +def access_timing_parameter(address, parameter_type, parameter_values): + write_custom_values = parameter_type == ACCESS_TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES + read_values = ( + parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or + parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_EXTENDED_SET + ) + if not write_custom_values and parameter_values is not None: + raise ValueError('parameter_values not allowed') + if write_custom_values and parameter_values is None: + raise ValueError('parameter_values is missing') + resp = _request(address, service=0x83, subfunction=parameter_type, data=parameter_values) + if read_values: + # TODO: parse response into values? + return {"parameter_values": resp} + +def secured_data_transmission(address, data): + # TODO: split data into multiple input parameters? + resp = _request(address, service=0x84, subfunction=None, data=data) + # TODO: parse response into multiple output values? + return {"data"=resp} + +class CONTROL_DTC_SETTING_TYPE(Enum): + ON = 1 + OFF = 2 + +def control_dtc_setting(address, setting_type): + _request(address, service=0x85, subfunction=setting_type) + +class RESPONSE_ON_EVENT_TYPE(Enum): + STOP_RESPONSE_ON_EVENT = 0 + ON_DTC_STATUS_CHANGE = 1 + ON_TIMER_INTERRUPT = 2 + ON_CHANGE_OF_DATA_IDENTIFIER = 3 + REPORT_ACTIVATED_EVENTS = 4 + START_RESPONSE_ON_EVENT = 5 + CLEAR_RESPONSE_ON_EVENT = 6 + ON_COMPARISON_OF_VALUES = 7 + +def response_on_event(address, event_type, store_event, window_time, event_type_record, service_response_record): + if store_event: + event_type |= 0x20 + # TODO: split record parameters into arrays + data = char(window_time) + event_type_record + service_response_record + resp = _request(address, service=0x86, subfunction=event_type, data=data) + # TODO: parse the reset of response + +class LINK_CONTROL_TYPE(Enum): + VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE = 1 + VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE = 2 + TRANSITION_BAUDRATE = 3 + +class LINK_CONTROL_BAUD_RATE(Enum): + PC9600 = 1 + PC19200 = 2 + PC38400 = 3 + PC57600 = 4 + PC115200 = 5 + CAN125000 = 16 + CAN250000 = 17 + CAN500000 = 18 + CAN1000000 = 19 + +def link_control(address, control_type, baud_rate=None): + if LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: + # baud_rate = LINK_CONTROL_BAUD_RATE + data = chr(baud_rate) + elif LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE: + # baud_rate = custom value (3 bytes big-endian) + data = struct.pack('!I', baud_rate)[1:] + else: + data = None + _request(address, service=0x87, subfunction=control_type, data=data) + +class DATA_IDENTIFIER(Enum): + BOOT_SOFTWARE_IDENTIFICATION = 0XF180 + APPLICATION_SOFTWARE_IDENTIFICATION = 0XF181 + APPLICATION_DATA_IDENTIFICATION = 0XF182 + BOOT_SOFTWARE_FINGERPRINT = 0XF183 + APPLICATION_SOFTWARE_FINGERPRINT = 0XF184 + APPLICATION_DATA_FINGERPRINT = 0XF185 + ACTIVE_DIAGNOSTIC_SESSION = 0XF186 + VEHICLE_MANUFACTURER_SPARE_PART_NUMBER = 0XF187 + VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER = 0XF188 + VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER = 0XF189 + SYSTEM_SUPPLIER_IDENTIFIER = 0XF18A + ECU_MANUFACTURING_DATE = 0XF18B + ECU_SERIAL_NUMBER = 0XF18C + SUPPORTED_FUNCTIONAL_UNITS = 0XF18D + VEHICLE_MANUFACTURER_KIT_ASSEMBLY_PART_NUMBER = 0XF18E + VIN = 0XF190 + VEHICLE_MANUFACTURER_ECU_HARDWARE_NUMBER = 0XF191 + SYSTEM_SUPPLIER_ECU_HARDWARE_NUMBER = 0XF192 + SYSTEM_SUPPLIER_ECU_HARDWARE_VERSION_NUMBER = 0XF193 + SYSTEM_SUPPLIER_ECU_SOFTWARE_NUMBER = 0XF194 + SYSTEM_SUPPLIER_ECU_SOFTWARE_VERSION_NUMBER = 0XF195 + EXHAUST_REGULATION_OR_TYPE_APPROVAL_NUMBER = 0XF196 + SYSTEM_NAME_OR_ENGINE_TYPE = 0XF197 + REPAIR_SHOP_CODE_OR_TESTER_SERIAL_NUMBER = 0XF198 + PROGRAMMING_DATE = 0XF199 + CALIBRATION_REPAIR_SHOP_CODE_OR_CALIBRATION_EQUIPMENT_SERIAL_NUMBER = 0XF19A + CALIBRATION_DATE = 0XF19B + CALIBRATION_EQUIPMENT_SOFTWARE_NUMBER = 0XF19C + ECU_INSTALLATION_DATE = 0XF19D + ODX_FILE = 0XF19E + ENTITY = 0XF19F + +def read_data_by_identifier(address, data_identifier): + # TODO: support list of identifiers + data = struct.pack('!H', data_id) + resp = _request(address, service=0x22, subfunction=None, data=data) + resp_id = struct.unpack('!H', data[0:2])[0] if len(data) >= 2 else None + if resp_id != data_id: + raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) + return resp[2:] + +def read_memory_by_address(address, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4): + if memory_address_bytes < 1 or memory_address_bytes > 4: + raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) + if memory_size_bytes < 1 or memory_size_bytes > 4: + raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) + data = struct.pack('!BB', memory_size_bytes, memory_address_bytes) + + if memory_address >= 1<<(memory_address_bytes*8) + raise ValueError('invalid memory_address: {}'.format(memory_address)) + data += struct.pack('!I', memory_address)[4-memory_address_bytes:] + if memory_size >= 1<<(memory_size_bytes*8) + raise ValueError('invalid memory_size: {}'.format(memory_address)) + data += struct.pack('!I', memory_size)[4-memory_size_bytes:] + + resp = _request(address, service=0x23, subfunction=None, data=data) + return resp + +def read_scaling_data_by_identifier(address): + raise NotImplementedError() + _request(address, service=0x24, subfunction=0x00) + +def read_data_by_periodic_identifier(address): + raise NotImplementedError() + _request(address, service=0x2A, subfunction=0x00) + +def dynamically_define_data_identifier(address): + raise NotImplementedError() + _request(address, service=0x2C, subfunction=0x00) + +def write_data_by_identifier(address): + raise NotImplementedError() + _request(address, service=0x2E, subfunction=0x00) + +def write_memory_by_address(address): + raise NotImplementedError() + _request(address, service=0x3D, subfunction=0x00) + +def clear_diagnostic_information(address): + raise NotImplementedError() + _request(address, service=0x14, subfunction=0x00) + +def read_dtc_information(address): + raise NotImplementedError() + _request(address, service=0x19, subfunction=0x00) + +class INPUT_OUTPUT_CONTROL_PARAMETER(Enum): + RETURN_CONTROL_TO_ECU = 0 + RESET_TO_DEFAULT = 1 + FREEZE_CURRENT_STATE = 2 + SHORT_TERM_ADJUSTMENT = 3 + +def input_output_control_by_identifier(address): + raise NotImplementedError() + _request(address, service=0x2F, subfunction=0x00) + +class ROUTINE_CONTROL_TYPE(Enum): + ERASE_MEMORY = 0xFF00 + CHECK_PROGRAMMING_DEPENDENCIES = 0xFF01 + ERASE_MIRROR_MEMORY_DTCS = 0xFF02 + +def routine_control(address): + raise NotImplementedError() + _request(address, service=0x31, subfunction=0x00) + +def request_download(address): + raise NotImplementedError() + _request(address, service=0x34, subfunction=0x00) + +def request_upload(address): + raise NotImplementedError() + _request(address, service=0x35, subfunction=0x00) + +def transfer_data(address): + raise NotImplementedError() + _request(address, service=0x36, subfunction=0x00) + +def request_transfer_exit(address) + raise NotImplementedError() + _request(address, service=0x37, subfunction=0x00) + +if __name__ == "__main__": + from . import uds + # examples + vin = uds.read_data_by_identifier(0x18da10f1, uds.DATA_IDENTIFIER.VIN) From 98e73b51d28457ca156f122ecbf2ddec838cb5fc Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Sun, 25 Nov 2018 02:46:20 -0800 Subject: [PATCH 02/31] more UDS message type implementation --- python/uds.py | 367 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 287 insertions(+), 80 deletions(-) diff --git a/python/uds.py b/python/uds.py index 9e931d30caca9f..b596b4e1cd5037 100644 --- a/python/uds.py +++ b/python/uds.py @@ -75,6 +75,13 @@ '\x93': 'voltage too low', } +class NegativeResponseError(Exception): + pass +class InvalidServiceIdError(Exception): + pass +class InvalidSubFunctioneError(Exception): + pass + # generic uds request def _request(address, service, subfunction, data=None): # TODO: send request @@ -84,24 +91,24 @@ def _request(address, service, subfunction, data=None): if resp[0] == '\x7f' error_code = resp[2] error_desc = _negative_response_codes[error_code] - raise Exception('[{}] {}'.format(hex(ord(error_code)), error_desc)) + raise NegativeResponseError('[{}] {}'.format(hex(ord(error_code)), error_desc)) resp_sid = ord(resp[0]) if len(resp) > 0 else None if service != resp_sid + 0x40: resp_sid_hex = hex(resp_sid) if resp_sid is not None else None - raise Exception('invalid response service id: {}'.format(resp_sid_hex)) + raise InvalidServiceIdError('invalid response service id: {}'.format(resp_sid_hex)) if subfunction is None: resp_subf = ord(resp[1]) if len(resp) > 1 else None if subfunction != resp_subf: resp_subf_hex = hex(resp_subf) if resp_subf is not None else None - raise Exception('invalid response subfunction: {}'.format(hex(resp_subf))) + raise InvalidSubFunctioneError('invalid response subfunction: {}'.format(hex(resp_subf))) # return data (exclude service id and sub-function id) return resp[(1 if subfunction is None else 2):] # services -class DIAGNOSTIC_SESSION_CONTROL_TYPE(Enum): +class SESSION_TYPE(Enum): DEFAULT = 1 PROGRAMMING = 2 EXTENDED_DIAGNOSTIC = 3 @@ -110,7 +117,7 @@ class DIAGNOSTIC_SESSION_CONTROL_TYPE(Enum): def diagnostic_session_control(address, session_type): _request(address, service=0x10, subfunction=session_type) -class ECU_RESET_TYPE(Enum): +class RESET_TYPE(Enum): HARD = 1 KEY_OFF_ON = 2 SOFT = 3 @@ -122,9 +129,9 @@ def ecu_reset(address, reset_type): power_down_time = None if reset_type == RESET_TYPE.ENABLE_RAPID_POWER_SHUTDOWN power_down_time = ord(resp[0]) - return {"power_down_time": power_down_time} + return power_down_time -class SECURITY_ACCESS_TYPE(Enum): +class ACCESS_TYPE(Enum): REQUEST_SEED = 1 SEND_KEY = 2 @@ -136,15 +143,16 @@ def security_access(address, access_type, security_key=None): raise ValueError('security_key is missing') resp = _request(address, service=0x27, subfunction=access_type, data=security_key) if request_seed: - return {"security_seed": resp} + security_seed = resp + return security_seed -class COMMUNICATION_CONTROL_TYPE(Enum): +class CONTROL_TYPE(Enum): ENABLE_RX_ENABLE_TX = 0 ENABLE_RX_DISABLE_TX = 1 DISABLE_RX_ENABLE_TX = 2 DISABLE_RX_DISABLE_TX = 3 -class COMMUNICATION_CONTROL_MESSAGE_TYPE(Enum): +class MESSAGE_TYPE(Enum): NORMAL = 1 NETWORK_MANAGEMENT = 2 NORMAL_AND_NETWORK_MANAGEMENT = 3 @@ -156,41 +164,42 @@ def communication_control(address, control_type, message_type): def tester_present(address): _request(address, service=0x3E, subfunction=0x00) -class ACCESS_TIMING_PARAMETER_TYPE(Enum): +class TIMING_PARAMETER_TYPE(Enum): READ_EXTENDED_SET = 1 SET_TO_DEFAULT_VALUES = 2 READ_CURRENTLY_ACTIVE = 3 SET_TO_GIVEN_VALUES = 4 -def access_timing_parameter(address, parameter_type, parameter_values): - write_custom_values = parameter_type == ACCESS_TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES +def access_timing_parameter(address, timing_parameter_type, parameter_values): + write_custom_values = timing_parameter_type == ACCESS_TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES read_values = ( - parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or - parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_EXTENDED_SET + timing_parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or + timing_parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_EXTENDED_SET ) if not write_custom_values and parameter_values is not None: raise ValueError('parameter_values not allowed') if write_custom_values and parameter_values is None: raise ValueError('parameter_values is missing') - resp = _request(address, service=0x83, subfunction=parameter_type, data=parameter_values) + resp = _request(address, service=0x83, subfunction=timing_parameter_type, data=parameter_values) if read_values: # TODO: parse response into values? - return {"parameter_values": resp} + parameter_values = resp + return parameter_values def secured_data_transmission(address, data): # TODO: split data into multiple input parameters? resp = _request(address, service=0x84, subfunction=None, data=data) # TODO: parse response into multiple output values? - return {"data"=resp} + return resp -class CONTROL_DTC_SETTING_TYPE(Enum): +class DTC_SETTING_TYPE(Enum): ON = 1 OFF = 2 -def control_dtc_setting(address, setting_type): - _request(address, service=0x85, subfunction=setting_type) +def control_dtc_setting(address, dtc_setting_type): + _request(address, service=0x85, subfunction=dtc_setting_type) -class RESPONSE_ON_EVENT_TYPE(Enum): +class RESPONSE_EVENT_TYPE(Enum): STOP_RESPONSE_ON_EVENT = 0 ON_DTC_STATUS_CHANGE = 1 ON_TIMER_INTERRUPT = 2 @@ -200,20 +209,31 @@ class RESPONSE_ON_EVENT_TYPE(Enum): CLEAR_RESPONSE_ON_EVENT = 6 ON_COMPARISON_OF_VALUES = 7 -def response_on_event(address, event_type, store_event, window_time, event_type_record, service_response_record): +def response_on_event(address, response_event_type, store_event, window_time, event_type_record, service_response_record): if store_event: - event_type |= 0x20 + response_event_type |= 0x20 # TODO: split record parameters into arrays data = char(window_time) + event_type_record + service_response_record - resp = _request(address, service=0x86, subfunction=event_type, data=data) - # TODO: parse the reset of response + resp = _request(address, service=0x86, subfunction=response_event_type, data=data) + + if response_event_type == REPORT_ACTIVATED_EVENTS: + return { + "num_of_activated_events": ord(resp[0]), + "data": resp[1:], # TODO: parse the reset of response + } + + return { + "num_of_identified_events": ord(resp[0]), + "event_window_time": ord(resp[1]), + "data": resp[2:], # TODO: parse the reset of response + } class LINK_CONTROL_TYPE(Enum): VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE = 1 VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE = 2 TRANSITION_BAUDRATE = 3 -class LINK_CONTROL_BAUD_RATE(Enum): +class BAUD_RATE_TYPE(Enum): PC9600 = 1 PC19200 = 2 PC38400 = 3 @@ -224,18 +244,18 @@ class LINK_CONTROL_BAUD_RATE(Enum): CAN500000 = 18 CAN1000000 = 19 -def link_control(address, control_type, baud_rate=None): +def link_control(address, link_control_type, baud_rate_type=None): if LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: - # baud_rate = LINK_CONTROL_BAUD_RATE - data = chr(baud_rate) + # baud_rate_type = BAUD_RATE_TYPE + data = chr(baud_rate_type) elif LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE: - # baud_rate = custom value (3 bytes big-endian) - data = struct.pack('!I', baud_rate)[1:] + # baud_rate_type = custom value (3 bytes big-endian) + data = struct.pack('!I', baud_rate_type)[1:] else: data = None - _request(address, service=0x87, subfunction=control_type, data=data) + _request(address, service=0x87, subfunction=link_control_type, data=data) -class DATA_IDENTIFIER(Enum): +class DATA_IDENTIFIER_TYPE(Enum): BOOT_SOFTWARE_IDENTIFICATION = 0XF180 APPLICATION_SOFTWARE_IDENTIFICATION = 0XF181 APPLICATION_DATA_IDENTIFICATION = 0XF182 @@ -268,16 +288,16 @@ class DATA_IDENTIFIER(Enum): ODX_FILE = 0XF19E ENTITY = 0XF19F -def read_data_by_identifier(address, data_identifier): +def read_data_by_identifier(address, data_identifier_type): # TODO: support list of identifiers - data = struct.pack('!H', data_id) + data = struct.pack('!H', data_identifier_type) resp = _request(address, service=0x22, subfunction=None, data=data) - resp_id = struct.unpack('!H', data[0:2])[0] if len(data) >= 2 else None - if resp_id != data_id: + resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None + if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) return resp[2:] -def read_memory_by_address(address, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4): +def read_memory_by_address(address, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=1): if memory_address_bytes < 1 or memory_address_bytes > 4: raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: @@ -288,74 +308,261 @@ def read_memory_by_address(address, memory_address, memory_size, memory_address_ raise ValueError('invalid memory_address: {}'.format(memory_address)) data += struct.pack('!I', memory_address)[4-memory_address_bytes:] if memory_size >= 1<<(memory_size_bytes*8) - raise ValueError('invalid memory_size: {}'.format(memory_address)) + raise ValueError('invalid memory_size: {}'.format(memory_size)) data += struct.pack('!I', memory_size)[4-memory_size_bytes:] resp = _request(address, service=0x23, subfunction=None, data=data) return resp -def read_scaling_data_by_identifier(address): - raise NotImplementedError() - _request(address, service=0x24, subfunction=0x00) +def read_scaling_data_by_identifier(address, data_identifier_type): + data = struct.pack('!H', data_identifier_type) + resp = _request(address, service=0x24, subfunction=None, data=data) + resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None + if resp_id != data_identifier_type: + raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) + return resp[2:] # TODO: parse the response -def read_data_by_periodic_identifier(address): - raise NotImplementedError() - _request(address, service=0x2A, subfunction=0x00) +class TRANSMISSION_MODE_TYPE(Enum): + SEND_AT_SLOW_RATE = 1 + SEND_AT_MEDIUM_RATE = 2 + SEND_AT_FAST_RATE = 3 + STOP_SENDING = 4 -def dynamically_define_data_identifier(address): - raise NotImplementedError() - _request(address, service=0x2C, subfunction=0x00) +def read_data_by_periodic_identifier(address, transmission_mode_type, periodic_data_identifier): + # TODO: support list of identifiers + data = chr(transmission_mode_type) + chr(periodic_data_identifier) + _request(address, service=0x2A, subfunction=None, data=data) -def write_data_by_identifier(address): - raise NotImplementedError() - _request(address, service=0x2E, subfunction=0x00) +class DYNAMIC_DEFINITION_TYPE(Enum): + DEFINE_BY_IDENTIFIER = 1 + DEFINE_BY_MEMORY_ADDRESS = 2 + CLEAR_DYNAMICALLY_DEFINED_DATA_IDENTIFIER = 3 -def write_memory_by_address(address): - raise NotImplementedError() - _request(address, service=0x3D, subfunction=0x00) +def dynamically_define_data_identifier(address, dynamic_definition_type, dynamic_data_identifier, source_definitions, memory_address_bytes=4, memory_size_bytes=1): + if memory_address_bytes < 1 or memory_address_bytes > 4: + raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) + if memory_size_bytes < 1 or memory_size_bytes > 4: + raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) + data = struct.pack('!BB', memory_size_bytes, memory_address_bytes) -def clear_diagnostic_information(address): - raise NotImplementedError() - _request(address, service=0x14, subfunction=0x00) + data = struct.pack('!H', dynamic_data_identifier) + if dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_IDENTIFIER: + for s in source_definitions: + data += struct.pack('!H', s["data_identifier"]) + chr(s["position"]) + chr(s["memory_size"]) + elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_MEMORY_ADDRESS: + data += struct.pack('!BB', memory_size_bytes, memory_address_bytes) + for s in source_definitions: + if s["memory_address"] >= 1<<(memory_address_bytes*8) + raise ValueError('invalid memory_address: {}'.format(s["memory_address"])) + data += struct.pack('!I', memory_address)[4-memory_address_bytes:] + if s["memory_size"] >= 1<<(memory_size_bytes*8) + raise ValueError('invalid memory_size: {}'.format(s["memory_size"])) + data += struct.pack('!I', s["memory_size"])[4-memory_size_bytes:] + elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.CLEAR_DYNAMICALLY_DEFINED_DATA_IDENTIFIER: + pass + else: + raise ValueError('invalid dynamic identifier type: {}'.format(hex(dynamic_definition_type))) + _request(address, service=0x2C, subfunction=dynamic_definition_type, data=data) + +def write_data_by_identifier(address, data_identifier_type, data_record): + data = struct.pack('!H', data_identifier_type) + data_record + resp = _request(address, service=0x2E, subfunction=None, data=data) + resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None + if resp_id != data_identifier_type: + raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) -def read_dtc_information(address): - raise NotImplementedError() - _request(address, service=0x19, subfunction=0x00) +def write_memory_by_address(address, memory_address, memory_size, data_record, memory_address_bytes=4, memory_size_bytes=1): + if memory_address_bytes < 1 or memory_address_bytes > 4: + raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) + if memory_size_bytes < 1 or memory_size_bytes > 4: + raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) + data = struct.pack('!BB', memory_size_bytes, memory_address_bytes) + + if memory_address >= 1<<(memory_address_bytes*8) + raise ValueError('invalid memory_address: {}'.format(memory_address)) + data += struct.pack('!I', memory_address)[4-memory_address_bytes:] + if memory_size >= 1<<(memory_size_bytes*8) + raise ValueError('invalid memory_size: {}'.format(memory_size)) + data += struct.pack('!I', memory_size)[4-memory_size_bytes:] -class INPUT_OUTPUT_CONTROL_PARAMETER(Enum): + data += data_record + _request(address, service=0x3D, subfunction=0x00, data=data) + +class DTC_GROUP_TYPE(Enum): + EMISSIONS = 0x000000 + ALL = 0xFFFFFF + +def clear_diagnostic_information(address, dtc_group_type): + data = struct.pack('!I', dtc_group_type)[1:] # 3 bytes + _request(address, service=0x14, subfunction=None, data=data) + +class DTC_REPORT_TYPE(Enum): + NUMBER_OF_DTC_BY_STATUS_MASK = 0x01 + DTC_BY_STATUS_MASK = 0x02 + DTC_SNAPSHOT_IDENTIFICATION = 0x03 + DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER = 0x04 + DTC_SNAPSHOT_RECORD_BY_RECORD_NUMBER = 0x05 + DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER = 0x06 + NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD = 0x07 + DTC_BY_SEVERITY_MASK_RECORD = 0x08 + SEVERITY_INFORMATION_OF_DTC = 0x09 + SUPPORTED_DTC = 0x0A + FIRST_TEST_FAILED_DTC = 0x0B + FIRST_CONFIRMED_DTC = 0x0C + MOST_RECENT_TEST_FAILED_DTC = 0x0D + MOST_RECENT_CONFIRMED_DTC = 0x0E + MIRROR_MEMORY_DTC_BY_STATUS_MASK = 0x0F + MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER = 0x10 + NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK = 0x11 + NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK = 0x12 + EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK = 0x13 + DTC_FAULT_DETECTION_COUNTER = 0x14 + DTC_WITH_PERMANENT_STATUS = 0x15 + +class DTC_STATUS_MASK_TYPE(Enum): + TEST_FAILED = 0x01 + TEST_FAILED_THIS_OPERATION_CYCLE = 0x02 + PENDING_DTC = 0x04 + CONFIRMED_DTC = 0x08 + TEST_NOT_COMPLETED_SINCE_LAST_CLEAR = 0x10 + TEST_FAILED_SINCE_LAST_CLEAR = 0x20 + TEST_NOT_COMPLETED_THIS_OPERATION_CYCLE = 0x40 + WARNING_INDICATOR_REQUESTED = 0x80 + ALL = 0xFF + +class DTC_SEVERITY_MASK_TYPE(Enum): + MAINTENANCE_ONLY = 0x20 + CHECK_AT_NEXT_HALT = 0x40 + CHECK_IMMEDIATELY = 0x80 + ALL = 0xE0 + +def read_dtc_information(address, dtc_report_type, dtc_status_mask_type=DTC_STATUS_MASK_TYPE.ALL, dtc_severity_mask_type=DTC_SEVERITY_MASK_TYPE.ALL, dtc_mask_record=0xFFFFFF, dtc_snapshot_record_num=0xFF, dtc_extended_record_num=0xFF): + data = '' + # dtc_status_mask_type + if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_STATUS_MASK or + dtc_report_type == DTC_REPORT_TYPE.DTC_BY_STATUS_MASK or + dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_BY_STATUS_MASK or + dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK or + dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK or + dtc_report_type == DTC_REPORT_TYPE.EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK: + data += chr(dtc_status_mask_type) + # dtc_mask_record + if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or + dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or + dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or + dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or + dtc_report_type == DTC_REPORT_TYPE.SEVERITY_INFORMATION_OF_DTC: + data += struct.pack('!I', dtc_mask_record)[1:] # 3 bytes + # dtc_snapshot_record_num + if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or + dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or + dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_RECORD_NUMBER: + data += ord(dtc_snapshot_record_num) + # dtc_extended_record_num + if dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or + dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER: + data += chr(dtc_extended_record_num) + # dtc_severity_mask_type + if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD or + dtc_report_type == DTC_REPORT_TYPE.DTC_BY_SEVERITY_MASK_RECORD: + data += chr(dtc_severity_mask_type) + chr(dtc_status_mask_type) + + resp = _request(address, service=0x19, subfunction=dtc_report_type, data=data) + + # TODO: parse response + return resp + +class CONTROL_OPTION_TYPE(Enum): RETURN_CONTROL_TO_ECU = 0 RESET_TO_DEFAULT = 1 FREEZE_CURRENT_STATE = 2 SHORT_TERM_ADJUSTMENT = 3 -def input_output_control_by_identifier(address): - raise NotImplementedError() - _request(address, service=0x2F, subfunction=0x00) +def input_output_control_by_identifier(address, data_identifier_type, control_option_record, control_enable_mask_record=''): + data = struct.pack('!H', data_identifier_type) + control_option_record + control_enable_mask_record + resp = _request(address, service=0x2F, subfunction=None, data=data) + resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None + if resp_id != data_identifier_type: + raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) + return resp[2:] class ROUTINE_CONTROL_TYPE(Enum): + START = 1 + STOP = 2 + REQUEST_RESULTS = 3 + +class ROUTINE_IDENTIFIER_TYPE(Enum): ERASE_MEMORY = 0xFF00 CHECK_PROGRAMMING_DEPENDENCIES = 0xFF01 ERASE_MIRROR_MEMORY_DTCS = 0xFF02 -def routine_control(address): - raise NotImplementedError() - _request(address, service=0x31, subfunction=0x00) +def routine_control(address, routine_control_type, routine_identifier_type, routine_option_record=''): + data = struct.pack('!H', routine_identifier_type) + routine_option_record + _request(address, service=0x31, subfunction=routine_control_type, data=data) + resp = resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None + if resp_id != routine_identifier_type: + raise ValueError('invalid response routine identifier: {}'.format(hex(resp_id))) + return resp[2:] -def request_download(address): - raise NotImplementedError() - _request(address, service=0x34, subfunction=0x00) +def request_download(address, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): + data = chr(data_format) -def request_upload(address): - raise NotImplementedError() - _request(address, service=0x35, subfunction=0x00) + if memory_address_bytes < 1 or memory_address_bytes > 4: + raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) + if memory_size_bytes < 1 or memory_size_bytes > 4: + raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) + data += struct.pack('!BB', memory_size_bytes, memory_address_bytes) + + if memory_address >= 1<<(memory_address_bytes*8) + raise ValueError('invalid memory_address: {}'.format(memory_address)) + data += struct.pack('!I', memory_address)[4-memory_address_bytes:] + if memory_size >= 1<<(memory_size_bytes*8) + raise ValueError('invalid memory_size: {}'.format(memory_size)) + data += struct.pack('!I', memory_size)[4-memory_size_bytes:] + + resp = _request(address, service=0x34, subfunction=None, data=data) + max_num_bytes_len = ord(resp[0]) >> 4 if len(resp) > 0 else None + if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: + max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] + else: + raise ValueError('invalid max_num_bytes_len: {}'.format(max_num_bytes_len)) + + return max_num_bytes # max number of bytes per transfer data request + +def request_upload(address, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): + data = chr(data_format) + + if memory_address_bytes < 1 or memory_address_bytes > 4: + raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) + if memory_size_bytes < 1 or memory_size_bytes > 4: + raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) + data += struct.pack('!BB', memory_size_bytes, memory_address_bytes) + + if memory_address >= 1<<(memory_address_bytes*8) + raise ValueError('invalid memory_address: {}'.format(memory_address)) + data += struct.pack('!I', memory_address)[4-memory_address_bytes:] + if memory_size >= 1<<(memory_size_bytes*8) + raise ValueError('invalid memory_size: {}'.format(memory_size)) + data += struct.pack('!I', memory_size)[4-memory_size_bytes:] + + resp = _request(address, service=0x35, subfunction=None, data=data) + max_num_bytes_len = ord(resp[0]) >> 4 if len(resp) > 0 else None + if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: + max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] + else: + raise ValueError('invalid max_num_bytes_len: {}'.format(max_num_bytes_len)) + + return max_num_bytes # max number of bytes per transfer data request + +def transfer_data(address, block_sequence_count, data=''): + resp = _request(address, service=0x36, subfunction=None, data=data) + resp_id = ord(resp[0]) if len(resp) > 0 else None + if resp_id != block_sequence_count: + raise ValueError('invalid block_sequence_count: {}'.format(resp_id)) + return resp[1:] -def transfer_data(address): - raise NotImplementedError() - _request(address, service=0x36, subfunction=0x00) - def request_transfer_exit(address) - raise NotImplementedError() - _request(address, service=0x37, subfunction=0x00) + _request(address, service=0x37, subfunction=None) if __name__ == "__main__": from . import uds From 95be4811efaa55939141481f3fda603de35a2e7a Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Sun, 25 Nov 2018 11:51:39 -0800 Subject: [PATCH 03/31] SERVICE_TYPE enum --- python/uds.py | 104 +++++++++++++++++++++++++------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/python/uds.py b/python/uds.py index b596b4e1cd5037..185f4257ae190c 100644 --- a/python/uds.py +++ b/python/uds.py @@ -4,32 +4,32 @@ from enum import Enum import threading -# class Services(Enum): -# DiagnosticSessionControl = 0x10 -# ECUReset = 0x11 -# SecurityAccess = 0x27 -# CommunicationControl = 0x28 -# TesterPresent = 0x3E -# AccessTimingParameter = 0x83 -# SecuredDataTransmission = 0x84 -# ControlDTCSetting = 0x85 -# ResponseOnEvent = 0x86 -# LinkControl = 0x87 -# ReadDataByIdentifier = 0x22 -# ReadMemoryByAddress = 0x23 -# ReadScalingDataByIdentifier = 0x24 -# ReadDataByPeriodicIdentifier = 0x2A -# DynamicallyDefineDataIdentifier = 0x2C -# WriteDataByIdentifier = 0x2E -# WriteMemoryByAddress = 0x3D -# ClearDiagnosticInformation = 0x14 -# ReadDTCInformation = 0x19 -# InputOutputControlByIdentifier = 0x2F -# RoutineControl = 0x31 -# RequestDownload = 0x34 -# RequestUpload = 0x35 -# TransferData = 0x36 -# RequestTransferExit = 0x37 +class SERVICE_TYPE(Enum): + DIAGNOSTIC_SESSION_CONTROL = 0x10 + ECU_RESET = 0x11 + SECURITY_ACCESS = 0x27 + COMMUNICATION_CONTROL = 0x28 + TESTER_PRESENT = 0x3E + ACCESS_TIMING_PARAMETER = 0x83 + SECURED_DATA_TRANSMISSION = 0x84 + CONTROL_DTC_SETTING = 0x85 + RESPONSE_ON_EVENT = 0x86 + LINK_CONTROL = 0x87 + READ_DATA_BY_IDENTIFIER = 0x22 + READ_MEMORY_BY_ADDRESS = 0x23 + READ_SCALING_DATA_BY_IDENTIFIER = 0x24 + READ_DATA_BY_PERIODIC_IDENTIFIER = 0x2A + DYNAMICALLY_DEFINE_DATA_IDENTIFIER = 0x2C + WRITE_DATA_BY_IDENTIFIER = 0x2E + WRITE_MEMORY_BY_ADDRESS = 0x3D + CLEAR_DIAGNOSTIC_INFORMATION = 0x14 + READ_DTC_INFORMATION = 0x19 + INPUT_OUTPUT_CONTROL_BY_IDENTIFIER = 0x2F + ROUTINE_CONTROL = 0x31 + REQUEST_DOWNLOAD = 0x34 + REQUEST_UPLOAD = 0x35 + TRANSFER_DATA = 0x36 + REQUEST_TRANSFER_EXIT = 0x37 _negative_response_codes = { '\x00': 'positive response', @@ -83,7 +83,7 @@ class InvalidSubFunctioneError(Exception): pass # generic uds request -def _request(address, service, subfunction, data=None): +def _request(address, service_type, subfunction, data=None): # TODO: send request # TODO: wait for response @@ -115,7 +115,7 @@ class SESSION_TYPE(Enum): SAFETY_SYSTEM_DIAGNOSTIC = 4 def diagnostic_session_control(address, session_type): - _request(address, service=0x10, subfunction=session_type) + _request(address, service=SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, subfunction=session_type) class RESET_TYPE(Enum): HARD = 1 @@ -125,7 +125,7 @@ class RESET_TYPE(Enum): DISABLE_RAPID_POWER_SHUTDOWN = 5 def ecu_reset(address, reset_type): - resp = _request(address, service=0x11, subfunction=reset_type) + resp = _request(address, SERVICE_TYPE.ECU_RESET, subfunction=reset_type) power_down_time = None if reset_type == RESET_TYPE.ENABLE_RAPID_POWER_SHUTDOWN power_down_time = ord(resp[0]) @@ -141,7 +141,7 @@ def security_access(address, access_type, security_key=None): raise ValueError('security_key not allowed') if not request_seed and security_key is None: raise ValueError('security_key is missing') - resp = _request(address, service=0x27, subfunction=access_type, data=security_key) + resp = _request(address, service=SERVICE_TYPE.SECURITY_ACCESS, subfunction=access_type, data=security_key) if request_seed: security_seed = resp return security_seed @@ -159,10 +159,10 @@ class MESSAGE_TYPE(Enum): def communication_control(address, control_type, message_type): data = chr(message_type) - _request(address, service=0x28, subfunction=control_type, data=data) + _request(address, service=SERVICE_TYPE.COMMUNICATION_CONTROL, subfunction=control_type, data=data) def tester_present(address): - _request(address, service=0x3E, subfunction=0x00) + _request(address, service=SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) class TIMING_PARAMETER_TYPE(Enum): READ_EXTENDED_SET = 1 @@ -180,7 +180,7 @@ def access_timing_parameter(address, timing_parameter_type, parameter_values): raise ValueError('parameter_values not allowed') if write_custom_values and parameter_values is None: raise ValueError('parameter_values is missing') - resp = _request(address, service=0x83, subfunction=timing_parameter_type, data=parameter_values) + resp = _request(address, service=SERVICE_TYPE.ACCESS_TIMING_PARAMETER, subfunction=timing_parameter_type, data=parameter_values) if read_values: # TODO: parse response into values? parameter_values = resp @@ -188,7 +188,7 @@ def access_timing_parameter(address, timing_parameter_type, parameter_values): def secured_data_transmission(address, data): # TODO: split data into multiple input parameters? - resp = _request(address, service=0x84, subfunction=None, data=data) + resp = _request(address, service=SERVICE_TYPE.SECURED_DATA_TRANSMISSION, subfunction=None, data=data) # TODO: parse response into multiple output values? return resp @@ -197,7 +197,7 @@ class DTC_SETTING_TYPE(Enum): OFF = 2 def control_dtc_setting(address, dtc_setting_type): - _request(address, service=0x85, subfunction=dtc_setting_type) + _request(address, service=SERVICE_TYPE.CONTROL_DTC_SETTING, subfunction=dtc_setting_type) class RESPONSE_EVENT_TYPE(Enum): STOP_RESPONSE_ON_EVENT = 0 @@ -214,7 +214,7 @@ def response_on_event(address, response_event_type, store_event, window_time, ev response_event_type |= 0x20 # TODO: split record parameters into arrays data = char(window_time) + event_type_record + service_response_record - resp = _request(address, service=0x86, subfunction=response_event_type, data=data) + resp = _request(address, service=SERVICE_TYPE.RESPONSE_ON_EVENT, subfunction=response_event_type, data=data) if response_event_type == REPORT_ACTIVATED_EVENTS: return { @@ -253,7 +253,7 @@ def link_control(address, link_control_type, baud_rate_type=None): data = struct.pack('!I', baud_rate_type)[1:] else: data = None - _request(address, service=0x87, subfunction=link_control_type, data=data) + _request(address, service=SERVICE_TYPE.LINK_CONTROL, subfunction=link_control_type, data=data) class DATA_IDENTIFIER_TYPE(Enum): BOOT_SOFTWARE_IDENTIFICATION = 0XF180 @@ -291,7 +291,7 @@ class DATA_IDENTIFIER_TYPE(Enum): def read_data_by_identifier(address, data_identifier_type): # TODO: support list of identifiers data = struct.pack('!H', data_identifier_type) - resp = _request(address, service=0x22, subfunction=None, data=data) + resp = _request(address, service=SERVICE_TYPE.READ_DATA_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) @@ -311,12 +311,12 @@ def read_memory_by_address(address, memory_address, memory_size, memory_address_ raise ValueError('invalid memory_size: {}'.format(memory_size)) data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - resp = _request(address, service=0x23, subfunction=None, data=data) + resp = _request(address, service=SERVICE_TYPE.READ_MEMORY_BY_ADDRESS, subfunction=None, data=data) return resp def read_scaling_data_by_identifier(address, data_identifier_type): data = struct.pack('!H', data_identifier_type) - resp = _request(address, service=0x24, subfunction=None, data=data) + resp = _request(address, service=SERVICE_TYPE.READ_SCALING_DATA_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) @@ -331,7 +331,7 @@ class TRANSMISSION_MODE_TYPE(Enum): def read_data_by_periodic_identifier(address, transmission_mode_type, periodic_data_identifier): # TODO: support list of identifiers data = chr(transmission_mode_type) + chr(periodic_data_identifier) - _request(address, service=0x2A, subfunction=None, data=data) + _request(address, service=SERVICE_TYPE.READ_DATA_BY_PERIODIC_IDENTIFIER, subfunction=None, data=data) class DYNAMIC_DEFINITION_TYPE(Enum): DEFINE_BY_IDENTIFIER = 1 @@ -362,11 +362,11 @@ def dynamically_define_data_identifier(address, dynamic_definition_type, dynamic pass else: raise ValueError('invalid dynamic identifier type: {}'.format(hex(dynamic_definition_type))) - _request(address, service=0x2C, subfunction=dynamic_definition_type, data=data) + _request(address, service=SERVICE_TYPE.DYNAMICALLY_DEFINE_DATA_IDENTIFIER, subfunction=dynamic_definition_type, data=data) def write_data_by_identifier(address, data_identifier_type, data_record): data = struct.pack('!H', data_identifier_type) + data_record - resp = _request(address, service=0x2E, subfunction=None, data=data) + resp = _request(address, service=SERVICE_TYPE.WRITE_DATA_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) @@ -386,7 +386,7 @@ def write_memory_by_address(address, memory_address, memory_size, data_record, m data += struct.pack('!I', memory_size)[4-memory_size_bytes:] data += data_record - _request(address, service=0x3D, subfunction=0x00, data=data) + _request(address, service=SERVICE_TYPE.WRITE_MEMORY_BY_ADDRESS, subfunction=0x00, data=data) class DTC_GROUP_TYPE(Enum): EMISSIONS = 0x000000 @@ -394,7 +394,7 @@ class DTC_GROUP_TYPE(Enum): def clear_diagnostic_information(address, dtc_group_type): data = struct.pack('!I', dtc_group_type)[1:] # 3 bytes - _request(address, service=0x14, subfunction=None, data=data) + _request(address, service=SERVICE_TYPE.CLEAR_DIAGNOSTIC_INFORMATION, subfunction=None, data=data) class DTC_REPORT_TYPE(Enum): NUMBER_OF_DTC_BY_STATUS_MASK = 0x01 @@ -467,7 +467,7 @@ def read_dtc_information(address, dtc_report_type, dtc_status_mask_type=DTC_STAT dtc_report_type == DTC_REPORT_TYPE.DTC_BY_SEVERITY_MASK_RECORD: data += chr(dtc_severity_mask_type) + chr(dtc_status_mask_type) - resp = _request(address, service=0x19, subfunction=dtc_report_type, data=data) + resp = _request(address, service=SERVICE_TYPE.READ_DTC_INFORMATION, subfunction=dtc_report_type, data=data) # TODO: parse response return resp @@ -480,7 +480,7 @@ class CONTROL_OPTION_TYPE(Enum): def input_output_control_by_identifier(address, data_identifier_type, control_option_record, control_enable_mask_record=''): data = struct.pack('!H', data_identifier_type) + control_option_record + control_enable_mask_record - resp = _request(address, service=0x2F, subfunction=None, data=data) + resp = _request(address, service=SERVICE_TYPE.INPUT_OUTPUT_CONTROL_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) @@ -498,7 +498,7 @@ class ROUTINE_IDENTIFIER_TYPE(Enum): def routine_control(address, routine_control_type, routine_identifier_type, routine_option_record=''): data = struct.pack('!H', routine_identifier_type) + routine_option_record - _request(address, service=0x31, subfunction=routine_control_type, data=data) + _request(address, service=SERVICE_TYPE.ROUTINE_CONTROL, subfunction=routine_control_type, data=data) resp = resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != routine_identifier_type: raise ValueError('invalid response routine identifier: {}'.format(hex(resp_id))) @@ -520,7 +520,7 @@ def request_download(address, memory_address, memory_size, memory_address_bytes= raise ValueError('invalid memory_size: {}'.format(memory_size)) data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - resp = _request(address, service=0x34, subfunction=None, data=data) + resp = _request(address, service=SERVICE_TYPE.REQUEST_DOWNLOAD, subfunction=None, data=data) max_num_bytes_len = ord(resp[0]) >> 4 if len(resp) > 0 else None if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] @@ -545,7 +545,7 @@ def request_upload(address, memory_address, memory_size, memory_address_bytes=4, raise ValueError('invalid memory_size: {}'.format(memory_size)) data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - resp = _request(address, service=0x35, subfunction=None, data=data) + resp = _request(address, service=SERVICE_TYPE.REQUEST_UPLOAD, subfunction=None, data=data) max_num_bytes_len = ord(resp[0]) >> 4 if len(resp) > 0 else None if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] @@ -555,14 +555,14 @@ def request_upload(address, memory_address, memory_size, memory_address_bytes=4, return max_num_bytes # max number of bytes per transfer data request def transfer_data(address, block_sequence_count, data=''): - resp = _request(address, service=0x36, subfunction=None, data=data) + resp = _request(address, service=SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) resp_id = ord(resp[0]) if len(resp) > 0 else None if resp_id != block_sequence_count: raise ValueError('invalid block_sequence_count: {}'.format(resp_id)) return resp[1:] def request_transfer_exit(address) - _request(address, service=0x37, subfunction=None) + _request(address, service=SERVICE_TYPE.REQUEST_TRANSFER_EXIT, subfunction=None) if __name__ == "__main__": from . import uds From dca176e7170901f054aef4fb93c8bf73d63a1026 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Sun, 25 Nov 2018 12:53:56 -0800 Subject: [PATCH 04/31] syntax errors --- python/uds.py | 183 ++++++++++++++++++++++++++------------------------ 1 file changed, 97 insertions(+), 86 deletions(-) diff --git a/python/uds.py b/python/uds.py index 185f4257ae190c..1244f2c50a78e1 100644 --- a/python/uds.py +++ b/python/uds.py @@ -1,10 +1,10 @@ #!/usr/bin/env python import time import struct -from enum import Enum +from enum import IntEnum import threading -class SERVICE_TYPE(Enum): +class SERVICE_TYPE(IntEnum): DIAGNOSTIC_SESSION_CONTROL = 0x10 ECU_RESET = 0x11 SECURITY_ACCESS = 0x27 @@ -83,41 +83,52 @@ class InvalidSubFunctioneError(Exception): pass # generic uds request -def _request(address, service_type, subfunction, data=None): +def _request(address, service_type, subfunction=None, data=None): # TODO: send request # TODO: wait for response - - # raise exception on error - if resp[0] == '\x7f' - error_code = resp[2] - error_desc = _negative_response_codes[error_code] - raise NegativeResponseError('[{}] {}'.format(hex(ord(error_code)), error_desc)) + #resp = '\x7f'+chr(service_type)+'\x10' + resp = '\x62\xf1\x90' + '\x57\x30\x4c\x30\x30\x30\x30\x34\x33\x4d\x42\x35\x34\x31\x33\x32\x36' resp_sid = ord(resp[0]) if len(resp) > 0 else None - if service != resp_sid + 0x40: + + # negative response + if resp_sid == 0x7F: + try: + error_service = SERVICE_TYPE(ord(resp[1])).name + except: + error_service = 'NON_STANDARD_SERVICE' + error_code = hex(ord(resp[2])) if len(resp) > 2 else '0x??' + try: + error_desc = _negative_response_codes[resp[2]] + except: + error_desc = 'unknown error' + raise NegativeResponseError('{} - {} - {}'.format(error_service, error_code, error_desc)) + + # positive response + if service_type+0x40 != resp_sid: resp_sid_hex = hex(resp_sid) if resp_sid is not None else None raise InvalidServiceIdError('invalid response service id: {}'.format(resp_sid_hex)) - if subfunction is None: - resp_subf = ord(resp[1]) if len(resp) > 1 else None - if subfunction != resp_subf: - resp_subf_hex = hex(resp_subf) if resp_subf is not None else None - raise InvalidSubFunctioneError('invalid response subfunction: {}'.format(hex(resp_subf))) + if subfunction is not None: + resp_sfn = ord(resp[1]) if len(resp) > 1 else None + if subfunction != resp_sfn: + resp_sfn_hex = hex(resp_sfn) if resp_sfn is not None else None + raise InvalidSubFunctioneError('invalid response subfunction: {}'.format(hex(resp_sfn))) # return data (exclude service id and sub-function id) return resp[(1 if subfunction is None else 2):] # services -class SESSION_TYPE(Enum): +class SESSION_TYPE(IntEnum): DEFAULT = 1 PROGRAMMING = 2 EXTENDED_DIAGNOSTIC = 3 SAFETY_SYSTEM_DIAGNOSTIC = 4 def diagnostic_session_control(address, session_type): - _request(address, service=SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, subfunction=session_type) + _request(address, SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, subfunction=session_type) -class RESET_TYPE(Enum): +class RESET_TYPE(IntEnum): HARD = 1 KEY_OFF_ON = 2 SOFT = 3 @@ -127,11 +138,11 @@ class RESET_TYPE(Enum): def ecu_reset(address, reset_type): resp = _request(address, SERVICE_TYPE.ECU_RESET, subfunction=reset_type) power_down_time = None - if reset_type == RESET_TYPE.ENABLE_RAPID_POWER_SHUTDOWN + if reset_type == RESET_TYPE.ENABLE_RAPID_POWER_SHUTDOWN: power_down_time = ord(resp[0]) return power_down_time -class ACCESS_TYPE(Enum): +class ACCESS_TYPE(IntEnum): REQUEST_SEED = 1 SEND_KEY = 2 @@ -141,30 +152,30 @@ def security_access(address, access_type, security_key=None): raise ValueError('security_key not allowed') if not request_seed and security_key is None: raise ValueError('security_key is missing') - resp = _request(address, service=SERVICE_TYPE.SECURITY_ACCESS, subfunction=access_type, data=security_key) + resp = _request(address, SERVICE_TYPE.SECURITY_ACCESS, subfunction=access_type, data=security_key) if request_seed: security_seed = resp return security_seed -class CONTROL_TYPE(Enum): +class CONTROL_TYPE(IntEnum): ENABLE_RX_ENABLE_TX = 0 ENABLE_RX_DISABLE_TX = 1 DISABLE_RX_ENABLE_TX = 2 DISABLE_RX_DISABLE_TX = 3 -class MESSAGE_TYPE(Enum): +class MESSAGE_TYPE(IntEnum): NORMAL = 1 NETWORK_MANAGEMENT = 2 NORMAL_AND_NETWORK_MANAGEMENT = 3 def communication_control(address, control_type, message_type): data = chr(message_type) - _request(address, service=SERVICE_TYPE.COMMUNICATION_CONTROL, subfunction=control_type, data=data) + _request(address, SERVICE_TYPE.COMMUNICATION_CONTROL, subfunction=control_type, data=data) def tester_present(address): - _request(address, service=SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) + _request(address, SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) -class TIMING_PARAMETER_TYPE(Enum): +class TIMING_PARAMETER_TYPE(IntEnum): READ_EXTENDED_SET = 1 SET_TO_DEFAULT_VALUES = 2 READ_CURRENTLY_ACTIVE = 3 @@ -180,7 +191,7 @@ def access_timing_parameter(address, timing_parameter_type, parameter_values): raise ValueError('parameter_values not allowed') if write_custom_values and parameter_values is None: raise ValueError('parameter_values is missing') - resp = _request(address, service=SERVICE_TYPE.ACCESS_TIMING_PARAMETER, subfunction=timing_parameter_type, data=parameter_values) + resp = _request(address, SERVICE_TYPE.ACCESS_TIMING_PARAMETER, subfunction=timing_parameter_type, data=parameter_values) if read_values: # TODO: parse response into values? parameter_values = resp @@ -188,18 +199,18 @@ def access_timing_parameter(address, timing_parameter_type, parameter_values): def secured_data_transmission(address, data): # TODO: split data into multiple input parameters? - resp = _request(address, service=SERVICE_TYPE.SECURED_DATA_TRANSMISSION, subfunction=None, data=data) + resp = _request(address, SERVICE_TYPE.SECURED_DATA_TRANSMISSION, subfunction=None, data=data) # TODO: parse response into multiple output values? return resp -class DTC_SETTING_TYPE(Enum): +class DTC_SETTING_TYPE(IntEnum): ON = 1 OFF = 2 def control_dtc_setting(address, dtc_setting_type): - _request(address, service=SERVICE_TYPE.CONTROL_DTC_SETTING, subfunction=dtc_setting_type) + _request(address, SERVICE_TYPE.CONTROL_DTC_SETTING, subfunction=dtc_setting_type) -class RESPONSE_EVENT_TYPE(Enum): +class RESPONSE_EVENT_TYPE(IntEnum): STOP_RESPONSE_ON_EVENT = 0 ON_DTC_STATUS_CHANGE = 1 ON_TIMER_INTERRUPT = 2 @@ -214,7 +225,7 @@ def response_on_event(address, response_event_type, store_event, window_time, ev response_event_type |= 0x20 # TODO: split record parameters into arrays data = char(window_time) + event_type_record + service_response_record - resp = _request(address, service=SERVICE_TYPE.RESPONSE_ON_EVENT, subfunction=response_event_type, data=data) + resp = _request(address, SERVICE_TYPE.RESPONSE_ON_EVENT, subfunction=response_event_type, data=data) if response_event_type == REPORT_ACTIVATED_EVENTS: return { @@ -228,12 +239,12 @@ def response_on_event(address, response_event_type, store_event, window_time, ev "data": resp[2:], # TODO: parse the reset of response } -class LINK_CONTROL_TYPE(Enum): +class LINK_CONTROL_TYPE(IntEnum): VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE = 1 VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE = 2 TRANSITION_BAUDRATE = 3 -class BAUD_RATE_TYPE(Enum): +class BAUD_RATE_TYPE(IntEnum): PC9600 = 1 PC19200 = 2 PC38400 = 3 @@ -253,9 +264,9 @@ def link_control(address, link_control_type, baud_rate_type=None): data = struct.pack('!I', baud_rate_type)[1:] else: data = None - _request(address, service=SERVICE_TYPE.LINK_CONTROL, subfunction=link_control_type, data=data) + _request(address, SERVICE_TYPE.LINK_CONTROL, subfunction=link_control_type, data=data) -class DATA_IDENTIFIER_TYPE(Enum): +class DATA_IDENTIFIER_TYPE(IntEnum): BOOT_SOFTWARE_IDENTIFICATION = 0XF180 APPLICATION_SOFTWARE_IDENTIFICATION = 0XF181 APPLICATION_DATA_IDENTIFICATION = 0XF182 @@ -291,7 +302,7 @@ class DATA_IDENTIFIER_TYPE(Enum): def read_data_by_identifier(address, data_identifier_type): # TODO: support list of identifiers data = struct.pack('!H', data_identifier_type) - resp = _request(address, service=SERVICE_TYPE.READ_DATA_BY_IDENTIFIER, subfunction=None, data=data) + resp = _request(address, SERVICE_TYPE.READ_DATA_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) @@ -304,25 +315,25 @@ def read_memory_by_address(address, memory_address, memory_size, memory_address_ raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) data = struct.pack('!BB', memory_size_bytes, memory_address_bytes) - if memory_address >= 1<<(memory_address_bytes*8) + if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) data += struct.pack('!I', memory_address)[4-memory_address_bytes:] - if memory_size >= 1<<(memory_size_bytes*8) + if memory_size >= 1<<(memory_size_bytes*8): raise ValueError('invalid memory_size: {}'.format(memory_size)) data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - resp = _request(address, service=SERVICE_TYPE.READ_MEMORY_BY_ADDRESS, subfunction=None, data=data) + resp = _request(address, SERVICE_TYPE.READ_MEMORY_BY_ADDRESS, subfunction=None, data=data) return resp def read_scaling_data_by_identifier(address, data_identifier_type): data = struct.pack('!H', data_identifier_type) - resp = _request(address, service=SERVICE_TYPE.READ_SCALING_DATA_BY_IDENTIFIER, subfunction=None, data=data) + resp = _request(address, SERVICE_TYPE.READ_SCALING_DATA_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) return resp[2:] # TODO: parse the response -class TRANSMISSION_MODE_TYPE(Enum): +class TRANSMISSION_MODE_TYPE(IntEnum): SEND_AT_SLOW_RATE = 1 SEND_AT_MEDIUM_RATE = 2 SEND_AT_FAST_RATE = 3 @@ -331,9 +342,9 @@ class TRANSMISSION_MODE_TYPE(Enum): def read_data_by_periodic_identifier(address, transmission_mode_type, periodic_data_identifier): # TODO: support list of identifiers data = chr(transmission_mode_type) + chr(periodic_data_identifier) - _request(address, service=SERVICE_TYPE.READ_DATA_BY_PERIODIC_IDENTIFIER, subfunction=None, data=data) + _request(address, SERVICE_TYPE.READ_DATA_BY_PERIODIC_IDENTIFIER, subfunction=None, data=data) -class DYNAMIC_DEFINITION_TYPE(Enum): +class DYNAMIC_DEFINITION_TYPE(IntEnum): DEFINE_BY_IDENTIFIER = 1 DEFINE_BY_MEMORY_ADDRESS = 2 CLEAR_DYNAMICALLY_DEFINED_DATA_IDENTIFIER = 3 @@ -352,21 +363,21 @@ def dynamically_define_data_identifier(address, dynamic_definition_type, dynamic elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_MEMORY_ADDRESS: data += struct.pack('!BB', memory_size_bytes, memory_address_bytes) for s in source_definitions: - if s["memory_address"] >= 1<<(memory_address_bytes*8) + if s["memory_address"] >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(s["memory_address"])) data += struct.pack('!I', memory_address)[4-memory_address_bytes:] - if s["memory_size"] >= 1<<(memory_size_bytes*8) + if s["memory_size"] >= 1<<(memory_size_bytes*8): raise ValueError('invalid memory_size: {}'.format(s["memory_size"])) data += struct.pack('!I', s["memory_size"])[4-memory_size_bytes:] elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.CLEAR_DYNAMICALLY_DEFINED_DATA_IDENTIFIER: pass else: raise ValueError('invalid dynamic identifier type: {}'.format(hex(dynamic_definition_type))) - _request(address, service=SERVICE_TYPE.DYNAMICALLY_DEFINE_DATA_IDENTIFIER, subfunction=dynamic_definition_type, data=data) + _request(address, SERVICE_TYPE.DYNAMICALLY_DEFINE_DATA_IDENTIFIER, subfunction=dynamic_definition_type, data=data) def write_data_by_identifier(address, data_identifier_type, data_record): data = struct.pack('!H', data_identifier_type) + data_record - resp = _request(address, service=SERVICE_TYPE.WRITE_DATA_BY_IDENTIFIER, subfunction=None, data=data) + resp = _request(address, SERVICE_TYPE.WRITE_DATA_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) @@ -378,25 +389,25 @@ def write_memory_by_address(address, memory_address, memory_size, data_record, m raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) data = struct.pack('!BB', memory_size_bytes, memory_address_bytes) - if memory_address >= 1<<(memory_address_bytes*8) + if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) data += struct.pack('!I', memory_address)[4-memory_address_bytes:] - if memory_size >= 1<<(memory_size_bytes*8) + if memory_size >= 1<<(memory_size_bytes*8): raise ValueError('invalid memory_size: {}'.format(memory_size)) data += struct.pack('!I', memory_size)[4-memory_size_bytes:] data += data_record - _request(address, service=SERVICE_TYPE.WRITE_MEMORY_BY_ADDRESS, subfunction=0x00, data=data) + _request(address, SERVICE_TYPE.WRITE_MEMORY_BY_ADDRESS, subfunction=0x00, data=data) -class DTC_GROUP_TYPE(Enum): +class DTC_GROUP_TYPE(IntEnum): EMISSIONS = 0x000000 ALL = 0xFFFFFF def clear_diagnostic_information(address, dtc_group_type): data = struct.pack('!I', dtc_group_type)[1:] # 3 bytes - _request(address, service=SERVICE_TYPE.CLEAR_DIAGNOSTIC_INFORMATION, subfunction=None, data=data) + _request(address, SERVICE_TYPE.CLEAR_DIAGNOSTIC_INFORMATION, subfunction=None, data=data) -class DTC_REPORT_TYPE(Enum): +class DTC_REPORT_TYPE(IntEnum): NUMBER_OF_DTC_BY_STATUS_MASK = 0x01 DTC_BY_STATUS_MASK = 0x02 DTC_SNAPSHOT_IDENTIFICATION = 0x03 @@ -419,7 +430,7 @@ class DTC_REPORT_TYPE(Enum): DTC_FAULT_DETECTION_COUNTER = 0x14 DTC_WITH_PERMANENT_STATUS = 0x15 -class DTC_STATUS_MASK_TYPE(Enum): +class DTC_STATUS_MASK_TYPE(IntEnum): TEST_FAILED = 0x01 TEST_FAILED_THIS_OPERATION_CYCLE = 0x02 PENDING_DTC = 0x04 @@ -430,7 +441,7 @@ class DTC_STATUS_MASK_TYPE(Enum): WARNING_INDICATOR_REQUESTED = 0x80 ALL = 0xFF -class DTC_SEVERITY_MASK_TYPE(Enum): +class DTC_SEVERITY_MASK_TYPE(IntEnum): MAINTENANCE_ONLY = 0x20 CHECK_AT_NEXT_HALT = 0x40 CHECK_IMMEDIATELY = 0x80 @@ -439,40 +450,40 @@ class DTC_SEVERITY_MASK_TYPE(Enum): def read_dtc_information(address, dtc_report_type, dtc_status_mask_type=DTC_STATUS_MASK_TYPE.ALL, dtc_severity_mask_type=DTC_SEVERITY_MASK_TYPE.ALL, dtc_mask_record=0xFFFFFF, dtc_snapshot_record_num=0xFF, dtc_extended_record_num=0xFF): data = '' # dtc_status_mask_type - if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_STATUS_MASK or - dtc_report_type == DTC_REPORT_TYPE.DTC_BY_STATUS_MASK or - dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_BY_STATUS_MASK or - dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK or - dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK or + if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_STATUS_MASK or \ + dtc_report_type == DTC_REPORT_TYPE.DTC_BY_STATUS_MASK or \ + dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_BY_STATUS_MASK or \ + dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK or \ + dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK or \ dtc_report_type == DTC_REPORT_TYPE.EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK: data += chr(dtc_status_mask_type) # dtc_mask_record - if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or - dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or - dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or - dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or + if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or \ + dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or \ + dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ + dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ dtc_report_type == DTC_REPORT_TYPE.SEVERITY_INFORMATION_OF_DTC: data += struct.pack('!I', dtc_mask_record)[1:] # 3 bytes # dtc_snapshot_record_num - if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or - dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or + if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or \ + dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or \ dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_RECORD_NUMBER: data += ord(dtc_snapshot_record_num) # dtc_extended_record_num - if dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or + if dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER: data += chr(dtc_extended_record_num) # dtc_severity_mask_type - if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD or + if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD or \ dtc_report_type == DTC_REPORT_TYPE.DTC_BY_SEVERITY_MASK_RECORD: data += chr(dtc_severity_mask_type) + chr(dtc_status_mask_type) - resp = _request(address, service=SERVICE_TYPE.READ_DTC_INFORMATION, subfunction=dtc_report_type, data=data) + resp = _request(address, SERVICE_TYPE.READ_DTC_INFORMATION, subfunction=dtc_report_type, data=data) # TODO: parse response return resp -class CONTROL_OPTION_TYPE(Enum): +class CONTROL_OPTION_TYPE(IntEnum): RETURN_CONTROL_TO_ECU = 0 RESET_TO_DEFAULT = 1 FREEZE_CURRENT_STATE = 2 @@ -480,25 +491,25 @@ class CONTROL_OPTION_TYPE(Enum): def input_output_control_by_identifier(address, data_identifier_type, control_option_record, control_enable_mask_record=''): data = struct.pack('!H', data_identifier_type) + control_option_record + control_enable_mask_record - resp = _request(address, service=SERVICE_TYPE.INPUT_OUTPUT_CONTROL_BY_IDENTIFIER, subfunction=None, data=data) + resp = _request(address, SERVICE_TYPE.INPUT_OUTPUT_CONTROL_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) return resp[2:] -class ROUTINE_CONTROL_TYPE(Enum): +class ROUTINE_CONTROL_TYPE(IntEnum): START = 1 STOP = 2 REQUEST_RESULTS = 3 -class ROUTINE_IDENTIFIER_TYPE(Enum): +class ROUTINE_IDENTIFIER_TYPE(IntEnum): ERASE_MEMORY = 0xFF00 CHECK_PROGRAMMING_DEPENDENCIES = 0xFF01 ERASE_MIRROR_MEMORY_DTCS = 0xFF02 def routine_control(address, routine_control_type, routine_identifier_type, routine_option_record=''): data = struct.pack('!H', routine_identifier_type) + routine_option_record - _request(address, service=SERVICE_TYPE.ROUTINE_CONTROL, subfunction=routine_control_type, data=data) + _request(address, SERVICE_TYPE.ROUTINE_CONTROL, subfunction=routine_control_type, data=data) resp = resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != routine_identifier_type: raise ValueError('invalid response routine identifier: {}'.format(hex(resp_id))) @@ -513,14 +524,14 @@ def request_download(address, memory_address, memory_size, memory_address_bytes= raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) data += struct.pack('!BB', memory_size_bytes, memory_address_bytes) - if memory_address >= 1<<(memory_address_bytes*8) + if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) data += struct.pack('!I', memory_address)[4-memory_address_bytes:] - if memory_size >= 1<<(memory_size_bytes*8) + if memory_size >= 1<<(memory_size_bytes*8): raise ValueError('invalid memory_size: {}'.format(memory_size)) data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - resp = _request(address, service=SERVICE_TYPE.REQUEST_DOWNLOAD, subfunction=None, data=data) + resp = _request(address, SERVICE_TYPE.REQUEST_DOWNLOAD, subfunction=None, data=data) max_num_bytes_len = ord(resp[0]) >> 4 if len(resp) > 0 else None if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] @@ -538,14 +549,14 @@ def request_upload(address, memory_address, memory_size, memory_address_bytes=4, raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) data += struct.pack('!BB', memory_size_bytes, memory_address_bytes) - if memory_address >= 1<<(memory_address_bytes*8) + if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) data += struct.pack('!I', memory_address)[4-memory_address_bytes:] - if memory_size >= 1<<(memory_size_bytes*8) + if memory_size >= 1<<(memory_size_bytes*8): raise ValueError('invalid memory_size: {}'.format(memory_size)) data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - resp = _request(address, service=SERVICE_TYPE.REQUEST_UPLOAD, subfunction=None, data=data) + resp = _request(address, SERVICE_TYPE.REQUEST_UPLOAD, subfunction=None, data=data) max_num_bytes_len = ord(resp[0]) >> 4 if len(resp) > 0 else None if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] @@ -555,16 +566,16 @@ def request_upload(address, memory_address, memory_size, memory_address_bytes=4, return max_num_bytes # max number of bytes per transfer data request def transfer_data(address, block_sequence_count, data=''): - resp = _request(address, service=SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) + resp = _request(address, SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) resp_id = ord(resp[0]) if len(resp) > 0 else None if resp_id != block_sequence_count: raise ValueError('invalid block_sequence_count: {}'.format(resp_id)) return resp[1:] -def request_transfer_exit(address) - _request(address, service=SERVICE_TYPE.REQUEST_TRANSFER_EXIT, subfunction=None) +def request_transfer_exit(address): + _request(address, SERVICE_TYPE.REQUEST_TRANSFER_EXIT, subfunction=None) if __name__ == "__main__": - from . import uds # examples - vin = uds.read_data_by_identifier(0x18da10f1, uds.DATA_IDENTIFIER.VIN) + vin = read_data_by_identifier(0x18da10f1, DATA_IDENTIFIER_TYPE.VIN) + print(vin) From 1ddc9735d1a424deb1b41db2a1c3778dbf6fafec Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Mon, 3 Dec 2018 10:09:42 -0800 Subject: [PATCH 05/31] uds can communication --- python/uds.py | 143 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 109 insertions(+), 34 deletions(-) diff --git a/python/uds.py b/python/uds.py index 1244f2c50a78e1..eb9329e296aaf8 100644 --- a/python/uds.py +++ b/python/uds.py @@ -2,8 +2,11 @@ import time import struct from enum import IntEnum +from Queue import Queue, Empty import threading +DEBUG = True + class SERVICE_TYPE(IntEnum): DIAGNOSTIC_SESSION_CONTROL = 0x10 ECU_RESET = 0x11 @@ -75,6 +78,8 @@ class SERVICE_TYPE(IntEnum): '\x93': 'voltage too low', } +class MessageTimeoutError(Exception): + pass class NegativeResponseError(Exception): pass class InvalidServiceIdError(Exception): @@ -82,13 +87,72 @@ class InvalidServiceIdError(Exception): class InvalidSubFunctioneError(Exception): pass -# generic uds request -def _request(address, service_type, subfunction=None, data=None): - # TODO: send request - # TODO: wait for response - #resp = '\x7f'+chr(service_type)+'\x10' - resp = '\x62\xf1\x90' + '\x57\x30\x4c\x30\x30\x30\x30\x34\x33\x4d\x42\x35\x34\x31\x33\x32\x36' +def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): + try: + panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) + if tx_addr < 0xFFF8: + filter_addr = tx_addr+8 + elif tx_addr > 0x10000000 and tx_addr < 0xFFFFFFFF: + filter_addr = (tx_addr & 0xFFFF0000) + (tx_addr<<8 & 0xFF00) + (tx_addr>>8 & 0xFF) + else: + raise ValueError("invalid tx_addr: {}".format(tx_addr)) + rx_frame = {"size": 0, "data": "", "sent": True} + + panda.can_clear(0) + while True: + messages = panda.can_recv() + for rx_addr, rx_ts, rx_data, rx_bus in messages: + if rx_bus != bus or rx_addr != filter_addr or len(data) == 0: + continue + + if (DEBUG): print "R:", hex(rx_addr), rx_data.encode('hex') + if rx_data[0] >> 4 == 0x0: + # single rx_frame + rx_frame["size"] = rx_data[0] & 0xFF + rx_frame["data"] = rx_data[1:1+rx_frame["size"]] + rx_frame["sent"] = True + rx_queue.put(rx_frame["data"]) + elif rx_data[0] >> 4 == 0x1: + # first rx_frame + rx_frame["size"] = ((rx_data[0] & 0x0F) << 8) + rx_data[1] + rx_frame["data"] = rx_data[2:] + rx_frame["sent"] = False + # send flow control message (send all bytes) + flow_ctl = "\x30\x00\x00".ljust(8, "\x00") + if (DEBUG): print "S:", hex(tx_addr), flow_ctl.encode("hex") + panda.can_send(tx_addr, flow_ctl, bus) + elif rx_data[0] >> 4 == 0x2: + # consecutive rx_frame + assert rx_frame["sent"] == False, "no active frame" + rx_size = rx_frame["size"] - len(rx_data) + rx_frame["data"] += rx_data[1:1+min(rx_size, 7)] + if rx_size <= 7: + rx_frame["sent"] = True + rx_queue.put(rx_frame["data"]) + + if not tx_queue.empty(): + req = tx_queue.get(block=False) + # reset rx rx_frame + rx_frame = {"size": 0, "data": "", "sent": True} + if (DEBUG): print "S:", hex(tx_addr), req.encode("hex") + panda.can_send(tx_addr, req, bus) + else: + time.sleep(0.001) + finally: + panda.close() +# generic uds request +def _uds_request(address, service_type, subfunction=None, data=None): + req = chr(service_type) + if subfunction is not None: + req += chr(subfunction) + if data is not None: + req += data + tx_queue.put(req) + try: + resp = rx_queue.get(block=True, timeout=10) + except Empty: + raise MessageTimeoutError("timeout waiting for response") resp_sid = ord(resp[0]) if len(resp) > 0 else None # negative response @@ -126,7 +190,7 @@ class SESSION_TYPE(IntEnum): SAFETY_SYSTEM_DIAGNOSTIC = 4 def diagnostic_session_control(address, session_type): - _request(address, SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, subfunction=session_type) + _uds_request(address, SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, subfunction=session_type) class RESET_TYPE(IntEnum): HARD = 1 @@ -136,7 +200,7 @@ class RESET_TYPE(IntEnum): DISABLE_RAPID_POWER_SHUTDOWN = 5 def ecu_reset(address, reset_type): - resp = _request(address, SERVICE_TYPE.ECU_RESET, subfunction=reset_type) + resp = _uds_request(address, SERVICE_TYPE.ECU_RESET, subfunction=reset_type) power_down_time = None if reset_type == RESET_TYPE.ENABLE_RAPID_POWER_SHUTDOWN: power_down_time = ord(resp[0]) @@ -152,7 +216,7 @@ def security_access(address, access_type, security_key=None): raise ValueError('security_key not allowed') if not request_seed and security_key is None: raise ValueError('security_key is missing') - resp = _request(address, SERVICE_TYPE.SECURITY_ACCESS, subfunction=access_type, data=security_key) + resp = _uds_request(address, SERVICE_TYPE.SECURITY_ACCESS, subfunction=access_type, data=security_key) if request_seed: security_seed = resp return security_seed @@ -170,10 +234,10 @@ class MESSAGE_TYPE(IntEnum): def communication_control(address, control_type, message_type): data = chr(message_type) - _request(address, SERVICE_TYPE.COMMUNICATION_CONTROL, subfunction=control_type, data=data) + _uds_request(address, SERVICE_TYPE.COMMUNICATION_CONTROL, subfunction=control_type, data=data) def tester_present(address): - _request(address, SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) + _uds_request(address, SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) class TIMING_PARAMETER_TYPE(IntEnum): READ_EXTENDED_SET = 1 @@ -191,7 +255,7 @@ def access_timing_parameter(address, timing_parameter_type, parameter_values): raise ValueError('parameter_values not allowed') if write_custom_values and parameter_values is None: raise ValueError('parameter_values is missing') - resp = _request(address, SERVICE_TYPE.ACCESS_TIMING_PARAMETER, subfunction=timing_parameter_type, data=parameter_values) + resp = _uds_request(address, SERVICE_TYPE.ACCESS_TIMING_PARAMETER, subfunction=timing_parameter_type, data=parameter_values) if read_values: # TODO: parse response into values? parameter_values = resp @@ -199,7 +263,7 @@ def access_timing_parameter(address, timing_parameter_type, parameter_values): def secured_data_transmission(address, data): # TODO: split data into multiple input parameters? - resp = _request(address, SERVICE_TYPE.SECURED_DATA_TRANSMISSION, subfunction=None, data=data) + resp = _uds_request(address, SERVICE_TYPE.SECURED_DATA_TRANSMISSION, subfunction=None, data=data) # TODO: parse response into multiple output values? return resp @@ -208,7 +272,7 @@ class DTC_SETTING_TYPE(IntEnum): OFF = 2 def control_dtc_setting(address, dtc_setting_type): - _request(address, SERVICE_TYPE.CONTROL_DTC_SETTING, subfunction=dtc_setting_type) + _uds_request(address, SERVICE_TYPE.CONTROL_DTC_SETTING, subfunction=dtc_setting_type) class RESPONSE_EVENT_TYPE(IntEnum): STOP_RESPONSE_ON_EVENT = 0 @@ -225,7 +289,7 @@ def response_on_event(address, response_event_type, store_event, window_time, ev response_event_type |= 0x20 # TODO: split record parameters into arrays data = char(window_time) + event_type_record + service_response_record - resp = _request(address, SERVICE_TYPE.RESPONSE_ON_EVENT, subfunction=response_event_type, data=data) + resp = _uds_request(address, SERVICE_TYPE.RESPONSE_ON_EVENT, subfunction=response_event_type, data=data) if response_event_type == REPORT_ACTIVATED_EVENTS: return { @@ -264,7 +328,7 @@ def link_control(address, link_control_type, baud_rate_type=None): data = struct.pack('!I', baud_rate_type)[1:] else: data = None - _request(address, SERVICE_TYPE.LINK_CONTROL, subfunction=link_control_type, data=data) + _uds_request(address, SERVICE_TYPE.LINK_CONTROL, subfunction=link_control_type, data=data) class DATA_IDENTIFIER_TYPE(IntEnum): BOOT_SOFTWARE_IDENTIFICATION = 0XF180 @@ -302,7 +366,7 @@ class DATA_IDENTIFIER_TYPE(IntEnum): def read_data_by_identifier(address, data_identifier_type): # TODO: support list of identifiers data = struct.pack('!H', data_identifier_type) - resp = _request(address, SERVICE_TYPE.READ_DATA_BY_IDENTIFIER, subfunction=None, data=data) + resp = _uds_request(address, SERVICE_TYPE.READ_DATA_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) @@ -322,12 +386,12 @@ def read_memory_by_address(address, memory_address, memory_size, memory_address_ raise ValueError('invalid memory_size: {}'.format(memory_size)) data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - resp = _request(address, SERVICE_TYPE.READ_MEMORY_BY_ADDRESS, subfunction=None, data=data) + resp = _uds_request(address, SERVICE_TYPE.READ_MEMORY_BY_ADDRESS, subfunction=None, data=data) return resp def read_scaling_data_by_identifier(address, data_identifier_type): data = struct.pack('!H', data_identifier_type) - resp = _request(address, SERVICE_TYPE.READ_SCALING_DATA_BY_IDENTIFIER, subfunction=None, data=data) + resp = _uds_request(address, SERVICE_TYPE.READ_SCALING_DATA_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) @@ -342,7 +406,7 @@ class TRANSMISSION_MODE_TYPE(IntEnum): def read_data_by_periodic_identifier(address, transmission_mode_type, periodic_data_identifier): # TODO: support list of identifiers data = chr(transmission_mode_type) + chr(periodic_data_identifier) - _request(address, SERVICE_TYPE.READ_DATA_BY_PERIODIC_IDENTIFIER, subfunction=None, data=data) + _uds_request(address, SERVICE_TYPE.READ_DATA_BY_PERIODIC_IDENTIFIER, subfunction=None, data=data) class DYNAMIC_DEFINITION_TYPE(IntEnum): DEFINE_BY_IDENTIFIER = 1 @@ -373,11 +437,11 @@ def dynamically_define_data_identifier(address, dynamic_definition_type, dynamic pass else: raise ValueError('invalid dynamic identifier type: {}'.format(hex(dynamic_definition_type))) - _request(address, SERVICE_TYPE.DYNAMICALLY_DEFINE_DATA_IDENTIFIER, subfunction=dynamic_definition_type, data=data) + _uds_request(address, SERVICE_TYPE.DYNAMICALLY_DEFINE_DATA_IDENTIFIER, subfunction=dynamic_definition_type, data=data) def write_data_by_identifier(address, data_identifier_type, data_record): data = struct.pack('!H', data_identifier_type) + data_record - resp = _request(address, SERVICE_TYPE.WRITE_DATA_BY_IDENTIFIER, subfunction=None, data=data) + resp = _uds_request(address, SERVICE_TYPE.WRITE_DATA_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) @@ -397,7 +461,7 @@ def write_memory_by_address(address, memory_address, memory_size, data_record, m data += struct.pack('!I', memory_size)[4-memory_size_bytes:] data += data_record - _request(address, SERVICE_TYPE.WRITE_MEMORY_BY_ADDRESS, subfunction=0x00, data=data) + _uds_request(address, SERVICE_TYPE.WRITE_MEMORY_BY_ADDRESS, subfunction=0x00, data=data) class DTC_GROUP_TYPE(IntEnum): EMISSIONS = 0x000000 @@ -405,7 +469,7 @@ class DTC_GROUP_TYPE(IntEnum): def clear_diagnostic_information(address, dtc_group_type): data = struct.pack('!I', dtc_group_type)[1:] # 3 bytes - _request(address, SERVICE_TYPE.CLEAR_DIAGNOSTIC_INFORMATION, subfunction=None, data=data) + _uds_request(address, SERVICE_TYPE.CLEAR_DIAGNOSTIC_INFORMATION, subfunction=None, data=data) class DTC_REPORT_TYPE(IntEnum): NUMBER_OF_DTC_BY_STATUS_MASK = 0x01 @@ -438,7 +502,7 @@ class DTC_STATUS_MASK_TYPE(IntEnum): TEST_NOT_COMPLETED_SINCE_LAST_CLEAR = 0x10 TEST_FAILED_SINCE_LAST_CLEAR = 0x20 TEST_NOT_COMPLETED_THIS_OPERATION_CYCLE = 0x40 - WARNING_INDICATOR_REQUESTED = 0x80 + WARNING_INDICATOR_uds_requestED = 0x80 ALL = 0xFF class DTC_SEVERITY_MASK_TYPE(IntEnum): @@ -478,7 +542,7 @@ def read_dtc_information(address, dtc_report_type, dtc_status_mask_type=DTC_STAT dtc_report_type == DTC_REPORT_TYPE.DTC_BY_SEVERITY_MASK_RECORD: data += chr(dtc_severity_mask_type) + chr(dtc_status_mask_type) - resp = _request(address, SERVICE_TYPE.READ_DTC_INFORMATION, subfunction=dtc_report_type, data=data) + resp = _uds_request(address, SERVICE_TYPE.READ_DTC_INFORMATION, subfunction=dtc_report_type, data=data) # TODO: parse response return resp @@ -491,7 +555,7 @@ class CONTROL_OPTION_TYPE(IntEnum): def input_output_control_by_identifier(address, data_identifier_type, control_option_record, control_enable_mask_record=''): data = struct.pack('!H', data_identifier_type) + control_option_record + control_enable_mask_record - resp = _request(address, SERVICE_TYPE.INPUT_OUTPUT_CONTROL_BY_IDENTIFIER, subfunction=None, data=data) + resp = _uds_request(address, SERVICE_TYPE.INPUT_OUTPUT_CONTROL_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) @@ -509,7 +573,7 @@ class ROUTINE_IDENTIFIER_TYPE(IntEnum): def routine_control(address, routine_control_type, routine_identifier_type, routine_option_record=''): data = struct.pack('!H', routine_identifier_type) + routine_option_record - _request(address, SERVICE_TYPE.ROUTINE_CONTROL, subfunction=routine_control_type, data=data) + _uds_request(address, SERVICE_TYPE.ROUTINE_CONTROL, subfunction=routine_control_type, data=data) resp = resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != routine_identifier_type: raise ValueError('invalid response routine identifier: {}'.format(hex(resp_id))) @@ -531,7 +595,7 @@ def request_download(address, memory_address, memory_size, memory_address_bytes= raise ValueError('invalid memory_size: {}'.format(memory_size)) data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - resp = _request(address, SERVICE_TYPE.REQUEST_DOWNLOAD, subfunction=None, data=data) + resp = _uds_request(address, SERVICE_TYPE.REQUEST_DOWNLOAD, subfunction=None, data=data) max_num_bytes_len = ord(resp[0]) >> 4 if len(resp) > 0 else None if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] @@ -556,7 +620,7 @@ def request_upload(address, memory_address, memory_size, memory_address_bytes=4, raise ValueError('invalid memory_size: {}'.format(memory_size)) data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - resp = _request(address, SERVICE_TYPE.REQUEST_UPLOAD, subfunction=None, data=data) + resp = _uds_request(address, SERVICE_TYPE.REQUEST_UPLOAD, subfunction=None, data=data) max_num_bytes_len = ord(resp[0]) >> 4 if len(resp) > 0 else None if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] @@ -566,16 +630,27 @@ def request_upload(address, memory_address, memory_size, memory_address_bytes=4, return max_num_bytes # max number of bytes per transfer data request def transfer_data(address, block_sequence_count, data=''): - resp = _request(address, SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) + resp = _uds_request(address, SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) resp_id = ord(resp[0]) if len(resp) > 0 else None if resp_id != block_sequence_count: raise ValueError('invalid block_sequence_count: {}'.format(resp_id)) return resp[1:] def request_transfer_exit(address): - _request(address, SERVICE_TYPE.REQUEST_TRANSFER_EXIT, subfunction=None) + _uds_request(address, SERVICE_TYPE.REQUEST_TRANSFER_EXIT, subfunction=None) if __name__ == "__main__": + from python import Panda + panda = Panda() + bus = 0 + tx_addr = 0x18daf130 # EPS + tx_queue = Queue() + rx_queue = Queue() + can_reader_t = threading.Thread(target=_isotp_thread, args=(panda, bus, tx_addr, tx_queue, rx_queue)) + can_reader_t.daemon = True + can_reader_t.start() + # examples - vin = read_data_by_identifier(0x18da10f1, DATA_IDENTIFIER_TYPE.VIN) - print(vin) + tester_present(tx_addr) + app_id = read_data_by_identifier(tx_addr, DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) + print(app_id) From 01ef1fae3623b7b05bf196b19e7713b27c5a39e7 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Mon, 3 Dec 2018 22:08:58 -0800 Subject: [PATCH 06/31] zero pad messages before sending --- python/uds.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/uds.py b/python/uds.py index eb9329e296aaf8..b35061572a867a 100644 --- a/python/uds.py +++ b/python/uds.py @@ -134,6 +134,7 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): req = tx_queue.get(block=False) # reset rx rx_frame rx_frame = {"size": 0, "data": "", "sent": True} + req = req.ljust(8, "\x00") if (DEBUG): print "S:", hex(tx_addr), req.encode("hex") panda.can_send(tx_addr, req, bus) else: @@ -643,7 +644,7 @@ def request_transfer_exit(address): from python import Panda panda = Panda() bus = 0 - tx_addr = 0x18daf130 # EPS + tx_addr = 0x18da30f1 # EPS tx_queue = Queue() rx_queue = Queue() can_reader_t = threading.Thread(target=_isotp_thread, args=(panda, bus, tx_addr, tx_queue, rx_queue)) From 96623006322ad784810e8d24a5a6981fa5fa0a96 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Mon, 3 Dec 2018 22:33:14 -0800 Subject: [PATCH 07/31] fix remaining size calculation --- python/uds.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/python/uds.py b/python/uds.py index b35061572a867a..c471277ef6110e 100644 --- a/python/uds.py +++ b/python/uds.py @@ -4,6 +4,7 @@ from enum import IntEnum from Queue import Queue, Empty import threading +from binascii import hexlify DEBUG = True @@ -102,10 +103,10 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): while True: messages = panda.can_recv() for rx_addr, rx_ts, rx_data, rx_bus in messages: - if rx_bus != bus or rx_addr != filter_addr or len(data) == 0: + if rx_bus != bus or rx_addr != filter_addr or len(rx_data) == 0: continue - if (DEBUG): print "R:", hex(rx_addr), rx_data.encode('hex') + if (DEBUG): print("R: {} {}".format(hex(rx_addr), hexlify(rx_data))) if rx_data[0] >> 4 == 0x0: # single rx_frame rx_frame["size"] = rx_data[0] & 0xFF @@ -119,12 +120,12 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): rx_frame["sent"] = False # send flow control message (send all bytes) flow_ctl = "\x30\x00\x00".ljust(8, "\x00") - if (DEBUG): print "S:", hex(tx_addr), flow_ctl.encode("hex") + if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(flow_ctl))) panda.can_send(tx_addr, flow_ctl, bus) elif rx_data[0] >> 4 == 0x2: # consecutive rx_frame assert rx_frame["sent"] == False, "no active frame" - rx_size = rx_frame["size"] - len(rx_data) + rx_size = rx_frame["size"] - len(rx_frame["data"]) rx_frame["data"] += rx_data[1:1+min(rx_size, 7)] if rx_size <= 7: rx_frame["sent"] = True @@ -134,8 +135,9 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): req = tx_queue.get(block=False) # reset rx rx_frame rx_frame = {"size": 0, "data": "", "sent": True} - req = req.ljust(8, "\x00") - if (DEBUG): print "S:", hex(tx_addr), req.encode("hex") + # TODO: support sending more than 7 bytes + req = (chr(len(req)) + req).ljust(8, "\x00") + if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(req))) panda.can_send(tx_addr, req, bus) else: time.sleep(0.001) @@ -154,7 +156,7 @@ def _uds_request(address, service_type, subfunction=None, data=None): resp = rx_queue.get(block=True, timeout=10) except Empty: raise MessageTimeoutError("timeout waiting for response") - resp_sid = ord(resp[0]) if len(resp) > 0 else None + resp_sid = resp[0] if len(resp) > 0 else None # negative response if resp_sid == 0x7F: @@ -175,7 +177,7 @@ def _uds_request(address, service_type, subfunction=None, data=None): raise InvalidServiceIdError('invalid response service id: {}'.format(resp_sid_hex)) if subfunction is not None: - resp_sfn = ord(resp[1]) if len(resp) > 1 else None + resp_sfn = resp[1] if len(resp) > 1 else None if subfunction != resp_sfn: resp_sfn_hex = hex(resp_sfn) if resp_sfn is not None else None raise InvalidSubFunctioneError('invalid response subfunction: {}'.format(hex(resp_sfn))) From 5e89a9c728de0b3a83a9910cc75b0c48e683cc2a Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Mon, 3 Dec 2018 23:27:29 -0800 Subject: [PATCH 08/31] clear rx buffer and numeric error ids --- python/uds.py | 127 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 51 deletions(-) diff --git a/python/uds.py b/python/uds.py index c471277ef6110e..41f4e98d624002 100644 --- a/python/uds.py +++ b/python/uds.py @@ -6,7 +6,7 @@ import threading from binascii import hexlify -DEBUG = True +DEBUG = False class SERVICE_TYPE(IntEnum): DIAGNOSTIC_SESSION_CONTROL = 0x10 @@ -36,55 +36,61 @@ class SERVICE_TYPE(IntEnum): REQUEST_TRANSFER_EXIT = 0x37 _negative_response_codes = { - '\x00': 'positive response', - '\x10': 'general reject', - '\x11': 'service not supported', - '\x12': 'sub-function not supported', - '\x13': 'incorrect message length or invalid format', - '\x14': 'response too long', - '\x21': 'busy repeat request', - '\x22': 'conditions not correct', - '\x24': 'request sequence error', - '\x25': 'no response from subnet component', - '\x26': 'failure prevents execution of requested action', - '\x31': 'request out of range', - '\x33': 'security access denied', - '\x35': 'invalid key', - '\x36': 'exceed numebr of attempts', - '\x37': 'required time delay not expired', - '\x70': 'upload download not accepted', - '\x71': 'transfer data suspended', - '\x72': 'general programming failure', - '\x73': 'wrong block sequence counter', - '\x78': 'request correctly received - response pending', - '\x7e': 'sub-function not supported in active session', - '\x7f': 'service not supported in active session', - '\x81': 'rpm too high', - '\x82': 'rpm too low', - '\x83': 'engine is running', - '\x84': 'engine is not running', - '\x85': 'engine run time too low', - '\x86': 'temperature too high', - '\x87': 'temperature too low', - '\x88': 'vehicle speed too high', - '\x89': 'vehicle speed too low', - '\x8a': 'throttle/pedal too high', - '\x8b': 'throttle/pedal too low', - '\x8c': 'transmission not in neutral', - '\x8d': 'transmission not in gear', - '\x8f': 'brake switch(es) not closed', - '\x90': 'shifter lever not in park', - '\x91': 'torque converter clutch locked', - '\x92': 'voltage too high', - '\x93': 'voltage too low', + 0x00: 'positive response', + 0x10: 'general reject', + 0x11: 'service not supported', + 0x12: 'sub-function not supported', + 0x13: 'incorrect message length or invalid format', + 0x14: 'response too long', + 0x21: 'busy repeat request', + 0x22: 'conditions not correct', + 0x24: 'request sequence error', + 0x25: 'no response from subnet component', + 0x26: 'failure prevents execution of requested action', + 0x31: 'request out of range', + 0x33: 'security access denied', + 0x35: 'invalid key', + 0x36: 'exceed numebr of attempts', + 0x37: 'required time delay not expired', + 0x70: 'upload download not accepted', + 0x71: 'transfer data suspended', + 0x72: 'general programming failure', + 0x73: 'wrong block sequence counter', + 0x78: 'request correctly received - response pending', + 0x7e: 'sub-function not supported in active session', + 0x7f: 'service not supported in active session', + 0x81: 'rpm too high', + 0x82: 'rpm too low', + 0x83: 'engine is running', + 0x84: 'engine is not running', + 0x85: 'engine run time too low', + 0x86: 'temperature too high', + 0x87: 'temperature too low', + 0x88: 'vehicle speed too high', + 0x89: 'vehicle speed too low', + 0x8a: 'throttle/pedal too high', + 0x8b: 'throttle/pedal too low', + 0x8c: 'transmission not in neutral', + 0x8d: 'transmission not in gear', + 0x8f: 'brake switch(es) not closed', + 0x90: 'shifter lever not in park', + 0x91: 'torque converter clutch locked', + 0x92: 'voltage too high', + 0x93: 'voltage too low', } class MessageTimeoutError(Exception): pass + class NegativeResponseError(Exception): - pass + def __init__(self, message, service_id, error_code): + super(Exception, self).__init__(message) + self.service_id = service_id + self.error_code = error_code + class InvalidServiceIdError(Exception): pass + class InvalidSubFunctioneError(Exception): pass @@ -98,8 +104,12 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): else: raise ValueError("invalid tx_addr: {}".format(tx_addr)) rx_frame = {"size": 0, "data": "", "sent": True} - - panda.can_clear(0) + + # clear tx buffer + panda.can_clear(bus) + # clear rx buffer + panda.can_clear(0xFFFF) + time.sleep(1) while True: messages = panda.can_recv() for rx_addr, rx_ts, rx_data, rx_bus in messages: @@ -140,7 +150,7 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(req))) panda.can_send(tx_addr, req, bus) else: - time.sleep(0.001) + time.sleep(0.01) finally: panda.close() @@ -160,16 +170,18 @@ def _uds_request(address, service_type, subfunction=None, data=None): # negative response if resp_sid == 0x7F: + service_id = 0 try: - error_service = SERVICE_TYPE(ord(resp[1])).name + service_id = resp[1] + service_desc = SERVICE_TYPE(service_id).name except: - error_service = 'NON_STANDARD_SERVICE' - error_code = hex(ord(resp[2])) if len(resp) > 2 else '0x??' + service_desc = 'NON_STANDARD_SERVICE' + error_code = resp[2] if len(resp) > 2 else '0x??' try: - error_desc = _negative_response_codes[resp[2]] + error_desc = _negative_response_codes[error_code] except: error_desc = 'unknown error' - raise NegativeResponseError('{} - {} - {}'.format(error_service, error_code, error_desc)) + raise NegativeResponseError('{} - {}'.format(service_desc, error_desc), service_id, error_code) # positive response if service_type+0x40 != resp_sid: @@ -644,6 +656,7 @@ def request_transfer_exit(address): if __name__ == "__main__": from python import Panda + from string import printable panda = Panda() bus = 0 tx_addr = 0x18da30f1 # EPS @@ -657,3 +670,15 @@ def request_transfer_exit(address): tester_present(tx_addr) app_id = read_data_by_identifier(tx_addr, DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) print(app_id) + # for i in range(0xF100, 0xFFFF): + # try: + # dat = read_data_by_identifier(tx_addr, i) + # desc = "" + # try: + # desc = " [" + DATA_IDENTIFIER_TYPE(i).name + "]" + # except ValueError: + # pass + # print("{}:{} {} {}".format(hex(i), desc, hexlify(dat), dat.decode(errors="ignore"))) + # except NegativeResponseError as e: + # if e.error_code != 0x31: + # print("{}: {}".format(hex(i), e)) From 8ee89a091d67d53962c6e20c1bf8088c2c100632 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Tue, 4 Dec 2018 01:00:43 -0800 Subject: [PATCH 09/31] multi-frame tx --- python/uds.py | 69 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/python/uds.py b/python/uds.py index 41f4e98d624002..ed0404a6f031e2 100644 --- a/python/uds.py +++ b/python/uds.py @@ -103,7 +103,8 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): filter_addr = (tx_addr & 0xFFFF0000) + (tx_addr<<8 & 0xFF00) + (tx_addr>>8 & 0xFF) else: raise ValueError("invalid tx_addr: {}".format(tx_addr)) - rx_frame = {"size": 0, "data": "", "sent": True} + rx_frame = {"size": 0, "data": "", "idx": 0, "done": True} + tx_frame = {"size": 0, "data": "", "idx": 0, "done": True} # clear tx buffer panda.can_clear(bus) @@ -121,34 +122,64 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): # single rx_frame rx_frame["size"] = rx_data[0] & 0xFF rx_frame["data"] = rx_data[1:1+rx_frame["size"]] - rx_frame["sent"] = True + rx_frame["idx"] = 0 + rx_frame["done"] = True rx_queue.put(rx_frame["data"]) elif rx_data[0] >> 4 == 0x1: # first rx_frame rx_frame["size"] = ((rx_data[0] & 0x0F) << 8) + rx_data[1] rx_frame["data"] = rx_data[2:] - rx_frame["sent"] = False + rx_frame["idx"] = 0 + rx_frame["done"] = False # send flow control message (send all bytes) - flow_ctl = "\x30\x00\x00".ljust(8, "\x00") - if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(flow_ctl))) - panda.can_send(tx_addr, flow_ctl, bus) + msg = "\x30\x00\x00".ljust(8, "\x00") + if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) + panda.can_send(tx_addr, msg, bus) elif rx_data[0] >> 4 == 0x2: - # consecutive rx_frame - assert rx_frame["sent"] == False, "no active frame" + # consecutive rx frame + assert rx_frame["done"] == False, "rx: no active frame" + # validate frame index + rx_frame["idx"] += 1 + assert rx_frame["idx"] & 0xF == rx_data[0] & 0xF, "rx: invalid consecutive frame index" rx_size = rx_frame["size"] - len(rx_frame["data"]) rx_frame["data"] += rx_data[1:1+min(rx_size, 7)] - if rx_size <= 7: - rx_frame["sent"] = True + if rx_frame["size"] == len(rx_frame["data"]): + rx_frame["done"] = True rx_queue.put(rx_frame["data"]) + elif rx_data[0] >> 4 == 0x3: + # flow control + assert tx_frame["done"] == False, "tx: no active frame" + # TODO: support non-zero block size and separate time + assert rx_data[0] == 0x30 and rx_data[2] == 0x00, "tx: flow-control requires: continue, no delay" + # first frame = 6 bytes, each consecutive frame = 7 bytes + start = 6 + tx_frame["idx"] * 7 + count = rx_data[1] + end = start + count * 7 if count > 0 else tx_frame["size"] + for i in range(start, end, 7): + tx_frame["idx"] += 1 + # consecutive tx frames + msg = (chr(0x20 | (tx_frame["idx"] & 0xF)) + tx_frame["data"][i:i+7]).ljust(8, "\x00") + if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) + panda.can_send(tx_addr, msg, bus) + tx_frame["done"] = True if not tx_queue.empty(): req = tx_queue.get(block=False) - # reset rx rx_frame - rx_frame = {"size": 0, "data": "", "sent": True} - # TODO: support sending more than 7 bytes - req = (chr(len(req)) + req).ljust(8, "\x00") - if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(req))) - panda.can_send(tx_addr, req, bus) + # reset rx and tx frames + rx_frame = {"size": 0, "data": "", "idx": 0, "done": True} + tx_frame = {"size": len(req), "data": req, "idx": 0, "done": False} + if tx_frame["size"] < 8: + # single frame + tx_frame["done"] = True + msg = (chr(tx_frame["size"]) + tx_frame["data"]).ljust(8, "\x00") + if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) + panda.can_send(tx_addr, msg, bus) + else: + # first rx_frame + tx_frame["done"] = False + msg = (struct.pack("!H", 0x1000 | tx_frame["size"]) + tx_frame["data"][:6]).ljust(8, "\x00") + if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) + panda.can_send(tx_addr, msg, bus) else: time.sleep(0.01) finally: @@ -667,10 +698,14 @@ def request_transfer_exit(address): can_reader_t.start() # examples + print("tester present ...") tester_present(tx_addr) + print("extended diagnostic session ...") + diagnostic_session_control(tx_addr, SESSION_TYPE.EXTENDED_DIAGNOSTIC) + print("application software id ...") app_id = read_data_by_identifier(tx_addr, DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) print(app_id) - # for i in range(0xF100, 0xFFFF): + # for i in range(0xF100, 0xF2FF): # try: # dat = read_data_by_identifier(tx_addr, i) # desc = "" From 33a5167d9356db1321236e9d76c73818211821ef Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Wed, 5 Dec 2018 01:19:19 -0800 Subject: [PATCH 10/31] bug fixes --- python/uds.py | 82 +++++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/python/uds.py b/python/uds.py index ed0404a6f031e2..6580bd3b638133 100644 --- a/python/uds.py +++ b/python/uds.py @@ -193,26 +193,32 @@ def _uds_request(address, service_type, subfunction=None, data=None): if data is not None: req += data tx_queue.put(req) - try: - resp = rx_queue.get(block=True, timeout=10) - except Empty: - raise MessageTimeoutError("timeout waiting for response") - resp_sid = resp[0] if len(resp) > 0 else None - - # negative response - if resp_sid == 0x7F: - service_id = 0 - try: - service_id = resp[1] - service_desc = SERVICE_TYPE(service_id).name - except: - service_desc = 'NON_STANDARD_SERVICE' - error_code = resp[2] if len(resp) > 2 else '0x??' + + while True: try: - error_desc = _negative_response_codes[error_code] - except: - error_desc = 'unknown error' - raise NegativeResponseError('{} - {}'.format(service_desc, error_desc), service_id, error_code) + resp = rx_queue.get(block=True, timeout=10) + except Empty: + raise MessageTimeoutError("timeout waiting for response") + resp_sid = resp[0] if len(resp) > 0 else None + + # negative response + if resp_sid == 0x7F: + service_id = resp[1] if len(resp) > 1 else -1 + try: + service_desc = SERVICE_TYPE(service_id).name + except Exception: + service_desc = 'NON_STANDARD_SERVICE' + error_code = resp[2] if len(resp) > 2 else -1 + try: + error_desc = _negative_response_codes[error_code] + except Exception: + error_desc = 'unknown error' + # wait for another message if response pending + if error_code == 0x78: + time.sleep(0.1) + continue + raise NegativeResponseError('{} - {}'.format(service_desc, error_desc), service_id, error_code) + break # positive response if service_type+0x40 != resp_sid: @@ -249,7 +255,7 @@ def ecu_reset(address, reset_type): resp = _uds_request(address, SERVICE_TYPE.ECU_RESET, subfunction=reset_type) power_down_time = None if reset_type == RESET_TYPE.ENABLE_RAPID_POWER_SHUTDOWN: - power_down_time = ord(resp[0]) + power_down_time = resp[0] return power_down_time class ACCESS_TYPE(IntEnum): @@ -257,7 +263,7 @@ class ACCESS_TYPE(IntEnum): SEND_KEY = 2 def security_access(address, access_type, security_key=None): - request_seed = access_type % 2 == 0 + request_seed = access_type % 2 != 0 if request_seed and security_key is not None: raise ValueError('security_key not allowed') if not request_seed and security_key is None: @@ -339,13 +345,13 @@ def response_on_event(address, response_event_type, store_event, window_time, ev if response_event_type == REPORT_ACTIVATED_EVENTS: return { - "num_of_activated_events": ord(resp[0]), + "num_of_activated_events": resp[0], "data": resp[1:], # TODO: parse the reset of response } return { - "num_of_identified_events": ord(resp[0]), - "event_window_time": ord(resp[1]), + "num_of_identified_events": resp[0], + "event_window_time": resp[1], "data": resp[2:], # TODO: parse the reset of response } @@ -423,7 +429,7 @@ def read_memory_by_address(address, memory_address, memory_size, memory_address_ raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = struct.pack('!BB', memory_size_bytes, memory_address_bytes) + data = chr(memory_size_bytes<<4 | memory_address_bytes) if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -464,14 +470,14 @@ def dynamically_define_data_identifier(address, dynamic_definition_type, dynamic raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = struct.pack('!BB', memory_size_bytes, memory_address_bytes) + data = chr(memory_size_bytes<<4 | memory_address_bytes) data = struct.pack('!H', dynamic_data_identifier) if dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_IDENTIFIER: for s in source_definitions: data += struct.pack('!H', s["data_identifier"]) + chr(s["position"]) + chr(s["memory_size"]) elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_MEMORY_ADDRESS: - data += struct.pack('!BB', memory_size_bytes, memory_address_bytes) + data += chr(memory_size_bytes<<4 | memory_address_bytes) for s in source_definitions: if s["memory_address"] >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(s["memory_address"])) @@ -497,7 +503,7 @@ def write_memory_by_address(address, memory_address, memory_size, data_record, m raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = struct.pack('!BB', memory_size_bytes, memory_address_bytes) + data = chr(memory_size_bytes<<4 | memory_address_bytes) if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -619,8 +625,8 @@ class ROUTINE_IDENTIFIER_TYPE(IntEnum): def routine_control(address, routine_control_type, routine_identifier_type, routine_option_record=''): data = struct.pack('!H', routine_identifier_type) + routine_option_record - _uds_request(address, SERVICE_TYPE.ROUTINE_CONTROL, subfunction=routine_control_type, data=data) - resp = resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None + resp = _uds_request(address, SERVICE_TYPE.ROUTINE_CONTROL, subfunction=routine_control_type, data=data) + resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != routine_identifier_type: raise ValueError('invalid response routine identifier: {}'.format(hex(resp_id))) return resp[2:] @@ -632,7 +638,7 @@ def request_download(address, memory_address, memory_size, memory_address_bytes= raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data += struct.pack('!BB', memory_size_bytes, memory_address_bytes) + data += chr(memory_size_bytes<<4 | memory_address_bytes) if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -642,7 +648,7 @@ def request_download(address, memory_address, memory_size, memory_address_bytes= data += struct.pack('!I', memory_size)[4-memory_size_bytes:] resp = _uds_request(address, SERVICE_TYPE.REQUEST_DOWNLOAD, subfunction=None, data=data) - max_num_bytes_len = ord(resp[0]) >> 4 if len(resp) > 0 else None + max_num_bytes_len = resp[0] >> 4 if len(resp) > 0 else None if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] else: @@ -657,7 +663,7 @@ def request_upload(address, memory_address, memory_size, memory_address_bytes=4, raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data += struct.pack('!BB', memory_size_bytes, memory_address_bytes) + data += chr(memory_size_bytes<<4 | memory_address_bytes) if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -667,7 +673,7 @@ def request_upload(address, memory_address, memory_size, memory_address_bytes=4, data += struct.pack('!I', memory_size)[4-memory_size_bytes:] resp = _uds_request(address, SERVICE_TYPE.REQUEST_UPLOAD, subfunction=None, data=data) - max_num_bytes_len = ord(resp[0]) >> 4 if len(resp) > 0 else None + max_num_bytes_len = resp[0] >> 4 if len(resp) > 0 else None if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] else: @@ -676,8 +682,9 @@ def request_upload(address, memory_address, memory_size, memory_address_bytes=4, return max_num_bytes # max number of bytes per transfer data request def transfer_data(address, block_sequence_count, data=''): + data = chr(block_sequence_count)+data resp = _uds_request(address, SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) - resp_id = ord(resp[0]) if len(resp) > 0 else None + resp_id = resp[0] if len(resp) > 0 else None if resp_id != block_sequence_count: raise ValueError('invalid block_sequence_count: {}'.format(resp_id)) return resp[1:] @@ -687,7 +694,6 @@ def request_transfer_exit(address): if __name__ == "__main__": from python import Panda - from string import printable panda = Panda() bus = 0 tx_addr = 0x18da30f1 # EPS @@ -702,7 +708,7 @@ def request_transfer_exit(address): tester_present(tx_addr) print("extended diagnostic session ...") diagnostic_session_control(tx_addr, SESSION_TYPE.EXTENDED_DIAGNOSTIC) - print("application software id ...") + print("read data by id: application software id ...") app_id = read_data_by_identifier(tx_addr, DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) print(app_id) # for i in range(0xF100, 0xF2FF): @@ -713,7 +719,7 @@ def request_transfer_exit(address): # desc = " [" + DATA_IDENTIFIER_TYPE(i).name + "]" # except ValueError: # pass - # print("{}:{} {} {}".format(hex(i), desc, hexlify(dat), dat.decode(errors="ignore"))) + # print("{}:{} {} {}".format(hex(i), desc, hexlify(dat), "")) #, dat.decode(errors="ignore"))) # except NegativeResponseError as e: # if e.error_code != 0x31: # print("{}: {}".format(hex(i), e)) From 78f413d88f9860638c33a55fc3b469b5d4fcae21 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Mon, 10 Dec 2018 20:23:51 -0800 Subject: [PATCH 11/31] flow control delay --- python/uds.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/uds.py b/python/uds.py index 6580bd3b638133..176ad003cd695b 100644 --- a/python/uds.py +++ b/python/uds.py @@ -149,8 +149,11 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): elif rx_data[0] >> 4 == 0x3: # flow control assert tx_frame["done"] == False, "tx: no active frame" - # TODO: support non-zero block size and separate time - assert rx_data[0] == 0x30 and rx_data[2] == 0x00, "tx: flow-control requires: continue, no delay" + # TODO: support wait/overflow + assert rx_data[0] == 0x30, "tx: flow-control requires: continue" + delay_ts = ord(rx_data[2]) & 0x7F + # milliseconds if first bit == 0, micro seconds if first bit == 1 + delay_div = 1000. if ord(rx_data[2]) & 0x80 == 0 else 100000. # first frame = 6 bytes, each consecutive frame = 7 bytes start = 6 + tx_frame["idx"] * 7 count = rx_data[1] @@ -161,6 +164,8 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): msg = (chr(0x20 | (tx_frame["idx"] & 0xF)) + tx_frame["data"][i:i+7]).ljust(8, "\x00") if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) panda.can_send(tx_addr, msg, bus) + if delay_ms: + time.sleep(delay_ts / delay_div) tx_frame["done"] = True if not tx_queue.empty(): From 48b8dcc6f9913b8d86fa1c940f7ec9fb5d62f7b9 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Mon, 10 Dec 2018 20:28:33 -0800 Subject: [PATCH 12/31] fix flow control delay scale --- python/uds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/uds.py b/python/uds.py index 176ad003cd695b..3dac81f871c8fc 100644 --- a/python/uds.py +++ b/python/uds.py @@ -152,8 +152,8 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): # TODO: support wait/overflow assert rx_data[0] == 0x30, "tx: flow-control requires: continue" delay_ts = ord(rx_data[2]) & 0x7F - # milliseconds if first bit == 0, micro seconds if first bit == 1 - delay_div = 1000. if ord(rx_data[2]) & 0x80 == 0 else 100000. + # scale is 1 milliseconds if first bit == 0, 100 micro seconds if first bit == 1 + delay_div = 1000. if ord(rx_data[2]) & 0x80 == 0 else 10000. # first frame = 6 bytes, each consecutive frame = 7 bytes start = 6 + tx_frame["idx"] * 7 count = rx_data[1] From c641e66f723d71dffa7fda5513d551b05b7d0406 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Mon, 10 Dec 2018 20:39:26 -0800 Subject: [PATCH 13/31] fix typo --- python/uds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/uds.py b/python/uds.py index 3dac81f871c8fc..416c344696cbae 100644 --- a/python/uds.py +++ b/python/uds.py @@ -164,7 +164,7 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): msg = (chr(0x20 | (tx_frame["idx"] & 0xF)) + tx_frame["data"][i:i+7]).ljust(8, "\x00") if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) panda.can_send(tx_addr, msg, bus) - if delay_ms: + if delay_ts: time.sleep(delay_ts / delay_div) tx_frame["done"] = True From 4429600d8e47cb71786c92cc881352acf42087de Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Mon, 10 Dec 2018 20:50:56 -0800 Subject: [PATCH 14/31] fix separation time parsing --- python/uds.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/uds.py b/python/uds.py index 416c344696cbae..18e29aca9d9bfe 100644 --- a/python/uds.py +++ b/python/uds.py @@ -151,9 +151,9 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): assert tx_frame["done"] == False, "tx: no active frame" # TODO: support wait/overflow assert rx_data[0] == 0x30, "tx: flow-control requires: continue" - delay_ts = ord(rx_data[2]) & 0x7F + delay_ts = rx_data[2] & 0x7F # scale is 1 milliseconds if first bit == 0, 100 micro seconds if first bit == 1 - delay_div = 1000. if ord(rx_data[2]) & 0x80 == 0 else 10000. + delay_div = 1000. if rx_data[2] & 0x80 == 0 else 100000. # first frame = 6 bytes, each consecutive frame = 7 bytes start = 6 + tx_frame["idx"] * 7 count = rx_data[1] @@ -164,7 +164,7 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): msg = (chr(0x20 | (tx_frame["idx"] & 0xF)) + tx_frame["data"][i:i+7]).ljust(8, "\x00") if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) panda.can_send(tx_addr, msg, bus) - if delay_ts: + if delay_ts > 0: time.sleep(delay_ts / delay_div) tx_frame["done"] = True From 59cd2b47f91f60a8914f6bebff59afd98ce2c594 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Mon, 10 Dec 2018 20:52:35 -0800 Subject: [PATCH 15/31] handle separation time in microseconds --- python/uds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/uds.py b/python/uds.py index 18e29aca9d9bfe..e762a8c8683dab 100644 --- a/python/uds.py +++ b/python/uds.py @@ -153,7 +153,7 @@ def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): assert rx_data[0] == 0x30, "tx: flow-control requires: continue" delay_ts = rx_data[2] & 0x7F # scale is 1 milliseconds if first bit == 0, 100 micro seconds if first bit == 1 - delay_div = 1000. if rx_data[2] & 0x80 == 0 else 100000. + delay_div = 1000. if rx_data[2] & 0x80 == 0 else 10000. # first frame = 6 bytes, each consecutive frame = 7 bytes start = 6 + tx_frame["idx"] * 7 count = rx_data[1] From 80fb6a6fa09caa03ed0f5f7cb550b88c25e92d95 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Wed, 12 Dec 2018 09:08:09 -0800 Subject: [PATCH 16/31] convert uds lib to class --- python/uds.py | 1040 ++++++++++++++++++++++++------------------------- 1 file changed, 513 insertions(+), 527 deletions(-) diff --git a/python/uds.py b/python/uds.py index e762a8c8683dab..4c5908108b496a 100644 --- a/python/uds.py +++ b/python/uds.py @@ -6,8 +6,6 @@ import threading from binascii import hexlify -DEBUG = False - class SERVICE_TYPE(IntEnum): DIAGNOSTIC_SESSION_CONTROL = 0x10 ECU_RESET = 0x11 @@ -35,220 +33,12 @@ class SERVICE_TYPE(IntEnum): TRANSFER_DATA = 0x36 REQUEST_TRANSFER_EXIT = 0x37 -_negative_response_codes = { - 0x00: 'positive response', - 0x10: 'general reject', - 0x11: 'service not supported', - 0x12: 'sub-function not supported', - 0x13: 'incorrect message length or invalid format', - 0x14: 'response too long', - 0x21: 'busy repeat request', - 0x22: 'conditions not correct', - 0x24: 'request sequence error', - 0x25: 'no response from subnet component', - 0x26: 'failure prevents execution of requested action', - 0x31: 'request out of range', - 0x33: 'security access denied', - 0x35: 'invalid key', - 0x36: 'exceed numebr of attempts', - 0x37: 'required time delay not expired', - 0x70: 'upload download not accepted', - 0x71: 'transfer data suspended', - 0x72: 'general programming failure', - 0x73: 'wrong block sequence counter', - 0x78: 'request correctly received - response pending', - 0x7e: 'sub-function not supported in active session', - 0x7f: 'service not supported in active session', - 0x81: 'rpm too high', - 0x82: 'rpm too low', - 0x83: 'engine is running', - 0x84: 'engine is not running', - 0x85: 'engine run time too low', - 0x86: 'temperature too high', - 0x87: 'temperature too low', - 0x88: 'vehicle speed too high', - 0x89: 'vehicle speed too low', - 0x8a: 'throttle/pedal too high', - 0x8b: 'throttle/pedal too low', - 0x8c: 'transmission not in neutral', - 0x8d: 'transmission not in gear', - 0x8f: 'brake switch(es) not closed', - 0x90: 'shifter lever not in park', - 0x91: 'torque converter clutch locked', - 0x92: 'voltage too high', - 0x93: 'voltage too low', -} - -class MessageTimeoutError(Exception): - pass - -class NegativeResponseError(Exception): - def __init__(self, message, service_id, error_code): - super(Exception, self).__init__(message) - self.service_id = service_id - self.error_code = error_code - -class InvalidServiceIdError(Exception): - pass - -class InvalidSubFunctioneError(Exception): - pass - -def _isotp_thread(panda, bus, tx_addr, tx_queue, rx_queue): - try: - panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) - if tx_addr < 0xFFF8: - filter_addr = tx_addr+8 - elif tx_addr > 0x10000000 and tx_addr < 0xFFFFFFFF: - filter_addr = (tx_addr & 0xFFFF0000) + (tx_addr<<8 & 0xFF00) + (tx_addr>>8 & 0xFF) - else: - raise ValueError("invalid tx_addr: {}".format(tx_addr)) - rx_frame = {"size": 0, "data": "", "idx": 0, "done": True} - tx_frame = {"size": 0, "data": "", "idx": 0, "done": True} - - # clear tx buffer - panda.can_clear(bus) - # clear rx buffer - panda.can_clear(0xFFFF) - time.sleep(1) - while True: - messages = panda.can_recv() - for rx_addr, rx_ts, rx_data, rx_bus in messages: - if rx_bus != bus or rx_addr != filter_addr or len(rx_data) == 0: - continue - - if (DEBUG): print("R: {} {}".format(hex(rx_addr), hexlify(rx_data))) - if rx_data[0] >> 4 == 0x0: - # single rx_frame - rx_frame["size"] = rx_data[0] & 0xFF - rx_frame["data"] = rx_data[1:1+rx_frame["size"]] - rx_frame["idx"] = 0 - rx_frame["done"] = True - rx_queue.put(rx_frame["data"]) - elif rx_data[0] >> 4 == 0x1: - # first rx_frame - rx_frame["size"] = ((rx_data[0] & 0x0F) << 8) + rx_data[1] - rx_frame["data"] = rx_data[2:] - rx_frame["idx"] = 0 - rx_frame["done"] = False - # send flow control message (send all bytes) - msg = "\x30\x00\x00".ljust(8, "\x00") - if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) - panda.can_send(tx_addr, msg, bus) - elif rx_data[0] >> 4 == 0x2: - # consecutive rx frame - assert rx_frame["done"] == False, "rx: no active frame" - # validate frame index - rx_frame["idx"] += 1 - assert rx_frame["idx"] & 0xF == rx_data[0] & 0xF, "rx: invalid consecutive frame index" - rx_size = rx_frame["size"] - len(rx_frame["data"]) - rx_frame["data"] += rx_data[1:1+min(rx_size, 7)] - if rx_frame["size"] == len(rx_frame["data"]): - rx_frame["done"] = True - rx_queue.put(rx_frame["data"]) - elif rx_data[0] >> 4 == 0x3: - # flow control - assert tx_frame["done"] == False, "tx: no active frame" - # TODO: support wait/overflow - assert rx_data[0] == 0x30, "tx: flow-control requires: continue" - delay_ts = rx_data[2] & 0x7F - # scale is 1 milliseconds if first bit == 0, 100 micro seconds if first bit == 1 - delay_div = 1000. if rx_data[2] & 0x80 == 0 else 10000. - # first frame = 6 bytes, each consecutive frame = 7 bytes - start = 6 + tx_frame["idx"] * 7 - count = rx_data[1] - end = start + count * 7 if count > 0 else tx_frame["size"] - for i in range(start, end, 7): - tx_frame["idx"] += 1 - # consecutive tx frames - msg = (chr(0x20 | (tx_frame["idx"] & 0xF)) + tx_frame["data"][i:i+7]).ljust(8, "\x00") - if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) - panda.can_send(tx_addr, msg, bus) - if delay_ts > 0: - time.sleep(delay_ts / delay_div) - tx_frame["done"] = True - - if not tx_queue.empty(): - req = tx_queue.get(block=False) - # reset rx and tx frames - rx_frame = {"size": 0, "data": "", "idx": 0, "done": True} - tx_frame = {"size": len(req), "data": req, "idx": 0, "done": False} - if tx_frame["size"] < 8: - # single frame - tx_frame["done"] = True - msg = (chr(tx_frame["size"]) + tx_frame["data"]).ljust(8, "\x00") - if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) - panda.can_send(tx_addr, msg, bus) - else: - # first rx_frame - tx_frame["done"] = False - msg = (struct.pack("!H", 0x1000 | tx_frame["size"]) + tx_frame["data"][:6]).ljust(8, "\x00") - if (DEBUG): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) - panda.can_send(tx_addr, msg, bus) - else: - time.sleep(0.01) - finally: - panda.close() - -# generic uds request -def _uds_request(address, service_type, subfunction=None, data=None): - req = chr(service_type) - if subfunction is not None: - req += chr(subfunction) - if data is not None: - req += data - tx_queue.put(req) - - while True: - try: - resp = rx_queue.get(block=True, timeout=10) - except Empty: - raise MessageTimeoutError("timeout waiting for response") - resp_sid = resp[0] if len(resp) > 0 else None - - # negative response - if resp_sid == 0x7F: - service_id = resp[1] if len(resp) > 1 else -1 - try: - service_desc = SERVICE_TYPE(service_id).name - except Exception: - service_desc = 'NON_STANDARD_SERVICE' - error_code = resp[2] if len(resp) > 2 else -1 - try: - error_desc = _negative_response_codes[error_code] - except Exception: - error_desc = 'unknown error' - # wait for another message if response pending - if error_code == 0x78: - time.sleep(0.1) - continue - raise NegativeResponseError('{} - {}'.format(service_desc, error_desc), service_id, error_code) - break - - # positive response - if service_type+0x40 != resp_sid: - resp_sid_hex = hex(resp_sid) if resp_sid is not None else None - raise InvalidServiceIdError('invalid response service id: {}'.format(resp_sid_hex)) - - if subfunction is not None: - resp_sfn = resp[1] if len(resp) > 1 else None - if subfunction != resp_sfn: - resp_sfn_hex = hex(resp_sfn) if resp_sfn is not None else None - raise InvalidSubFunctioneError('invalid response subfunction: {}'.format(hex(resp_sfn))) - - # return data (exclude service id and sub-function id) - return resp[(1 if subfunction is None else 2):] - -# services class SESSION_TYPE(IntEnum): DEFAULT = 1 PROGRAMMING = 2 EXTENDED_DIAGNOSTIC = 3 SAFETY_SYSTEM_DIAGNOSTIC = 4 -def diagnostic_session_control(address, session_type): - _uds_request(address, SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, subfunction=session_type) - class RESET_TYPE(IntEnum): HARD = 1 KEY_OFF_ON = 2 @@ -256,28 +46,10 @@ class RESET_TYPE(IntEnum): ENABLE_RAPID_POWER_SHUTDOWN = 4 DISABLE_RAPID_POWER_SHUTDOWN = 5 -def ecu_reset(address, reset_type): - resp = _uds_request(address, SERVICE_TYPE.ECU_RESET, subfunction=reset_type) - power_down_time = None - if reset_type == RESET_TYPE.ENABLE_RAPID_POWER_SHUTDOWN: - power_down_time = resp[0] - return power_down_time - class ACCESS_TYPE(IntEnum): REQUEST_SEED = 1 SEND_KEY = 2 -def security_access(address, access_type, security_key=None): - request_seed = access_type % 2 != 0 - if request_seed and security_key is not None: - raise ValueError('security_key not allowed') - if not request_seed and security_key is None: - raise ValueError('security_key is missing') - resp = _uds_request(address, SERVICE_TYPE.SECURITY_ACCESS, subfunction=access_type, data=security_key) - if request_seed: - security_seed = resp - return security_seed - class CONTROL_TYPE(IntEnum): ENABLE_RX_ENABLE_TX = 0 ENABLE_RX_DISABLE_TX = 1 @@ -289,48 +61,16 @@ class MESSAGE_TYPE(IntEnum): NETWORK_MANAGEMENT = 2 NORMAL_AND_NETWORK_MANAGEMENT = 3 -def communication_control(address, control_type, message_type): - data = chr(message_type) - _uds_request(address, SERVICE_TYPE.COMMUNICATION_CONTROL, subfunction=control_type, data=data) - -def tester_present(address): - _uds_request(address, SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) - class TIMING_PARAMETER_TYPE(IntEnum): READ_EXTENDED_SET = 1 SET_TO_DEFAULT_VALUES = 2 READ_CURRENTLY_ACTIVE = 3 SET_TO_GIVEN_VALUES = 4 -def access_timing_parameter(address, timing_parameter_type, parameter_values): - write_custom_values = timing_parameter_type == ACCESS_TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES - read_values = ( - timing_parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or - timing_parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_EXTENDED_SET - ) - if not write_custom_values and parameter_values is not None: - raise ValueError('parameter_values not allowed') - if write_custom_values and parameter_values is None: - raise ValueError('parameter_values is missing') - resp = _uds_request(address, SERVICE_TYPE.ACCESS_TIMING_PARAMETER, subfunction=timing_parameter_type, data=parameter_values) - if read_values: - # TODO: parse response into values? - parameter_values = resp - return parameter_values - -def secured_data_transmission(address, data): - # TODO: split data into multiple input parameters? - resp = _uds_request(address, SERVICE_TYPE.SECURED_DATA_TRANSMISSION, subfunction=None, data=data) - # TODO: parse response into multiple output values? - return resp - class DTC_SETTING_TYPE(IntEnum): ON = 1 OFF = 2 -def control_dtc_setting(address, dtc_setting_type): - _uds_request(address, SERVICE_TYPE.CONTROL_DTC_SETTING, subfunction=dtc_setting_type) - class RESPONSE_EVENT_TYPE(IntEnum): STOP_RESPONSE_ON_EVENT = 0 ON_DTC_STATUS_CHANGE = 1 @@ -341,25 +81,6 @@ class RESPONSE_EVENT_TYPE(IntEnum): CLEAR_RESPONSE_ON_EVENT = 6 ON_COMPARISON_OF_VALUES = 7 -def response_on_event(address, response_event_type, store_event, window_time, event_type_record, service_response_record): - if store_event: - response_event_type |= 0x20 - # TODO: split record parameters into arrays - data = char(window_time) + event_type_record + service_response_record - resp = _uds_request(address, SERVICE_TYPE.RESPONSE_ON_EVENT, subfunction=response_event_type, data=data) - - if response_event_type == REPORT_ACTIVATED_EVENTS: - return { - "num_of_activated_events": resp[0], - "data": resp[1:], # TODO: parse the reset of response - } - - return { - "num_of_identified_events": resp[0], - "event_window_time": resp[1], - "data": resp[2:], # TODO: parse the reset of response - } - class LINK_CONTROL_TYPE(IntEnum): VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE = 1 VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE = 2 @@ -376,17 +97,6 @@ class BAUD_RATE_TYPE(IntEnum): CAN500000 = 18 CAN1000000 = 19 -def link_control(address, link_control_type, baud_rate_type=None): - if LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: - # baud_rate_type = BAUD_RATE_TYPE - data = chr(baud_rate_type) - elif LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE: - # baud_rate_type = custom value (3 bytes big-endian) - data = struct.pack('!I', baud_rate_type)[1:] - else: - data = None - _uds_request(address, SERVICE_TYPE.LINK_CONTROL, subfunction=link_control_type, data=data) - class DATA_IDENTIFIER_TYPE(IntEnum): BOOT_SOFTWARE_IDENTIFICATION = 0XF180 APPLICATION_SOFTWARE_IDENTIFICATION = 0XF181 @@ -420,114 +130,21 @@ class DATA_IDENTIFIER_TYPE(IntEnum): ODX_FILE = 0XF19E ENTITY = 0XF19F -def read_data_by_identifier(address, data_identifier_type): - # TODO: support list of identifiers - data = struct.pack('!H', data_identifier_type) - resp = _uds_request(address, SERVICE_TYPE.READ_DATA_BY_IDENTIFIER, subfunction=None, data=data) - resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None - if resp_id != data_identifier_type: - raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) - return resp[2:] - -def read_memory_by_address(address, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=1): - if memory_address_bytes < 1 or memory_address_bytes > 4: - raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) - if memory_size_bytes < 1 or memory_size_bytes > 4: - raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = chr(memory_size_bytes<<4 | memory_address_bytes) - - if memory_address >= 1<<(memory_address_bytes*8): - raise ValueError('invalid memory_address: {}'.format(memory_address)) - data += struct.pack('!I', memory_address)[4-memory_address_bytes:] - if memory_size >= 1<<(memory_size_bytes*8): - raise ValueError('invalid memory_size: {}'.format(memory_size)) - data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - - resp = _uds_request(address, SERVICE_TYPE.READ_MEMORY_BY_ADDRESS, subfunction=None, data=data) - return resp - -def read_scaling_data_by_identifier(address, data_identifier_type): - data = struct.pack('!H', data_identifier_type) - resp = _uds_request(address, SERVICE_TYPE.READ_SCALING_DATA_BY_IDENTIFIER, subfunction=None, data=data) - resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None - if resp_id != data_identifier_type: - raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) - return resp[2:] # TODO: parse the response - class TRANSMISSION_MODE_TYPE(IntEnum): SEND_AT_SLOW_RATE = 1 SEND_AT_MEDIUM_RATE = 2 SEND_AT_FAST_RATE = 3 STOP_SENDING = 4 -def read_data_by_periodic_identifier(address, transmission_mode_type, periodic_data_identifier): - # TODO: support list of identifiers - data = chr(transmission_mode_type) + chr(periodic_data_identifier) - _uds_request(address, SERVICE_TYPE.READ_DATA_BY_PERIODIC_IDENTIFIER, subfunction=None, data=data) - class DYNAMIC_DEFINITION_TYPE(IntEnum): DEFINE_BY_IDENTIFIER = 1 DEFINE_BY_MEMORY_ADDRESS = 2 CLEAR_DYNAMICALLY_DEFINED_DATA_IDENTIFIER = 3 -def dynamically_define_data_identifier(address, dynamic_definition_type, dynamic_data_identifier, source_definitions, memory_address_bytes=4, memory_size_bytes=1): - if memory_address_bytes < 1 or memory_address_bytes > 4: - raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) - if memory_size_bytes < 1 or memory_size_bytes > 4: - raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = chr(memory_size_bytes<<4 | memory_address_bytes) - - data = struct.pack('!H', dynamic_data_identifier) - if dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_IDENTIFIER: - for s in source_definitions: - data += struct.pack('!H', s["data_identifier"]) + chr(s["position"]) + chr(s["memory_size"]) - elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_MEMORY_ADDRESS: - data += chr(memory_size_bytes<<4 | memory_address_bytes) - for s in source_definitions: - if s["memory_address"] >= 1<<(memory_address_bytes*8): - raise ValueError('invalid memory_address: {}'.format(s["memory_address"])) - data += struct.pack('!I', memory_address)[4-memory_address_bytes:] - if s["memory_size"] >= 1<<(memory_size_bytes*8): - raise ValueError('invalid memory_size: {}'.format(s["memory_size"])) - data += struct.pack('!I', s["memory_size"])[4-memory_size_bytes:] - elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.CLEAR_DYNAMICALLY_DEFINED_DATA_IDENTIFIER: - pass - else: - raise ValueError('invalid dynamic identifier type: {}'.format(hex(dynamic_definition_type))) - _uds_request(address, SERVICE_TYPE.DYNAMICALLY_DEFINE_DATA_IDENTIFIER, subfunction=dynamic_definition_type, data=data) - -def write_data_by_identifier(address, data_identifier_type, data_record): - data = struct.pack('!H', data_identifier_type) + data_record - resp = _uds_request(address, SERVICE_TYPE.WRITE_DATA_BY_IDENTIFIER, subfunction=None, data=data) - resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None - if resp_id != data_identifier_type: - raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) - -def write_memory_by_address(address, memory_address, memory_size, data_record, memory_address_bytes=4, memory_size_bytes=1): - if memory_address_bytes < 1 or memory_address_bytes > 4: - raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) - if memory_size_bytes < 1 or memory_size_bytes > 4: - raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = chr(memory_size_bytes<<4 | memory_address_bytes) - - if memory_address >= 1<<(memory_address_bytes*8): - raise ValueError('invalid memory_address: {}'.format(memory_address)) - data += struct.pack('!I', memory_address)[4-memory_address_bytes:] - if memory_size >= 1<<(memory_size_bytes*8): - raise ValueError('invalid memory_size: {}'.format(memory_size)) - data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - - data += data_record - _uds_request(address, SERVICE_TYPE.WRITE_MEMORY_BY_ADDRESS, subfunction=0x00, data=data) - class DTC_GROUP_TYPE(IntEnum): EMISSIONS = 0x000000 ALL = 0xFFFFFF -def clear_diagnostic_information(address, dtc_group_type): - data = struct.pack('!I', dtc_group_type)[1:] # 3 bytes - _uds_request(address, SERVICE_TYPE.CLEAR_DIAGNOSTIC_INFORMATION, subfunction=None, data=data) - class DTC_REPORT_TYPE(IntEnum): NUMBER_OF_DTC_BY_STATUS_MASK = 0x01 DTC_BY_STATUS_MASK = 0x02 @@ -568,56 +185,12 @@ class DTC_SEVERITY_MASK_TYPE(IntEnum): CHECK_IMMEDIATELY = 0x80 ALL = 0xE0 -def read_dtc_information(address, dtc_report_type, dtc_status_mask_type=DTC_STATUS_MASK_TYPE.ALL, dtc_severity_mask_type=DTC_SEVERITY_MASK_TYPE.ALL, dtc_mask_record=0xFFFFFF, dtc_snapshot_record_num=0xFF, dtc_extended_record_num=0xFF): - data = '' - # dtc_status_mask_type - if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_STATUS_MASK or \ - dtc_report_type == DTC_REPORT_TYPE.DTC_BY_STATUS_MASK or \ - dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_BY_STATUS_MASK or \ - dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK or \ - dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK or \ - dtc_report_type == DTC_REPORT_TYPE.EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK: - data += chr(dtc_status_mask_type) - # dtc_mask_record - if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or \ - dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or \ - dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ - dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ - dtc_report_type == DTC_REPORT_TYPE.SEVERITY_INFORMATION_OF_DTC: - data += struct.pack('!I', dtc_mask_record)[1:] # 3 bytes - # dtc_snapshot_record_num - if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or \ - dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or \ - dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_RECORD_NUMBER: - data += ord(dtc_snapshot_record_num) - # dtc_extended_record_num - if dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ - dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER: - data += chr(dtc_extended_record_num) - # dtc_severity_mask_type - if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD or \ - dtc_report_type == DTC_REPORT_TYPE.DTC_BY_SEVERITY_MASK_RECORD: - data += chr(dtc_severity_mask_type) + chr(dtc_status_mask_type) - - resp = _uds_request(address, SERVICE_TYPE.READ_DTC_INFORMATION, subfunction=dtc_report_type, data=data) - - # TODO: parse response - return resp - class CONTROL_OPTION_TYPE(IntEnum): RETURN_CONTROL_TO_ECU = 0 RESET_TO_DEFAULT = 1 FREEZE_CURRENT_STATE = 2 SHORT_TERM_ADJUSTMENT = 3 -def input_output_control_by_identifier(address, data_identifier_type, control_option_record, control_enable_mask_record=''): - data = struct.pack('!H', data_identifier_type) + control_option_record + control_enable_mask_record - resp = _uds_request(address, SERVICE_TYPE.INPUT_OUTPUT_CONTROL_BY_IDENTIFIER, subfunction=None, data=data) - resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None - if resp_id != data_identifier_type: - raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) - return resp[2:] - class ROUTINE_CONTROL_TYPE(IntEnum): START = 1 STOP = 2 @@ -628,103 +201,516 @@ class ROUTINE_IDENTIFIER_TYPE(IntEnum): CHECK_PROGRAMMING_DEPENDENCIES = 0xFF01 ERASE_MIRROR_MEMORY_DTCS = 0xFF02 -def routine_control(address, routine_control_type, routine_identifier_type, routine_option_record=''): - data = struct.pack('!H', routine_identifier_type) + routine_option_record - resp = _uds_request(address, SERVICE_TYPE.ROUTINE_CONTROL, subfunction=routine_control_type, data=data) - resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None - if resp_id != routine_identifier_type: - raise ValueError('invalid response routine identifier: {}'.format(hex(resp_id))) - return resp[2:] - -def request_download(address, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): - data = chr(data_format) - - if memory_address_bytes < 1 or memory_address_bytes > 4: - raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) - if memory_size_bytes < 1 or memory_size_bytes > 4: - raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data += chr(memory_size_bytes<<4 | memory_address_bytes) - - if memory_address >= 1<<(memory_address_bytes*8): - raise ValueError('invalid memory_address: {}'.format(memory_address)) - data += struct.pack('!I', memory_address)[4-memory_address_bytes:] - if memory_size >= 1<<(memory_size_bytes*8): - raise ValueError('invalid memory_size: {}'.format(memory_size)) - data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - - resp = _uds_request(address, SERVICE_TYPE.REQUEST_DOWNLOAD, subfunction=None, data=data) - max_num_bytes_len = resp[0] >> 4 if len(resp) > 0 else None - if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: - max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] - else: - raise ValueError('invalid max_num_bytes_len: {}'.format(max_num_bytes_len)) - - return max_num_bytes # max number of bytes per transfer data request - -def request_upload(address, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): - data = chr(data_format) - - if memory_address_bytes < 1 or memory_address_bytes > 4: - raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) - if memory_size_bytes < 1 or memory_size_bytes > 4: - raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data += chr(memory_size_bytes<<4 | memory_address_bytes) - - if memory_address >= 1<<(memory_address_bytes*8): - raise ValueError('invalid memory_address: {}'.format(memory_address)) - data += struct.pack('!I', memory_address)[4-memory_address_bytes:] - if memory_size >= 1<<(memory_size_bytes*8): - raise ValueError('invalid memory_size: {}'.format(memory_size)) - data += struct.pack('!I', memory_size)[4-memory_size_bytes:] - - resp = _uds_request(address, SERVICE_TYPE.REQUEST_UPLOAD, subfunction=None, data=data) - max_num_bytes_len = resp[0] >> 4 if len(resp) > 0 else None - if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: - max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] - else: - raise ValueError('invalid max_num_bytes_len: {}'.format(max_num_bytes_len)) - - return max_num_bytes # max number of bytes per transfer data request - -def transfer_data(address, block_sequence_count, data=''): - data = chr(block_sequence_count)+data - resp = _uds_request(address, SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) - resp_id = resp[0] if len(resp) > 0 else None - if resp_id != block_sequence_count: - raise ValueError('invalid block_sequence_count: {}'.format(resp_id)) - return resp[1:] - -def request_transfer_exit(address): - _uds_request(address, SERVICE_TYPE.REQUEST_TRANSFER_EXIT, subfunction=None) - -if __name__ == "__main__": - from python import Panda - panda = Panda() - bus = 0 - tx_addr = 0x18da30f1 # EPS - tx_queue = Queue() - rx_queue = Queue() - can_reader_t = threading.Thread(target=_isotp_thread, args=(panda, bus, tx_addr, tx_queue, rx_queue)) - can_reader_t.daemon = True - can_reader_t.start() - - # examples - print("tester present ...") - tester_present(tx_addr) - print("extended diagnostic session ...") - diagnostic_session_control(tx_addr, SESSION_TYPE.EXTENDED_DIAGNOSTIC) - print("read data by id: application software id ...") - app_id = read_data_by_identifier(tx_addr, DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) - print(app_id) - # for i in range(0xF100, 0xF2FF): - # try: - # dat = read_data_by_identifier(tx_addr, i) - # desc = "" - # try: - # desc = " [" + DATA_IDENTIFIER_TYPE(i).name + "]" - # except ValueError: - # pass - # print("{}:{} {} {}".format(hex(i), desc, hexlify(dat), "")) #, dat.decode(errors="ignore"))) - # except NegativeResponseError as e: - # if e.error_code != 0x31: - # print("{}: {}".format(hex(i), e)) +class MessageTimeoutError(Exception): + pass + +class NegativeResponseError(Exception): + def __init__(self, message, service_id, error_code): + super(Exception, self).__init__(message) + self.service_id = service_id + self.error_code = error_code + +class InvalidServiceIdError(Exception): + pass + +class InvalidSubFunctioneError(Exception): + pass + +_negative_response_codes = { + 0x00: 'positive response', + 0x10: 'general reject', + 0x11: 'service not supported', + 0x12: 'sub-function not supported', + 0x13: 'incorrect message length or invalid format', + 0x14: 'response too long', + 0x21: 'busy repeat request', + 0x22: 'conditions not correct', + 0x24: 'request sequence error', + 0x25: 'no response from subnet component', + 0x26: 'failure prevents execution of requested action', + 0x31: 'request out of range', + 0x33: 'security access denied', + 0x35: 'invalid key', + 0x36: 'exceed numebr of attempts', + 0x37: 'required time delay not expired', + 0x70: 'upload download not accepted', + 0x71: 'transfer data suspended', + 0x72: 'general programming failure', + 0x73: 'wrong block sequence counter', + 0x78: 'request correctly received - response pending', + 0x7e: 'sub-function not supported in active session', + 0x7f: 'service not supported in active session', + 0x81: 'rpm too high', + 0x82: 'rpm too low', + 0x83: 'engine is running', + 0x84: 'engine is not running', + 0x85: 'engine run time too low', + 0x86: 'temperature too high', + 0x87: 'temperature too low', + 0x88: 'vehicle speed too high', + 0x89: 'vehicle speed too low', + 0x8a: 'throttle/pedal too high', + 0x8b: 'throttle/pedal too low', + 0x8c: 'transmission not in neutral', + 0x8d: 'transmission not in gear', + 0x8f: 'brake switch(es) not closed', + 0x90: 'shifter lever not in park', + 0x91: 'torque converter clutch locked', + 0x92: 'voltage too high', + 0x93: 'voltage too low', +} + +class UdsClient(): + def __init__(self, panda, tx_addr, rx_addr=None, bus=0, debug=False): + self.panda = panda + self.bus = bus + self.tx_addr = tx_addr + if rx_addr == None: + if tx_addr < 0xFFF8: + self.rx_addr = tx_addr+8 + elif tx_addr > 0x10000000 and tx_addr < 0xFFFFFFFF: + self.rx_addr = (tx_addr & 0xFFFF0000) + (tx_addr<<8 & 0xFF00) + (tx_addr>>8 & 0xFF) + else: + raise ValueError("invalid tx_addr: {}".format(tx_addr)) + + self.tx_queue = Queue() + self.rx_queue = Queue() + self.debug = debug + + self.can_reader_t = threading.Thread(target=self._isotp_thread, args=(self.panda, self.bus, self.tx_addr, self.tx_queue, self.rx_queue, self.debug)) + self.can_reader_t.daemon = True + self.can_reader_t.start() + + def _isotp_thread(self, panda, bus, tx_addr, tx_queue, rx_queue, debug): + try: + rx_frame = {"size": 0, "data": "", "idx": 0, "done": True} + tx_frame = {"size": 0, "data": "", "idx": 0, "done": True} + + # allow all output + panda.set_safety_mode(0x1337) + # clear tx buffer + panda.can_clear(bus) + # clear rx buffer + panda.can_clear(0xFFFF) + time.sleep(1) + while True: + messages = panda.can_recv() + for rx_addr, rx_ts, rx_data, rx_bus in messages: + if rx_bus != bus or rx_addr != rx_addr or len(rx_data) == 0: + continue + + if (debug): print("R: {} {}".format(hex(rx_addr), hexlify(rx_data))) + if rx_data[0] >> 4 == 0x0: + # single rx_frame + rx_frame["size"] = rx_data[0] & 0xFF + rx_frame["data"] = rx_data[1:1+rx_frame["size"]] + rx_frame["idx"] = 0 + rx_frame["done"] = True + rx_queue.put(rx_frame["data"]) + elif rx_data[0] >> 4 == 0x1: + # first rx_frame + rx_frame["size"] = ((rx_data[0] & 0x0F) << 8) + rx_data[1] + rx_frame["data"] = rx_data[2:] + rx_frame["idx"] = 0 + rx_frame["done"] = False + # send flow control message (send all bytes) + msg = "\x30\x00\x00".ljust(8, "\x00") + if (debug): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) + panda.can_send(tx_addr, msg, bus) + elif rx_data[0] >> 4 == 0x2: + # consecutive rx frame + assert rx_frame["done"] == False, "rx: no active frame" + # validate frame index + rx_frame["idx"] += 1 + assert rx_frame["idx"] & 0xF == rx_data[0] & 0xF, "rx: invalid consecutive frame index" + rx_size = rx_frame["size"] - len(rx_frame["data"]) + rx_frame["data"] += rx_data[1:1+min(rx_size, 7)] + if rx_frame["size"] == len(rx_frame["data"]): + rx_frame["done"] = True + rx_queue.put(rx_frame["data"]) + elif rx_data[0] >> 4 == 0x3: + # flow control + assert tx_frame["done"] == False, "tx: no active frame" + # TODO: support wait/overflow + assert rx_data[0] == 0x30, "tx: flow-control requires: continue" + delay_ts = rx_data[2] & 0x7F + # scale is 1 milliseconds if first bit == 0, 100 micro seconds if first bit == 1 + delay_div = 1000. if rx_data[2] & 0x80 == 0 else 10000. + # first frame = 6 bytes, each consecutive frame = 7 bytes + start = 6 + tx_frame["idx"] * 7 + count = rx_data[1] + end = start + count * 7 if count > 0 else tx_frame["size"] + for i in range(start, end, 7): + tx_frame["idx"] += 1 + # consecutive tx frames + msg = (chr(0x20 | (tx_frame["idx"] & 0xF)) + tx_frame["data"][i:i+7]).ljust(8, "\x00") + if (debug): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) + panda.can_send(tx_addr, msg, bus) + if delay_ts > 0: + time.sleep(delay_ts / delay_div) + tx_frame["done"] = True + + if not tx_queue.empty(): + req = tx_queue.get(block=False) + # reset rx and tx frames + rx_frame = {"size": 0, "data": "", "idx": 0, "done": True} + tx_frame = {"size": len(req), "data": req, "idx": 0, "done": False} + if tx_frame["size"] < 8: + # single frame + tx_frame["done"] = True + msg = (chr(tx_frame["size"]) + tx_frame["data"]).ljust(8, "\x00") + if (debug): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) + panda.can_send(tx_addr, msg, bus) + else: + # first rx_frame + tx_frame["done"] = False + msg = (struct.pack("!H", 0x1000 | tx_frame["size"]) + tx_frame["data"][:6]).ljust(8, "\x00") + if (debug): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) + panda.can_send(tx_addr, msg, bus) + else: + time.sleep(0.01) + finally: + panda.close() + rx_queue.put(None) + + # generic uds request + def _uds_request(self, service_type, subfunction=None, data=None): + req = chr(service_type) + if subfunction is not None: + req += chr(subfunction) + if data is not None: + req += data + self.tx_queue.put(req) + + while True: + try: + resp = self.rx_queue.get(block=True, timeout=10) + except Empty: + raise MessageTimeoutError("timeout waiting for response") + if resp is None: + raise MessageTimeoutError("timeout waiting for response") + + resp_sid = resp[0] if len(resp) > 0 else None + + # negative response + if resp_sid == 0x7F: + service_id = resp[1] if len(resp) > 1 else -1 + try: + service_desc = SERVICE_TYPE(service_id).name + except Exception: + service_desc = 'NON_STANDARD_SERVICE' + error_code = resp[2] if len(resp) > 2 else -1 + try: + error_desc = _negative_response_codes[error_code] + except Exception: + error_desc = 'unknown error' + # wait for another message if response pending + if error_code == 0x78: + time.sleep(0.1) + continue + raise NegativeResponseError('{} - {}'.format(service_desc, error_desc), service_id, error_code) + break + + # positive response + if service_type+0x40 != resp_sid: + resp_sid_hex = hex(resp_sid) if resp_sid is not None else None + raise InvalidServiceIdError('invalid response service id: {}'.format(resp_sid_hex)) + + if subfunction is not None: + resp_sfn = resp[1] if len(resp) > 1 else None + if subfunction != resp_sfn: + resp_sfn_hex = hex(resp_sfn) if resp_sfn is not None else None + raise InvalidSubFunctioneError('invalid response subfunction: {}'.format(hex(resp_sfn))) + + # return data (exclude service id and sub-function id) + return resp[(1 if subfunction is None else 2):] + + # services + def diagnostic_session_control(self, session_type): + self._uds_request(SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, subfunction=session_type) + + def ecu_reset(self, reset_type): + resp = self._uds_request(SERVICE_TYPE.ECU_RESET, subfunction=reset_type) + power_down_time = None + if reset_type == RESET_TYPE.ENABLE_RAPID_POWER_SHUTDOWN: + power_down_time = resp[0] + return power_down_time + + def security_access(self, access_type, security_key=None): + request_seed = access_type % 2 != 0 + if request_seed and security_key is not None: + raise ValueError('security_key not allowed') + if not request_seed and security_key is None: + raise ValueError('security_key is missing') + resp = self._uds_request(SERVICE_TYPE.SECURITY_ACCESS, subfunction=access_type, data=security_key) + if request_seed: + security_seed = resp + return security_seed + + def communication_control(self, control_type, message_type): + data = chr(message_type) + self._uds_request(SERVICE_TYPE.COMMUNICATION_CONTROL, subfunction=control_type, data=data) + + def tester_present(self, ): + self._uds_request(SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) + + def access_timing_parameter(self, timing_parameter_type, parameter_values): + write_custom_values = timing_parameter_type == ACCESS_TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES + read_values = ( + timing_parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or + timing_parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_EXTENDED_SET + ) + if not write_custom_values and parameter_values is not None: + raise ValueError('parameter_values not allowed') + if write_custom_values and parameter_values is None: + raise ValueError('parameter_values is missing') + resp = self._uds_request(SERVICE_TYPE.ACCESS_TIMING_PARAMETER, subfunction=timing_parameter_type, data=parameter_values) + if read_values: + # TODO: parse response into values? + parameter_values = resp + return parameter_values + + def secured_data_transmission(self, data): + # TODO: split data into multiple input parameters? + resp = self._uds_request(SERVICE_TYPE.SECURED_DATA_TRANSMISSION, subfunction=None, data=data) + # TODO: parse response into multiple output values? + return resp + + def control_dtc_setting(self, dtc_setting_type): + self._uds_request(SERVICE_TYPE.CONTROL_DTC_SETTING, subfunction=dtc_setting_type) + + def response_on_event(self, response_event_type, store_event, window_time, event_type_record, service_response_record): + if store_event: + response_event_type |= 0x20 + # TODO: split record parameters into arrays + data = char(window_time) + event_type_record + service_response_record + resp = self._uds_request(SERVICE_TYPE.RESPONSE_ON_EVENT, subfunction=response_event_type, data=data) + + if response_event_type == REPORT_ACTIVATED_EVENTS: + return { + "num_of_activated_events": resp[0], + "data": resp[1:], # TODO: parse the reset of response + } + + return { + "num_of_identified_events": resp[0], + "event_window_time": resp[1], + "data": resp[2:], # TODO: parse the reset of response + } + + def link_control(self, link_control_type, baud_rate_type=None): + if LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: + # baud_rate_type = BAUD_RATE_TYPE + data = chr(baud_rate_type) + elif LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE: + # baud_rate_type = custom value (3 bytes big-endian) + data = struct.pack('!I', baud_rate_type)[1:] + else: + data = None + self._uds_request(SERVICE_TYPE.LINK_CONTROL, subfunction=link_control_type, data=data) + + def read_data_by_identifier(self, data_identifier_type): + # TODO: support list of identifiers + data = struct.pack('!H', data_identifier_type) + resp = self._uds_request(SERVICE_TYPE.READ_DATA_BY_IDENTIFIER, subfunction=None, data=data) + resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None + if resp_id != data_identifier_type: + raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) + return resp[2:] + + def read_memory_by_address(self, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=1): + if memory_address_bytes < 1 or memory_address_bytes > 4: + raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) + if memory_size_bytes < 1 or memory_size_bytes > 4: + raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) + data = chr(memory_size_bytes<<4 | memory_address_bytes) + + if memory_address >= 1<<(memory_address_bytes*8): + raise ValueError('invalid memory_address: {}'.format(memory_address)) + data += struct.pack('!I', memory_address)[4-memory_address_bytes:] + if memory_size >= 1<<(memory_size_bytes*8): + raise ValueError('invalid memory_size: {}'.format(memory_size)) + data += struct.pack('!I', memory_size)[4-memory_size_bytes:] + + resp = self._uds_request(SERVICE_TYPE.READ_MEMORY_BY_ADDRESS, subfunction=None, data=data) + return resp + + def read_scaling_data_by_identifier(self, data_identifier_type): + data = struct.pack('!H', data_identifier_type) + resp = self._uds_request(SERVICE_TYPE.READ_SCALING_DATA_BY_IDENTIFIER, subfunction=None, data=data) + resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None + if resp_id != data_identifier_type: + raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) + return resp[2:] # TODO: parse the response + + def read_data_by_periodic_identifier(self, transmission_mode_type, periodic_data_identifier): + # TODO: support list of identifiers + data = chr(transmission_mode_type) + chr(periodic_data_identifier) + self._uds_request(SERVICE_TYPE.READ_DATA_BY_PERIODIC_IDENTIFIER, subfunction=None, data=data) + + def dynamically_define_data_identifier(self, dynamic_definition_type, dynamic_data_identifier, source_definitions, memory_address_bytes=4, memory_size_bytes=1): + if memory_address_bytes < 1 or memory_address_bytes > 4: + raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) + if memory_size_bytes < 1 or memory_size_bytes > 4: + raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) + data = chr(memory_size_bytes<<4 | memory_address_bytes) + + data = struct.pack('!H', dynamic_data_identifier) + if dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_IDENTIFIER: + for s in source_definitions: + data += struct.pack('!H', s["data_identifier"]) + chr(s["position"]) + chr(s["memory_size"]) + elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_MEMORY_ADDRESS: + data += chr(memory_size_bytes<<4 | memory_address_bytes) + for s in source_definitions: + if s["memory_address"] >= 1<<(memory_address_bytes*8): + raise ValueError('invalid memory_address: {}'.format(s["memory_address"])) + data += struct.pack('!I', memory_address)[4-memory_address_bytes:] + if s["memory_size"] >= 1<<(memory_size_bytes*8): + raise ValueError('invalid memory_size: {}'.format(s["memory_size"])) + data += struct.pack('!I', s["memory_size"])[4-memory_size_bytes:] + elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.CLEAR_DYNAMICALLY_DEFINED_DATA_IDENTIFIER: + pass + else: + raise ValueError('invalid dynamic identifier type: {}'.format(hex(dynamic_definition_type))) + self._uds_request(SERVICE_TYPE.DYNAMICALLY_DEFINE_DATA_IDENTIFIER, subfunction=dynamic_definition_type, data=data) + + def write_data_by_identifier(self, data_identifier_type, data_record): + data = struct.pack('!H', data_identifier_type) + data_record + resp = self._uds_request(SERVICE_TYPE.WRITE_DATA_BY_IDENTIFIER, subfunction=None, data=data) + resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None + if resp_id != data_identifier_type: + raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) + + def write_memory_by_address(self, memory_address, memory_size, data_record, memory_address_bytes=4, memory_size_bytes=1): + if memory_address_bytes < 1 or memory_address_bytes > 4: + raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) + if memory_size_bytes < 1 or memory_size_bytes > 4: + raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) + data = chr(memory_size_bytes<<4 | memory_address_bytes) + + if memory_address >= 1<<(memory_address_bytes*8): + raise ValueError('invalid memory_address: {}'.format(memory_address)) + data += struct.pack('!I', memory_address)[4-memory_address_bytes:] + if memory_size >= 1<<(memory_size_bytes*8): + raise ValueError('invalid memory_size: {}'.format(memory_size)) + data += struct.pack('!I', memory_size)[4-memory_size_bytes:] + + data += data_record + self._uds_request(SERVICE_TYPE.WRITE_MEMORY_BY_ADDRESS, subfunction=0x00, data=data) + + def clear_diagnostic_information(self, dtc_group_type): + data = struct.pack('!I', dtc_group_type)[1:] # 3 bytes + self._uds_request(SERVICE_TYPE.CLEAR_DIAGNOSTIC_INFORMATION, subfunction=None, data=data) + + def read_dtc_information(self, dtc_report_type, dtc_status_mask_type=DTC_STATUS_MASK_TYPE.ALL, dtc_severity_mask_type=DTC_SEVERITY_MASK_TYPE.ALL, dtc_mask_record=0xFFFFFF, dtc_snapshot_record_num=0xFF, dtc_extended_record_num=0xFF): + data = '' + # dtc_status_mask_type + if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_STATUS_MASK or \ + dtc_report_type == DTC_REPORT_TYPE.DTC_BY_STATUS_MASK or \ + dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_BY_STATUS_MASK or \ + dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK or \ + dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK or \ + dtc_report_type == DTC_REPORT_TYPE.EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK: + data += chr(dtc_status_mask_type) + # dtc_mask_record + if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or \ + dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or \ + dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ + dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ + dtc_report_type == DTC_REPORT_TYPE.SEVERITY_INFORMATION_OF_DTC: + data += struct.pack('!I', dtc_mask_record)[1:] # 3 bytes + # dtc_snapshot_record_num + if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or \ + dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or \ + dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_RECORD_NUMBER: + data += ord(dtc_snapshot_record_num) + # dtc_extended_record_num + if dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ + dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER: + data += chr(dtc_extended_record_num) + # dtc_severity_mask_type + if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD or \ + dtc_report_type == DTC_REPORT_TYPE.DTC_BY_SEVERITY_MASK_RECORD: + data += chr(dtc_severity_mask_type) + chr(dtc_status_mask_type) + + resp = self._uds_request(SERVICE_TYPE.READ_DTC_INFORMATION, subfunction=dtc_report_type, data=data) + + # TODO: parse response + return resp + + def input_output_control_by_identifier(self, data_identifier_type, control_option_record, control_enable_mask_record=''): + data = struct.pack('!H', data_identifier_type) + control_option_record + control_enable_mask_record + resp = self._uds_request(SERVICE_TYPE.INPUT_OUTPUT_CONTROL_BY_IDENTIFIER, subfunction=None, data=data) + resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None + if resp_id != data_identifier_type: + raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) + return resp[2:] + + def routine_control(self, routine_control_type, routine_identifier_type, routine_option_record=''): + data = struct.pack('!H', routine_identifier_type) + routine_option_record + resp = self._uds_request(SERVICE_TYPE.ROUTINE_CONTROL, subfunction=routine_control_type, data=data) + resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None + if resp_id != routine_identifier_type: + raise ValueError('invalid response routine identifier: {}'.format(hex(resp_id))) + return resp[2:] + + def request_download(self, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): + data = chr(data_format) + + if memory_address_bytes < 1 or memory_address_bytes > 4: + raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) + if memory_size_bytes < 1 or memory_size_bytes > 4: + raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) + data += chr(memory_size_bytes<<4 | memory_address_bytes) + + if memory_address >= 1<<(memory_address_bytes*8): + raise ValueError('invalid memory_address: {}'.format(memory_address)) + data += struct.pack('!I', memory_address)[4-memory_address_bytes:] + if memory_size >= 1<<(memory_size_bytes*8): + raise ValueError('invalid memory_size: {}'.format(memory_size)) + data += struct.pack('!I', memory_size)[4-memory_size_bytes:] + + resp = self._uds_request(SERVICE_TYPE.REQUEST_DOWNLOAD, subfunction=None, data=data) + max_num_bytes_len = resp[0] >> 4 if len(resp) > 0 else None + if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: + max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] + else: + raise ValueError('invalid max_num_bytes_len: {}'.format(max_num_bytes_len)) + + return max_num_bytes # max number of bytes per transfer data request + + def request_upload(self, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): + data = chr(data_format) + + if memory_address_bytes < 1 or memory_address_bytes > 4: + raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) + if memory_size_bytes < 1 or memory_size_bytes > 4: + raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) + data += chr(memory_size_bytes<<4 | memory_address_bytes) + + if memory_address >= 1<<(memory_address_bytes*8): + raise ValueError('invalid memory_address: {}'.format(memory_address)) + data += struct.pack('!I', memory_address)[4-memory_address_bytes:] + if memory_size >= 1<<(memory_size_bytes*8): + raise ValueError('invalid memory_size: {}'.format(memory_size)) + data += struct.pack('!I', memory_size)[4-memory_size_bytes:] + + resp = self._uds_request(SERVICE_TYPE.REQUEST_UPLOAD, subfunction=None, data=data) + max_num_bytes_len = resp[0] >> 4 if len(resp) > 0 else None + if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: + max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] + else: + raise ValueError('invalid max_num_bytes_len: {}'.format(max_num_bytes_len)) + + return max_num_bytes # max number of bytes per transfer data request + + def transfer_data(self, block_sequence_count, data=''): + data = chr(block_sequence_count)+data + resp = self._uds_request(SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) + resp_id = resp[0] if len(resp) > 0 else None + if resp_id != block_sequence_count: + raise ValueError('invalid block_sequence_count: {}'.format(resp_id)) + return resp[1:] + + def request_transfer_exit(self): + self._uds_request(SERVICE_TYPE.REQUEST_TRANSFER_EXIT, subfunction=None) From b1a3195770d5a39e3fa772fde6aef76af2cb54ea Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Mon, 17 Dec 2018 00:45:10 -0800 Subject: [PATCH 17/31] fix rx message filtering bug --- python/uds.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/python/uds.py b/python/uds.py index 4c5908108b496a..f4604b39a5c806 100644 --- a/python/uds.py +++ b/python/uds.py @@ -277,26 +277,26 @@ def __init__(self, panda, tx_addr, rx_addr=None, bus=0, debug=False): self.rx_queue = Queue() self.debug = debug - self.can_reader_t = threading.Thread(target=self._isotp_thread, args=(self.panda, self.bus, self.tx_addr, self.tx_queue, self.rx_queue, self.debug)) + self.can_reader_t = threading.Thread(target=self._isotp_thread, args=(self.debug,)) self.can_reader_t.daemon = True self.can_reader_t.start() - def _isotp_thread(self, panda, bus, tx_addr, tx_queue, rx_queue, debug): + def _isotp_thread(self, debug): try: rx_frame = {"size": 0, "data": "", "idx": 0, "done": True} tx_frame = {"size": 0, "data": "", "idx": 0, "done": True} # allow all output - panda.set_safety_mode(0x1337) + self.panda.set_safety_mode(0x1337) # clear tx buffer - panda.can_clear(bus) + self.panda.can_clear(self.bus) # clear rx buffer - panda.can_clear(0xFFFF) + self.panda.can_clear(0xFFFF) time.sleep(1) while True: - messages = panda.can_recv() + messages = self.panda.can_recv() for rx_addr, rx_ts, rx_data, rx_bus in messages: - if rx_bus != bus or rx_addr != rx_addr or len(rx_data) == 0: + if rx_bus != self.bus or rx_addr != self.rx_addr or len(rx_data) == 0: continue if (debug): print("R: {} {}".format(hex(rx_addr), hexlify(rx_data))) @@ -306,7 +306,7 @@ def _isotp_thread(self, panda, bus, tx_addr, tx_queue, rx_queue, debug): rx_frame["data"] = rx_data[1:1+rx_frame["size"]] rx_frame["idx"] = 0 rx_frame["done"] = True - rx_queue.put(rx_frame["data"]) + self.rx_queue.put(rx_frame["data"]) elif rx_data[0] >> 4 == 0x1: # first rx_frame rx_frame["size"] = ((rx_data[0] & 0x0F) << 8) + rx_data[1] @@ -315,8 +315,8 @@ def _isotp_thread(self, panda, bus, tx_addr, tx_queue, rx_queue, debug): rx_frame["done"] = False # send flow control message (send all bytes) msg = "\x30\x00\x00".ljust(8, "\x00") - if (debug): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) - panda.can_send(tx_addr, msg, bus) + if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) + self.panda.can_send(self.tx_addr, msg, self.bus) elif rx_data[0] >> 4 == 0x2: # consecutive rx frame assert rx_frame["done"] == False, "rx: no active frame" @@ -327,7 +327,7 @@ def _isotp_thread(self, panda, bus, tx_addr, tx_queue, rx_queue, debug): rx_frame["data"] += rx_data[1:1+min(rx_size, 7)] if rx_frame["size"] == len(rx_frame["data"]): rx_frame["done"] = True - rx_queue.put(rx_frame["data"]) + self.rx_queue.put(rx_frame["data"]) elif rx_data[0] >> 4 == 0x3: # flow control assert tx_frame["done"] == False, "tx: no active frame" @@ -344,14 +344,14 @@ def _isotp_thread(self, panda, bus, tx_addr, tx_queue, rx_queue, debug): tx_frame["idx"] += 1 # consecutive tx frames msg = (chr(0x20 | (tx_frame["idx"] & 0xF)) + tx_frame["data"][i:i+7]).ljust(8, "\x00") - if (debug): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) - panda.can_send(tx_addr, msg, bus) + if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) + self.panda.can_send(self.tx_addr, msg, self.bus) if delay_ts > 0: time.sleep(delay_ts / delay_div) tx_frame["done"] = True - if not tx_queue.empty(): - req = tx_queue.get(block=False) + if not self.tx_queue.empty(): + req = self.tx_queue.get(block=False) # reset rx and tx frames rx_frame = {"size": 0, "data": "", "idx": 0, "done": True} tx_frame = {"size": len(req), "data": req, "idx": 0, "done": False} @@ -359,19 +359,19 @@ def _isotp_thread(self, panda, bus, tx_addr, tx_queue, rx_queue, debug): # single frame tx_frame["done"] = True msg = (chr(tx_frame["size"]) + tx_frame["data"]).ljust(8, "\x00") - if (debug): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) - panda.can_send(tx_addr, msg, bus) + if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) + self.panda.can_send(self.tx_addr, msg, self.bus) else: # first rx_frame tx_frame["done"] = False msg = (struct.pack("!H", 0x1000 | tx_frame["size"]) + tx_frame["data"][:6]).ljust(8, "\x00") - if (debug): print("S: {} {}".format(hex(tx_addr), hexlify(msg))) - panda.can_send(tx_addr, msg, bus) + if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) + self.panda.can_send(self.tx_addr, msg, self.bus) else: time.sleep(0.01) finally: - panda.close() - rx_queue.put(None) + self.panda.close() + self.rx_queue.put(None) # generic uds request def _uds_request(self, service_type, subfunction=None, data=None): From cdf2f626ba9d9ba5046b13f71dc0d03b5de0b184 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Wed, 19 Dec 2018 22:46:40 -0800 Subject: [PATCH 18/31] bug fixes --- python/uds.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/python/uds.py b/python/uds.py index f4604b39a5c806..0e24b399b2fa32 100644 --- a/python/uds.py +++ b/python/uds.py @@ -455,10 +455,10 @@ def tester_present(self, ): self._uds_request(SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) def access_timing_parameter(self, timing_parameter_type, parameter_values): - write_custom_values = timing_parameter_type == ACCESS_TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES + write_custom_values = timing_parameter_type == TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES read_values = ( - timing_parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or - timing_parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_EXTENDED_SET + timing_parameter_type == TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or + timing_parameter_type == TIMING_PARAMETER_TYPE.READ_EXTENDED_SET ) if not write_custom_values and parameter_values is not None: raise ValueError('parameter_values not allowed') @@ -486,7 +486,7 @@ def response_on_event(self, response_event_type, store_event, window_time, event data = char(window_time) + event_type_record + service_response_record resp = self._uds_request(SERVICE_TYPE.RESPONSE_ON_EVENT, subfunction=response_event_type, data=data) - if response_event_type == REPORT_ACTIVATED_EVENTS: + if response_event_type == RESPONSE_EVENT_TYPE.REPORT_ACTIVATED_EVENTS: return { "num_of_activated_events": resp[0], "data": resp[1:], # TODO: parse the reset of response @@ -499,10 +499,10 @@ def response_on_event(self, response_event_type, store_event, window_time, event } def link_control(self, link_control_type, baud_rate_type=None): - if LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: + if link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: # baud_rate_type = BAUD_RATE_TYPE data = chr(baud_rate_type) - elif LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE: + elif link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE: # baud_rate_type = custom value (3 bytes big-endian) data = struct.pack('!I', baud_rate_type)[1:] else: @@ -553,7 +553,6 @@ def dynamically_define_data_identifier(self, dynamic_definition_type, dynamic_da raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = chr(memory_size_bytes<<4 | memory_address_bytes) data = struct.pack('!H', dynamic_data_identifier) if dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_IDENTIFIER: From b1c371292c56a4f4faf6985745c2882e4224879c Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Thu, 20 Dec 2018 00:22:55 -0800 Subject: [PATCH 19/31] add timeout param --- python/uds.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/uds.py b/python/uds.py index 0e24b399b2fa32..647ba82da6b52c 100644 --- a/python/uds.py +++ b/python/uds.py @@ -261,7 +261,7 @@ class InvalidSubFunctioneError(Exception): } class UdsClient(): - def __init__(self, panda, tx_addr, rx_addr=None, bus=0, debug=False): + def __init__(self, panda, tx_addr, rx_addr=None, bus=0, timeout=10, debug=False): self.panda = panda self.bus = bus self.tx_addr = tx_addr @@ -275,6 +275,7 @@ def __init__(self, panda, tx_addr, rx_addr=None, bus=0, debug=False): self.tx_queue = Queue() self.rx_queue = Queue() + self.timeout = timeout self.debug = debug self.can_reader_t = threading.Thread(target=self._isotp_thread, args=(self.debug,)) @@ -384,7 +385,7 @@ def _uds_request(self, service_type, subfunction=None, data=None): while True: try: - resp = self.rx_queue.get(block=True, timeout=10) + resp = self.rx_queue.get(block=True, timeout=self.timeout) except Empty: raise MessageTimeoutError("timeout waiting for response") if resp is None: From 932745f62b4f1a8e92079d128786f3a106c262ac Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Thu, 20 Dec 2018 11:10:31 -0800 Subject: [PATCH 20/31] support tx flow control for chunked messages --- python/uds.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/uds.py b/python/uds.py index 647ba82da6b52c..9f0a8ae58008c1 100644 --- a/python/uds.py +++ b/python/uds.py @@ -293,7 +293,7 @@ def _isotp_thread(self, debug): self.panda.can_clear(self.bus) # clear rx buffer self.panda.can_clear(0xFFFF) - time.sleep(1) + while True: messages = self.panda.can_recv() for rx_addr, rx_ts, rx_data, rx_bus in messages: @@ -349,7 +349,8 @@ def _isotp_thread(self, debug): self.panda.can_send(self.tx_addr, msg, self.bus) if delay_ts > 0: time.sleep(delay_ts / delay_div) - tx_frame["done"] = True + if end >= tx_frame["size"]: + tx_frame["done"] = True if not self.tx_queue.empty(): req = self.tx_queue.get(block=False) From 4f288586d59e3e308852e4ec0f988a6529b53894 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Sat, 12 Oct 2019 18:52:17 -0700 Subject: [PATCH 21/31] updates for python3 --- python/uds.py | 52 ++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/python/uds.py b/python/uds.py index 9f0a8ae58008c1..3c964770bfe96c 100644 --- a/python/uds.py +++ b/python/uds.py @@ -1,9 +1,9 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import time import struct from enum import IntEnum -from Queue import Queue, Empty -import threading +from queue import Queue, Empty +from threading import Thread from binascii import hexlify class SERVICE_TYPE(IntEnum): @@ -267,8 +267,10 @@ def __init__(self, panda, tx_addr, rx_addr=None, bus=0, timeout=10, debug=False) self.tx_addr = tx_addr if rx_addr == None: if tx_addr < 0xFFF8: + # standard 11 bit response addr (add 8) self.rx_addr = tx_addr+8 elif tx_addr > 0x10000000 and tx_addr < 0xFFFFFFFF: + # standard 19 bit response addr (flip last two bytes) self.rx_addr = (tx_addr & 0xFFFF0000) + (tx_addr<<8 & 0xFF00) + (tx_addr>>8 & 0xFF) else: raise ValueError("invalid tx_addr: {}".format(tx_addr)) @@ -278,7 +280,7 @@ def __init__(self, panda, tx_addr, rx_addr=None, bus=0, timeout=10, debug=False) self.timeout = timeout self.debug = debug - self.can_reader_t = threading.Thread(target=self._isotp_thread, args=(self.debug,)) + self.can_reader_t = Thread(target=self._isotp_thread, args=(self.debug,)) self.can_reader_t.daemon = True self.can_reader_t.start() @@ -315,7 +317,7 @@ def _isotp_thread(self, debug): rx_frame["idx"] = 0 rx_frame["done"] = False # send flow control message (send all bytes) - msg = "\x30\x00\x00".ljust(8, "\x00") + msg = b"\x30\x00\x00".ljust(8, b"\x00") if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) self.panda.can_send(self.tx_addr, msg, self.bus) elif rx_data[0] >> 4 == 0x2: @@ -344,7 +346,7 @@ def _isotp_thread(self, debug): for i in range(start, end, 7): tx_frame["idx"] += 1 # consecutive tx frames - msg = (chr(0x20 | (tx_frame["idx"] & 0xF)) + tx_frame["data"][i:i+7]).ljust(8, "\x00") + msg = (chr(0x20 | (tx_frame["idx"] & 0xF)).encode("utf8") + tx_frame["data"][i:i+7]).ljust(8, b"\x00") if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) self.panda.can_send(self.tx_addr, msg, self.bus) if delay_ts > 0: @@ -360,13 +362,13 @@ def _isotp_thread(self, debug): if tx_frame["size"] < 8: # single frame tx_frame["done"] = True - msg = (chr(tx_frame["size"]) + tx_frame["data"]).ljust(8, "\x00") + msg = (chr(tx_frame["size"]).encode("utf8") + tx_frame["data"]).ljust(8, b"\x00") if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) self.panda.can_send(self.tx_addr, msg, self.bus) else: # first rx_frame tx_frame["done"] = False - msg = (struct.pack("!H", 0x1000 | tx_frame["size"]) + tx_frame["data"][:6]).ljust(8, "\x00") + msg = (struct.pack("!H", 0x1000 | tx_frame["size"]) + tx_frame["data"][:6]).ljust(8, b"\x00") if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) self.panda.can_send(self.tx_addr, msg, self.bus) else: @@ -377,9 +379,9 @@ def _isotp_thread(self, debug): # generic uds request def _uds_request(self, service_type, subfunction=None, data=None): - req = chr(service_type) + req = chr(service_type).encode("utf8") if subfunction is not None: - req += chr(subfunction) + req += chr(subfunction).encode("utf8") if data is not None: req += data self.tx_queue.put(req) @@ -450,7 +452,7 @@ def security_access(self, access_type, security_key=None): return security_seed def communication_control(self, control_type, message_type): - data = chr(message_type) + data = chr(message_type).encode("utf8") self._uds_request(SERVICE_TYPE.COMMUNICATION_CONTROL, subfunction=control_type, data=data) def tester_present(self, ): @@ -503,7 +505,7 @@ def response_on_event(self, response_event_type, store_event, window_time, event def link_control(self, link_control_type, baud_rate_type=None): if link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: # baud_rate_type = BAUD_RATE_TYPE - data = chr(baud_rate_type) + data = chr(baud_rate_type).encode("utf8") elif link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE: # baud_rate_type = custom value (3 bytes big-endian) data = struct.pack('!I', baud_rate_type)[1:] @@ -525,7 +527,7 @@ def read_memory_by_address(self, memory_address, memory_size, memory_address_byt raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = chr(memory_size_bytes<<4 | memory_address_bytes) + data = chr(memory_size_bytes<<4 | memory_address_bytes).encode("utf8") if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -547,7 +549,7 @@ def read_scaling_data_by_identifier(self, data_identifier_type): def read_data_by_periodic_identifier(self, transmission_mode_type, periodic_data_identifier): # TODO: support list of identifiers - data = chr(transmission_mode_type) + chr(periodic_data_identifier) + data = chr(transmission_mode_type).encode("utf8") + chr(periodic_data_identifier).encode("utf8") self._uds_request(SERVICE_TYPE.READ_DATA_BY_PERIODIC_IDENTIFIER, subfunction=None, data=data) def dynamically_define_data_identifier(self, dynamic_definition_type, dynamic_data_identifier, source_definitions, memory_address_bytes=4, memory_size_bytes=1): @@ -559,9 +561,9 @@ def dynamically_define_data_identifier(self, dynamic_definition_type, dynamic_da data = struct.pack('!H', dynamic_data_identifier) if dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_IDENTIFIER: for s in source_definitions: - data += struct.pack('!H', s["data_identifier"]) + chr(s["position"]) + chr(s["memory_size"]) + data += struct.pack('!H', s["data_identifier"]) + chr(s["position"]).encode("utf8") + chr(s["memory_size"]).encode("utf8") elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_MEMORY_ADDRESS: - data += chr(memory_size_bytes<<4 | memory_address_bytes) + data += chr(memory_size_bytes<<4 | memory_address_bytes).encode("utf8") for s in source_definitions: if s["memory_address"] >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(s["memory_address"])) @@ -587,7 +589,7 @@ def write_memory_by_address(self, memory_address, memory_size, data_record, memo raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = chr(memory_size_bytes<<4 | memory_address_bytes) + data = chr(memory_size_bytes<<4 | memory_address_bytes).encode("utf8") if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -612,7 +614,7 @@ def read_dtc_information(self, dtc_report_type, dtc_status_mask_type=DTC_STATUS_ dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK or \ dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK or \ dtc_report_type == DTC_REPORT_TYPE.EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK: - data += chr(dtc_status_mask_type) + data += chr(dtc_status_mask_type).encode("utf8") # dtc_mask_record if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or \ dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or \ @@ -628,11 +630,11 @@ def read_dtc_information(self, dtc_report_type, dtc_status_mask_type=DTC_STATUS_ # dtc_extended_record_num if dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER: - data += chr(dtc_extended_record_num) + data += chr(dtc_extended_record_num).encode("utf8") # dtc_severity_mask_type if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD or \ dtc_report_type == DTC_REPORT_TYPE.DTC_BY_SEVERITY_MASK_RECORD: - data += chr(dtc_severity_mask_type) + chr(dtc_status_mask_type) + data += chr(dtc_severity_mask_type).encode("utf8") + chr(dtc_status_mask_type).encode("utf8") resp = self._uds_request(SERVICE_TYPE.READ_DTC_INFORMATION, subfunction=dtc_report_type, data=data) @@ -656,13 +658,13 @@ def routine_control(self, routine_control_type, routine_identifier_type, routine return resp[2:] def request_download(self, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): - data = chr(data_format) + data = chr(data_format).encode("utf8") if memory_address_bytes < 1 or memory_address_bytes > 4: raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data += chr(memory_size_bytes<<4 | memory_address_bytes) + data += chr(memory_size_bytes<<4 | memory_address_bytes).encode("utf8") if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -681,13 +683,13 @@ def request_download(self, memory_address, memory_size, memory_address_bytes=4, return max_num_bytes # max number of bytes per transfer data request def request_upload(self, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): - data = chr(data_format) + data = chr(data_format).encode("utf8") if memory_address_bytes < 1 or memory_address_bytes > 4: raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data += chr(memory_size_bytes<<4 | memory_address_bytes) + data += chr(memory_size_bytes<<4 | memory_address_bytes).encode("utf8") if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -706,7 +708,7 @@ def request_upload(self, memory_address, memory_size, memory_address_bytes=4, me return max_num_bytes # max number of bytes per transfer data request def transfer_data(self, block_sequence_count, data=''): - data = chr(block_sequence_count)+data + data = chr(block_sequence_count).encode("utf8") + data resp = self._uds_request(SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) resp_id = resp[0] if len(resp) > 0 else None if resp_id != block_sequence_count: From eb358e81ca45896bfb8fe76913f03b965d99034b Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Sat, 12 Oct 2019 20:43:38 -0700 Subject: [PATCH 22/31] uds lib example --- examples/eps_read_software_ids.py | 62 +++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 examples/eps_read_software_ids.py diff --git a/examples/eps_read_software_ids.py b/examples/eps_read_software_ids.py new file mode 100755 index 00000000000000..c285582ff4d0f7 --- /dev/null +++ b/examples/eps_read_software_ids.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +import sys +import os +sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) +from python import Panda +from python.uds import UdsClient, NegativeResponseError, DATA_IDENTIFIER_TYPE + +if __name__ == "__main__": + address = 0x18da30f1 # Honda EPS + panda = Panda() + uds_client = UdsClient(panda, address, debug=False) + + print("tester present ...") + uds_client.tester_present() + + try: + print("") + print("read data by id: boot software id ...") + data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.BOOT_SOFTWARE_IDENTIFICATION) + print(data.decode('utf-8')) + except NegativeResponseError as e: + print(e) + + try: + print("") + print("read data by id: application software id ...") + data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) + print(data.decode('utf-8')) + except NegativeResponseError as e: + print(e) + + try: + print("") + print("read data by id: application data id ...") + data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) + print(data.decode('utf-8')) + except NegativeResponseError as e: + print(e) + + try: + print("") + print("read data by id: boot software fingerprint ...") + data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.BOOT_SOFTWARE_FINGERPRINT) + print(data.decode('utf-8')) + except NegativeResponseError as e: + print(e) + + try: + print("") + print("read data by id: application software fingerprint ...") + data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_FINGERPRINT) + print(data.decode('utf-8')) + except NegativeResponseError as e: + print(e) + + try: + print("") + print("read data by id: application data fingerprint ...") + data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_DATA_FINGERPRINT) + print(data.decode('utf-8')) + except NegativeResponseError as e: + print(e) From 68da8315f3406d4603f8297fcf7fcef9e455f952 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Sat, 12 Oct 2019 20:52:51 -0700 Subject: [PATCH 23/31] more python3 --- python/uds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/uds.py b/python/uds.py index 3c964770bfe96c..0eae9152d452c0 100644 --- a/python/uds.py +++ b/python/uds.py @@ -676,7 +676,7 @@ def request_download(self, memory_address, memory_size, memory_address_bytes=4, resp = self._uds_request(SERVICE_TYPE.REQUEST_DOWNLOAD, subfunction=None, data=data) max_num_bytes_len = resp[0] >> 4 if len(resp) > 0 else None if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: - max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] + max_num_bytes = struct.unpack('!I', (b"\x00"*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] else: raise ValueError('invalid max_num_bytes_len: {}'.format(max_num_bytes_len)) @@ -701,7 +701,7 @@ def request_upload(self, memory_address, memory_size, memory_address_bytes=4, me resp = self._uds_request(SERVICE_TYPE.REQUEST_UPLOAD, subfunction=None, data=data) max_num_bytes_len = resp[0] >> 4 if len(resp) > 0 else None if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: - max_num_bytes = struct.unpack('!I', ('\x00'*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] + max_num_bytes = struct.unpack('!I', (b"\x00"*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] else: raise ValueError('invalid max_num_bytes_len: {}'.format(max_num_bytes_len)) From 1be15ea9341ed9eb53a685e4881ee49ebfa0d2be Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Sun, 13 Oct 2019 08:30:44 -0700 Subject: [PATCH 24/31] custom errors from thread --- python/uds.py | 67 ++++++++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/python/uds.py b/python/uds.py index 0eae9152d452c0..dc09d3fd6e709e 100644 --- a/python/uds.py +++ b/python/uds.py @@ -286,8 +286,8 @@ def __init__(self, panda, tx_addr, rx_addr=None, bus=0, timeout=10, debug=False) def _isotp_thread(self, debug): try: - rx_frame = {"size": 0, "data": "", "idx": 0, "done": True} - tx_frame = {"size": 0, "data": "", "idx": 0, "done": True} + rx_frame = {"size": 0, "data": b"", "idx": 0, "done": True} + tx_frame = {"size": 0, "data": b"", "idx": 0, "done": True} # allow all output self.panda.set_safety_mode(0x1337) @@ -333,9 +333,16 @@ def _isotp_thread(self, debug): self.rx_queue.put(rx_frame["data"]) elif rx_data[0] >> 4 == 0x3: # flow control - assert tx_frame["done"] == False, "tx: no active frame" - # TODO: support wait/overflow - assert rx_data[0] == 0x30, "tx: flow-control requires: continue" + if tx_frame["done"] != False: + tx_frame["done"] = True + self.rx_queue.put(b"\x7F\xFF\xFFtx: no active frame") + # TODO: support wait + if rx_data[0] == 0x31: + tx_frame["done"] = True + self.rx_queue.put(b"\x7F\xFF\xFFtx: flow-control error - wait not supported") + if rx_data[0] == 0x32: + tx_frame["done"] = True + self.rx_queue.put(b"\x7F\xFF\xFFtx: flow-control error - overflow/abort") delay_ts = rx_data[2] & 0x7F # scale is 1 milliseconds if first bit == 0, 100 micro seconds if first bit == 1 delay_div = 1000. if rx_data[2] & 0x80 == 0 else 10000. @@ -346,7 +353,7 @@ def _isotp_thread(self, debug): for i in range(start, end, 7): tx_frame["idx"] += 1 # consecutive tx frames - msg = (chr(0x20 | (tx_frame["idx"] & 0xF)).encode("utf8") + tx_frame["data"][i:i+7]).ljust(8, b"\x00") + msg = (chr(0x20 | (tx_frame["idx"] & 0xF)).encode() + tx_frame["data"][i:i+7]).ljust(8, b"\x00") if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) self.panda.can_send(self.tx_addr, msg, self.bus) if delay_ts > 0: @@ -357,12 +364,12 @@ def _isotp_thread(self, debug): if not self.tx_queue.empty(): req = self.tx_queue.get(block=False) # reset rx and tx frames - rx_frame = {"size": 0, "data": "", "idx": 0, "done": True} + rx_frame = {"size": 0, "data": b"", "idx": 0, "done": True} tx_frame = {"size": len(req), "data": req, "idx": 0, "done": False} if tx_frame["size"] < 8: # single frame tx_frame["done"] = True - msg = (chr(tx_frame["size"]).encode("utf8") + tx_frame["data"]).ljust(8, b"\x00") + msg = (chr(tx_frame["size"]).encode() + tx_frame["data"]).ljust(8, b"\x00") if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) self.panda.can_send(self.tx_addr, msg, self.bus) else: @@ -379,9 +386,9 @@ def _isotp_thread(self, debug): # generic uds request def _uds_request(self, service_type, subfunction=None, data=None): - req = chr(service_type).encode("utf8") + req = chr(service_type).encode() if subfunction is not None: - req += chr(subfunction).encode("utf8") + req += chr(subfunction).encode() if data is not None: req += data self.tx_queue.put(req) @@ -407,7 +414,7 @@ def _uds_request(self, service_type, subfunction=None, data=None): try: error_desc = _negative_response_codes[error_code] except Exception: - error_desc = 'unknown error' + error_desc = resp[3:] # wait for another message if response pending if error_code == 0x78: time.sleep(0.1) @@ -452,7 +459,7 @@ def security_access(self, access_type, security_key=None): return security_seed def communication_control(self, control_type, message_type): - data = chr(message_type).encode("utf8") + data = chr(message_type).encode() self._uds_request(SERVICE_TYPE.COMMUNICATION_CONTROL, subfunction=control_type, data=data) def tester_present(self, ): @@ -505,7 +512,7 @@ def response_on_event(self, response_event_type, store_event, window_time, event def link_control(self, link_control_type, baud_rate_type=None): if link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: # baud_rate_type = BAUD_RATE_TYPE - data = chr(baud_rate_type).encode("utf8") + data = chr(baud_rate_type).encode() elif link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE: # baud_rate_type = custom value (3 bytes big-endian) data = struct.pack('!I', baud_rate_type)[1:] @@ -527,7 +534,7 @@ def read_memory_by_address(self, memory_address, memory_size, memory_address_byt raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = chr(memory_size_bytes<<4 | memory_address_bytes).encode("utf8") + data = chr(memory_size_bytes<<4 | memory_address_bytes).encode() if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -549,7 +556,7 @@ def read_scaling_data_by_identifier(self, data_identifier_type): def read_data_by_periodic_identifier(self, transmission_mode_type, periodic_data_identifier): # TODO: support list of identifiers - data = chr(transmission_mode_type).encode("utf8") + chr(periodic_data_identifier).encode("utf8") + data = chr(transmission_mode_type).encode() + chr(periodic_data_identifier).encode() self._uds_request(SERVICE_TYPE.READ_DATA_BY_PERIODIC_IDENTIFIER, subfunction=None, data=data) def dynamically_define_data_identifier(self, dynamic_definition_type, dynamic_data_identifier, source_definitions, memory_address_bytes=4, memory_size_bytes=1): @@ -561,9 +568,9 @@ def dynamically_define_data_identifier(self, dynamic_definition_type, dynamic_da data = struct.pack('!H', dynamic_data_identifier) if dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_IDENTIFIER: for s in source_definitions: - data += struct.pack('!H', s["data_identifier"]) + chr(s["position"]).encode("utf8") + chr(s["memory_size"]).encode("utf8") + data += struct.pack('!H', s["data_identifier"]) + chr(s["position"]).encode() + chr(s["memory_size"]).encode() elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_MEMORY_ADDRESS: - data += chr(memory_size_bytes<<4 | memory_address_bytes).encode("utf8") + data += chr(memory_size_bytes<<4 | memory_address_bytes).encode() for s in source_definitions: if s["memory_address"] >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(s["memory_address"])) @@ -589,7 +596,7 @@ def write_memory_by_address(self, memory_address, memory_size, data_record, memo raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = chr(memory_size_bytes<<4 | memory_address_bytes).encode("utf8") + data = chr(memory_size_bytes<<4 | memory_address_bytes).encode() if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -606,7 +613,7 @@ def clear_diagnostic_information(self, dtc_group_type): self._uds_request(SERVICE_TYPE.CLEAR_DIAGNOSTIC_INFORMATION, subfunction=None, data=data) def read_dtc_information(self, dtc_report_type, dtc_status_mask_type=DTC_STATUS_MASK_TYPE.ALL, dtc_severity_mask_type=DTC_SEVERITY_MASK_TYPE.ALL, dtc_mask_record=0xFFFFFF, dtc_snapshot_record_num=0xFF, dtc_extended_record_num=0xFF): - data = '' + data = b'' # dtc_status_mask_type if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_STATUS_MASK or \ dtc_report_type == DTC_REPORT_TYPE.DTC_BY_STATUS_MASK or \ @@ -614,7 +621,7 @@ def read_dtc_information(self, dtc_report_type, dtc_status_mask_type=DTC_STATUS_ dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK or \ dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK or \ dtc_report_type == DTC_REPORT_TYPE.EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK: - data += chr(dtc_status_mask_type).encode("utf8") + data += chr(dtc_status_mask_type).encode() # dtc_mask_record if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or \ dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or \ @@ -630,18 +637,18 @@ def read_dtc_information(self, dtc_report_type, dtc_status_mask_type=DTC_STATUS_ # dtc_extended_record_num if dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER: - data += chr(dtc_extended_record_num).encode("utf8") + data += chr(dtc_extended_record_num).encode() # dtc_severity_mask_type if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD or \ dtc_report_type == DTC_REPORT_TYPE.DTC_BY_SEVERITY_MASK_RECORD: - data += chr(dtc_severity_mask_type).encode("utf8") + chr(dtc_status_mask_type).encode("utf8") + data += chr(dtc_severity_mask_type).encode() + chr(dtc_status_mask_type).encode() resp = self._uds_request(SERVICE_TYPE.READ_DTC_INFORMATION, subfunction=dtc_report_type, data=data) # TODO: parse response return resp - def input_output_control_by_identifier(self, data_identifier_type, control_option_record, control_enable_mask_record=''): + def input_output_control_by_identifier(self, data_identifier_type, control_option_record, control_enable_mask_record=b''): data = struct.pack('!H', data_identifier_type) + control_option_record + control_enable_mask_record resp = self._uds_request(SERVICE_TYPE.INPUT_OUTPUT_CONTROL_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None @@ -649,7 +656,7 @@ def input_output_control_by_identifier(self, data_identifier_type, control_optio raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) return resp[2:] - def routine_control(self, routine_control_type, routine_identifier_type, routine_option_record=''): + def routine_control(self, routine_control_type, routine_identifier_type, routine_option_record=b''): data = struct.pack('!H', routine_identifier_type) + routine_option_record resp = self._uds_request(SERVICE_TYPE.ROUTINE_CONTROL, subfunction=routine_control_type, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None @@ -658,13 +665,13 @@ def routine_control(self, routine_control_type, routine_identifier_type, routine return resp[2:] def request_download(self, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): - data = chr(data_format).encode("utf8") + data = chr(data_format).encode() if memory_address_bytes < 1 or memory_address_bytes > 4: raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data += chr(memory_size_bytes<<4 | memory_address_bytes).encode("utf8") + data += chr(memory_size_bytes<<4 | memory_address_bytes).encode() if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -683,13 +690,13 @@ def request_download(self, memory_address, memory_size, memory_address_bytes=4, return max_num_bytes # max number of bytes per transfer data request def request_upload(self, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): - data = chr(data_format).encode("utf8") + data = chr(data_format).encode() if memory_address_bytes < 1 or memory_address_bytes > 4: raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data += chr(memory_size_bytes<<4 | memory_address_bytes).encode("utf8") + data += chr(memory_size_bytes<<4 | memory_address_bytes).encode() if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -707,8 +714,8 @@ def request_upload(self, memory_address, memory_size, memory_address_bytes=4, me return max_num_bytes # max number of bytes per transfer data request - def transfer_data(self, block_sequence_count, data=''): - data = chr(block_sequence_count).encode("utf8") + data + def transfer_data(self, block_sequence_count, data=b''): + data = chr(block_sequence_count).encode() + data resp = self._uds_request(SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) resp_id = resp[0] if len(resp) > 0 else None if resp_id != block_sequence_count: From 768fdf7e19e94facbbfb7ad7259c1f87f8e86a19 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Sun, 13 Oct 2019 12:13:44 -0700 Subject: [PATCH 25/31] bytes() > chr().encode() --- python/uds.py | 82 +++++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/python/uds.py b/python/uds.py index dc09d3fd6e709e..438a2ab719c37c 100644 --- a/python/uds.py +++ b/python/uds.py @@ -270,7 +270,7 @@ def __init__(self, panda, tx_addr, rx_addr=None, bus=0, timeout=10, debug=False) # standard 11 bit response addr (add 8) self.rx_addr = tx_addr+8 elif tx_addr > 0x10000000 and tx_addr < 0xFFFFFFFF: - # standard 19 bit response addr (flip last two bytes) + # standard 29 bit response addr (flip last two bytes) self.rx_addr = (tx_addr & 0xFFFF0000) + (tx_addr<<8 & 0xFF00) + (tx_addr>>8 & 0xFF) else: raise ValueError("invalid tx_addr: {}".format(tx_addr)) @@ -336,28 +336,32 @@ def _isotp_thread(self, debug): if tx_frame["done"] != False: tx_frame["done"] = True self.rx_queue.put(b"\x7F\xFF\xFFtx: no active frame") - # TODO: support wait - if rx_data[0] == 0x31: - tx_frame["done"] = True - self.rx_queue.put(b"\x7F\xFF\xFFtx: flow-control error - wait not supported") if rx_data[0] == 0x32: + # 0x32 = overflow/abort tx_frame["done"] = True self.rx_queue.put(b"\x7F\xFF\xFFtx: flow-control error - overflow/abort") - delay_ts = rx_data[2] & 0x7F - # scale is 1 milliseconds if first bit == 0, 100 micro seconds if first bit == 1 - delay_div = 1000. if rx_data[2] & 0x80 == 0 else 10000. - # first frame = 6 bytes, each consecutive frame = 7 bytes - start = 6 + tx_frame["idx"] * 7 - count = rx_data[1] - end = start + count * 7 if count > 0 else tx_frame["size"] - for i in range(start, end, 7): - tx_frame["idx"] += 1 - # consecutive tx frames - msg = (chr(0x20 | (tx_frame["idx"] & 0xF)).encode() + tx_frame["data"][i:i+7]).ljust(8, b"\x00") - if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) - self.panda.can_send(self.tx_addr, msg, self.bus) - if delay_ts > 0: - time.sleep(delay_ts / delay_div) + if rx_data[0] != 0x30 and rx_data[0] != 0x31: + # 0x30 = continue + # 0x31 = wait + tx_frame["done"] = True + self.rx_queue.put(b"\x7F\xFF\xFFtx: flow-control error - invalid transfer state indicator") + if rx_data[0] == 0x30: + delay_ts = rx_data[2] & 0x7F + # scale is 1 milliseconds if first bit == 0, 100 micro seconds if first bit == 1 + delay_div = 1000. if rx_data[2] & 0x80 == 0 else 10000. + # first frame = 6 bytes, each consecutive frame = 7 bytes + start = 6 + tx_frame["idx"] * 7 + count = rx_data[1] + end = start + count * 7 if count > 0 else tx_frame["size"] + for i in range(start, end, 7): + if delay_ts > 0 and i > start: + if (debug): print("D: {}".format(delay_ts / delay_div)) + time.sleep(delay_ts / delay_div) + tx_frame["idx"] += 1 + # consecutive tx frames + msg = (bytes([0x20 | (tx_frame["idx"] & 0xF)]) + tx_frame["data"][i:i+7]).ljust(8, b"\x00") + if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) + self.panda.can_send(self.tx_addr, msg, self.bus) if end >= tx_frame["size"]: tx_frame["done"] = True @@ -369,7 +373,7 @@ def _isotp_thread(self, debug): if tx_frame["size"] < 8: # single frame tx_frame["done"] = True - msg = (chr(tx_frame["size"]).encode() + tx_frame["data"]).ljust(8, b"\x00") + msg = (bytes([tx_frame["size"]]) + tx_frame["data"]).ljust(8, b"\x00") if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) self.panda.can_send(self.tx_addr, msg, self.bus) else: @@ -386,9 +390,9 @@ def _isotp_thread(self, debug): # generic uds request def _uds_request(self, service_type, subfunction=None, data=None): - req = chr(service_type).encode() + req = bytes([service_type]) if subfunction is not None: - req += chr(subfunction).encode() + req += bytes([subfunction]) if data is not None: req += data self.tx_queue.put(req) @@ -459,7 +463,7 @@ def security_access(self, access_type, security_key=None): return security_seed def communication_control(self, control_type, message_type): - data = chr(message_type).encode() + data = bytes([message_type]) self._uds_request(SERVICE_TYPE.COMMUNICATION_CONTROL, subfunction=control_type, data=data) def tester_present(self, ): @@ -494,7 +498,7 @@ def response_on_event(self, response_event_type, store_event, window_time, event if store_event: response_event_type |= 0x20 # TODO: split record parameters into arrays - data = char(window_time) + event_type_record + service_response_record + data = bytes([window_time, event_type_record, service_response_record]) resp = self._uds_request(SERVICE_TYPE.RESPONSE_ON_EVENT, subfunction=response_event_type, data=data) if response_event_type == RESPONSE_EVENT_TYPE.REPORT_ACTIVATED_EVENTS: @@ -512,7 +516,7 @@ def response_on_event(self, response_event_type, store_event, window_time, event def link_control(self, link_control_type, baud_rate_type=None): if link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: # baud_rate_type = BAUD_RATE_TYPE - data = chr(baud_rate_type).encode() + data = bytes([baud_rate_type]) elif link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE: # baud_rate_type = custom value (3 bytes big-endian) data = struct.pack('!I', baud_rate_type)[1:] @@ -534,7 +538,7 @@ def read_memory_by_address(self, memory_address, memory_size, memory_address_byt raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = chr(memory_size_bytes<<4 | memory_address_bytes).encode() + data = bytes([memory_size_bytes<<4 | memory_address_bytes]) if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -556,7 +560,7 @@ def read_scaling_data_by_identifier(self, data_identifier_type): def read_data_by_periodic_identifier(self, transmission_mode_type, periodic_data_identifier): # TODO: support list of identifiers - data = chr(transmission_mode_type).encode() + chr(periodic_data_identifier).encode() + data = bytes([transmission_mode_type, periodic_data_identifier]) self._uds_request(SERVICE_TYPE.READ_DATA_BY_PERIODIC_IDENTIFIER, subfunction=None, data=data) def dynamically_define_data_identifier(self, dynamic_definition_type, dynamic_data_identifier, source_definitions, memory_address_bytes=4, memory_size_bytes=1): @@ -568,9 +572,9 @@ def dynamically_define_data_identifier(self, dynamic_definition_type, dynamic_da data = struct.pack('!H', dynamic_data_identifier) if dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_IDENTIFIER: for s in source_definitions: - data += struct.pack('!H', s["data_identifier"]) + chr(s["position"]).encode() + chr(s["memory_size"]).encode() + data += struct.pack('!H', s["data_identifier"]) + bytes([s["position"], s["memory_size"]]) elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_MEMORY_ADDRESS: - data += chr(memory_size_bytes<<4 | memory_address_bytes).encode() + data += bytes([memory_size_bytes<<4 | memory_address_bytes]) for s in source_definitions: if s["memory_address"] >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(s["memory_address"])) @@ -596,7 +600,7 @@ def write_memory_by_address(self, memory_address, memory_size, data_record, memo raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data = chr(memory_size_bytes<<4 | memory_address_bytes).encode() + data = bytes([memory_size_bytes<<4 | memory_address_bytes]) if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -621,7 +625,7 @@ def read_dtc_information(self, dtc_report_type, dtc_status_mask_type=DTC_STATUS_ dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK or \ dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK or \ dtc_report_type == DTC_REPORT_TYPE.EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK: - data += chr(dtc_status_mask_type).encode() + data += bytes([dtc_status_mask_type]) # dtc_mask_record if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or \ dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or \ @@ -637,11 +641,11 @@ def read_dtc_information(self, dtc_report_type, dtc_status_mask_type=DTC_STATUS_ # dtc_extended_record_num if dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER: - data += chr(dtc_extended_record_num).encode() + data += bytes([dtc_extended_record_num]) # dtc_severity_mask_type if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD or \ dtc_report_type == DTC_REPORT_TYPE.DTC_BY_SEVERITY_MASK_RECORD: - data += chr(dtc_severity_mask_type).encode() + chr(dtc_status_mask_type).encode() + data += bytes([dtc_severity_mask_type, dtc_status_mask_type]) resp = self._uds_request(SERVICE_TYPE.READ_DTC_INFORMATION, subfunction=dtc_report_type, data=data) @@ -665,13 +669,13 @@ def routine_control(self, routine_control_type, routine_identifier_type, routine return resp[2:] def request_download(self, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): - data = chr(data_format).encode() + data = bytes([data_format]) if memory_address_bytes < 1 or memory_address_bytes > 4: raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data += chr(memory_size_bytes<<4 | memory_address_bytes).encode() + data += bytes([memory_size_bytes<<4 | memory_address_bytes]) if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -690,13 +694,13 @@ def request_download(self, memory_address, memory_size, memory_address_bytes=4, return max_num_bytes # max number of bytes per transfer data request def request_upload(self, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): - data = chr(data_format).encode() + data = bytes([data_format]) if memory_address_bytes < 1 or memory_address_bytes > 4: raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) - data += chr(memory_size_bytes<<4 | memory_address_bytes).encode() + data += bytes([memory_size_bytes<<4 | memory_address_bytes]) if memory_address >= 1<<(memory_address_bytes*8): raise ValueError('invalid memory_address: {}'.format(memory_address)) @@ -715,7 +719,7 @@ def request_upload(self, memory_address, memory_size, memory_address_bytes=4, me return max_num_bytes # max number of bytes per transfer data request def transfer_data(self, block_sequence_count, data=b''): - data = chr(block_sequence_count).encode() + data + data = bytes([block_sequence_count]) + data resp = self._uds_request(SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) resp_id = resp[0] if len(resp) > 0 else None if resp_id != block_sequence_count: From b64d6fa5d2ed9dc0cf07994d6dcabcbb9ba4e0b4 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Sun, 13 Oct 2019 20:43:55 -0700 Subject: [PATCH 26/31] typing --- python/uds.py | 63 ++++++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/python/uds.py b/python/uds.py index 438a2ab719c37c..76bae2660030f2 100644 --- a/python/uds.py +++ b/python/uds.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import time import struct +from typing import NamedTuple, List from enum import IntEnum from queue import Queue, Empty from threading import Thread @@ -141,6 +142,12 @@ class DYNAMIC_DEFINITION_TYPE(IntEnum): DEFINE_BY_MEMORY_ADDRESS = 2 CLEAR_DYNAMICALLY_DEFINED_DATA_IDENTIFIER = 3 +class DynamicSourceDefinition(NamedTuple): + data_identifier: int + position: int + memory_size: int + memory_address: int + class DTC_GROUP_TYPE(IntEnum): EMISSIONS = 0x000000 ALL = 0xFFFFFF @@ -185,7 +192,7 @@ class DTC_SEVERITY_MASK_TYPE(IntEnum): CHECK_IMMEDIATELY = 0x80 ALL = 0xE0 -class CONTROL_OPTION_TYPE(IntEnum): +class CONTROL_PARAMETER_TYPE(IntEnum): RETURN_CONTROL_TO_ECU = 0 RESET_TO_DEFAULT = 1 FREEZE_CURRENT_STATE = 2 @@ -261,7 +268,7 @@ class InvalidSubFunctioneError(Exception): } class UdsClient(): - def __init__(self, panda, tx_addr, rx_addr=None, bus=0, timeout=10, debug=False): + def __init__(self, panda, tx_addr: int, rx_addr: int=None, bus: int=0, timeout: int=10, debug: bool=False): self.panda = panda self.bus = bus self.tx_addr = tx_addr @@ -284,7 +291,7 @@ def __init__(self, panda, tx_addr, rx_addr=None, bus=0, timeout=10, debug=False) self.can_reader_t.daemon = True self.can_reader_t.start() - def _isotp_thread(self, debug): + def _isotp_thread(self, debug: bool=False): try: rx_frame = {"size": 0, "data": b"", "idx": 0, "done": True} tx_frame = {"size": 0, "data": b"", "idx": 0, "done": True} @@ -389,7 +396,7 @@ def _isotp_thread(self, debug): self.rx_queue.put(None) # generic uds request - def _uds_request(self, service_type, subfunction=None, data=None): + def _uds_request(self, service_type: SERVICE_TYPE, subfunction: int=None, data: bytes=None) -> bytes: req = bytes([service_type]) if subfunction is not None: req += bytes([subfunction]) @@ -441,17 +448,17 @@ def _uds_request(self, service_type, subfunction=None, data=None): return resp[(1 if subfunction is None else 2):] # services - def diagnostic_session_control(self, session_type): + def diagnostic_session_control(self, session_type: SESSION_TYPE): self._uds_request(SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, subfunction=session_type) - def ecu_reset(self, reset_type): + def ecu_reset(self, reset_type: RESET_TYPE): resp = self._uds_request(SERVICE_TYPE.ECU_RESET, subfunction=reset_type) power_down_time = None if reset_type == RESET_TYPE.ENABLE_RAPID_POWER_SHUTDOWN: power_down_time = resp[0] return power_down_time - def security_access(self, access_type, security_key=None): + def security_access(self, access_type: ACCESS_TYPE, security_key: bytes=None): request_seed = access_type % 2 != 0 if request_seed and security_key is not None: raise ValueError('security_key not allowed') @@ -462,14 +469,14 @@ def security_access(self, access_type, security_key=None): security_seed = resp return security_seed - def communication_control(self, control_type, message_type): + def communication_control(self, control_type: CONTROL_TYPE, message_type: MESSAGE_TYPE): data = bytes([message_type]) self._uds_request(SERVICE_TYPE.COMMUNICATION_CONTROL, subfunction=control_type, data=data) def tester_present(self, ): self._uds_request(SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) - def access_timing_parameter(self, timing_parameter_type, parameter_values): + def access_timing_parameter(self, timing_parameter_type: TIMING_PARAMETER_TYPE, parameter_values: bytes=None): write_custom_values = timing_parameter_type == TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES read_values = ( timing_parameter_type == TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or @@ -485,16 +492,16 @@ def access_timing_parameter(self, timing_parameter_type, parameter_values): parameter_values = resp return parameter_values - def secured_data_transmission(self, data): + def secured_data_transmission(self, data: bytes): # TODO: split data into multiple input parameters? resp = self._uds_request(SERVICE_TYPE.SECURED_DATA_TRANSMISSION, subfunction=None, data=data) # TODO: parse response into multiple output values? return resp - def control_dtc_setting(self, dtc_setting_type): + def control_dtc_setting(self, dtc_setting_type: DTC_SETTING_TYPE): self._uds_request(SERVICE_TYPE.CONTROL_DTC_SETTING, subfunction=dtc_setting_type) - def response_on_event(self, response_event_type, store_event, window_time, event_type_record, service_response_record): + def response_on_event(self, response_event_type: RESPONSE_EVENT_TYPE, store_event: bool, window_time: int, event_type_record: int, service_response_record: int): if store_event: response_event_type |= 0x20 # TODO: split record parameters into arrays @@ -513,7 +520,7 @@ def response_on_event(self, response_event_type, store_event, window_time, event "data": resp[2:], # TODO: parse the reset of response } - def link_control(self, link_control_type, baud_rate_type=None): + def link_control(self, link_control_type: LINK_CONTROL_TYPE, baud_rate_type: BAUD_RATE_TYPE=None): if link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: # baud_rate_type = BAUD_RATE_TYPE data = bytes([baud_rate_type]) @@ -524,7 +531,7 @@ def link_control(self, link_control_type, baud_rate_type=None): data = None self._uds_request(SERVICE_TYPE.LINK_CONTROL, subfunction=link_control_type, data=data) - def read_data_by_identifier(self, data_identifier_type): + def read_data_by_identifier(self, data_identifier_type: DATA_IDENTIFIER_TYPE): # TODO: support list of identifiers data = struct.pack('!H', data_identifier_type) resp = self._uds_request(SERVICE_TYPE.READ_DATA_BY_IDENTIFIER, subfunction=None, data=data) @@ -533,7 +540,7 @@ def read_data_by_identifier(self, data_identifier_type): raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) return resp[2:] - def read_memory_by_address(self, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=1): + def read_memory_by_address(self, memory_address: int, memory_size: int, memory_address_bytes: int=4, memory_size_bytes: int=1): if memory_address_bytes < 1 or memory_address_bytes > 4: raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: @@ -550,7 +557,7 @@ def read_memory_by_address(self, memory_address, memory_size, memory_address_byt resp = self._uds_request(SERVICE_TYPE.READ_MEMORY_BY_ADDRESS, subfunction=None, data=data) return resp - def read_scaling_data_by_identifier(self, data_identifier_type): + def read_scaling_data_by_identifier(self, data_identifier_type: DATA_IDENTIFIER_TYPE): data = struct.pack('!H', data_identifier_type) resp = self._uds_request(SERVICE_TYPE.READ_SCALING_DATA_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None @@ -558,12 +565,12 @@ def read_scaling_data_by_identifier(self, data_identifier_type): raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) return resp[2:] # TODO: parse the response - def read_data_by_periodic_identifier(self, transmission_mode_type, periodic_data_identifier): + def read_data_by_periodic_identifier(self, transmission_mode_type: TRANSMISSION_MODE_TYPE, periodic_data_identifier: int): # TODO: support list of identifiers data = bytes([transmission_mode_type, periodic_data_identifier]) self._uds_request(SERVICE_TYPE.READ_DATA_BY_PERIODIC_IDENTIFIER, subfunction=None, data=data) - def dynamically_define_data_identifier(self, dynamic_definition_type, dynamic_data_identifier, source_definitions, memory_address_bytes=4, memory_size_bytes=1): + def dynamically_define_data_identifier(self, dynamic_definition_type: DYNAMIC_DEFINITION_TYPE, dynamic_data_identifier: int, source_definitions: List[DynamicSourceDefinition], memory_address_bytes: int=4, memory_size_bytes: int=1): if memory_address_bytes < 1 or memory_address_bytes > 4: raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: @@ -588,14 +595,14 @@ def dynamically_define_data_identifier(self, dynamic_definition_type, dynamic_da raise ValueError('invalid dynamic identifier type: {}'.format(hex(dynamic_definition_type))) self._uds_request(SERVICE_TYPE.DYNAMICALLY_DEFINE_DATA_IDENTIFIER, subfunction=dynamic_definition_type, data=data) - def write_data_by_identifier(self, data_identifier_type, data_record): + def write_data_by_identifier(self, data_identifier_type: DATA_IDENTIFIER_TYPE, data_record: bytes): data = struct.pack('!H', data_identifier_type) + data_record resp = self._uds_request(SERVICE_TYPE.WRITE_DATA_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) - def write_memory_by_address(self, memory_address, memory_size, data_record, memory_address_bytes=4, memory_size_bytes=1): + def write_memory_by_address(self, memory_address: int, memory_size: int, data_record: bytes, memory_address_bytes: int=4, memory_size_bytes: int=1): if memory_address_bytes < 1 or memory_address_bytes > 4: raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) if memory_size_bytes < 1 or memory_size_bytes > 4: @@ -612,11 +619,11 @@ def write_memory_by_address(self, memory_address, memory_size, data_record, memo data += data_record self._uds_request(SERVICE_TYPE.WRITE_MEMORY_BY_ADDRESS, subfunction=0x00, data=data) - def clear_diagnostic_information(self, dtc_group_type): + def clear_diagnostic_information(self, dtc_group_type: DTC_GROUP_TYPE): data = struct.pack('!I', dtc_group_type)[1:] # 3 bytes self._uds_request(SERVICE_TYPE.CLEAR_DIAGNOSTIC_INFORMATION, subfunction=None, data=data) - def read_dtc_information(self, dtc_report_type, dtc_status_mask_type=DTC_STATUS_MASK_TYPE.ALL, dtc_severity_mask_type=DTC_SEVERITY_MASK_TYPE.ALL, dtc_mask_record=0xFFFFFF, dtc_snapshot_record_num=0xFF, dtc_extended_record_num=0xFF): + def read_dtc_information(self, dtc_report_type: DTC_REPORT_TYPE, dtc_status_mask_type: DTC_STATUS_MASK_TYPE=DTC_STATUS_MASK_TYPE.ALL, dtc_severity_mask_type: DTC_SEVERITY_MASK_TYPE=DTC_SEVERITY_MASK_TYPE.ALL, dtc_mask_record: int=0xFFFFFF, dtc_snapshot_record_num: int=0xFF, dtc_extended_record_num: int=0xFF): data = b'' # dtc_status_mask_type if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_STATUS_MASK or \ @@ -652,15 +659,15 @@ def read_dtc_information(self, dtc_report_type, dtc_status_mask_type=DTC_STATUS_ # TODO: parse response return resp - def input_output_control_by_identifier(self, data_identifier_type, control_option_record, control_enable_mask_record=b''): - data = struct.pack('!H', data_identifier_type) + control_option_record + control_enable_mask_record + def input_output_control_by_identifier(self, data_identifier_type: DATA_IDENTIFIER_TYPE, control_parameter_type: CONTROL_PARAMETER_TYPE, control_option_record: bytes, control_enable_mask_record: bytes=b''): + data = struct.pack('!H', data_identifier_type) + bytes([control_parameter_type]) + control_option_record + control_enable_mask_record resp = self._uds_request(SERVICE_TYPE.INPUT_OUTPUT_CONTROL_BY_IDENTIFIER, subfunction=None, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None if resp_id != data_identifier_type: raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) return resp[2:] - def routine_control(self, routine_control_type, routine_identifier_type, routine_option_record=b''): + def routine_control(self, routine_control_type: ROUTINE_CONTROL_TYPE, routine_identifier_type: ROUTINE_IDENTIFIER_TYPE, routine_option_record: bytes=b''): data = struct.pack('!H', routine_identifier_type) + routine_option_record resp = self._uds_request(SERVICE_TYPE.ROUTINE_CONTROL, subfunction=routine_control_type, data=data) resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None @@ -668,7 +675,7 @@ def routine_control(self, routine_control_type, routine_identifier_type, routine raise ValueError('invalid response routine identifier: {}'.format(hex(resp_id))) return resp[2:] - def request_download(self, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): + def request_download(self, memory_address: int, memory_size: int, memory_address_bytes: int=4, memory_size_bytes: int=4, data_format: int=0x00): data = bytes([data_format]) if memory_address_bytes < 1 or memory_address_bytes > 4: @@ -693,7 +700,7 @@ def request_download(self, memory_address, memory_size, memory_address_bytes=4, return max_num_bytes # max number of bytes per transfer data request - def request_upload(self, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4, data_format=0x00): + def request_upload(self, memory_address: int, memory_size: int, memory_address_bytes: int=4, memory_size_bytes: int=4, data_format: int=0x00): data = bytes([data_format]) if memory_address_bytes < 1 or memory_address_bytes > 4: @@ -718,7 +725,7 @@ def request_upload(self, memory_address, memory_size, memory_address_bytes=4, me return max_num_bytes # max number of bytes per transfer data request - def transfer_data(self, block_sequence_count, data=b''): + def transfer_data(self, block_sequence_count: int, data: bytes=b''): data = bytes([block_sequence_count]) + data resp = self._uds_request(SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) resp_id = resp[0] if len(resp) > 0 else None From 9c857da37948c46da339539643d182fd874c6030 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Sun, 13 Oct 2019 20:54:02 -0700 Subject: [PATCH 27/31] 0x --- python/uds.py | 62 +++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/python/uds.py b/python/uds.py index 76bae2660030f2..5081b9dd2b4d45 100644 --- a/python/uds.py +++ b/python/uds.py @@ -99,37 +99,37 @@ class BAUD_RATE_TYPE(IntEnum): CAN1000000 = 19 class DATA_IDENTIFIER_TYPE(IntEnum): - BOOT_SOFTWARE_IDENTIFICATION = 0XF180 - APPLICATION_SOFTWARE_IDENTIFICATION = 0XF181 - APPLICATION_DATA_IDENTIFICATION = 0XF182 - BOOT_SOFTWARE_FINGERPRINT = 0XF183 - APPLICATION_SOFTWARE_FINGERPRINT = 0XF184 - APPLICATION_DATA_FINGERPRINT = 0XF185 - ACTIVE_DIAGNOSTIC_SESSION = 0XF186 - VEHICLE_MANUFACTURER_SPARE_PART_NUMBER = 0XF187 - VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER = 0XF188 - VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER = 0XF189 - SYSTEM_SUPPLIER_IDENTIFIER = 0XF18A - ECU_MANUFACTURING_DATE = 0XF18B - ECU_SERIAL_NUMBER = 0XF18C - SUPPORTED_FUNCTIONAL_UNITS = 0XF18D - VEHICLE_MANUFACTURER_KIT_ASSEMBLY_PART_NUMBER = 0XF18E - VIN = 0XF190 - VEHICLE_MANUFACTURER_ECU_HARDWARE_NUMBER = 0XF191 - SYSTEM_SUPPLIER_ECU_HARDWARE_NUMBER = 0XF192 - SYSTEM_SUPPLIER_ECU_HARDWARE_VERSION_NUMBER = 0XF193 - SYSTEM_SUPPLIER_ECU_SOFTWARE_NUMBER = 0XF194 - SYSTEM_SUPPLIER_ECU_SOFTWARE_VERSION_NUMBER = 0XF195 - EXHAUST_REGULATION_OR_TYPE_APPROVAL_NUMBER = 0XF196 - SYSTEM_NAME_OR_ENGINE_TYPE = 0XF197 - REPAIR_SHOP_CODE_OR_TESTER_SERIAL_NUMBER = 0XF198 - PROGRAMMING_DATE = 0XF199 - CALIBRATION_REPAIR_SHOP_CODE_OR_CALIBRATION_EQUIPMENT_SERIAL_NUMBER = 0XF19A - CALIBRATION_DATE = 0XF19B - CALIBRATION_EQUIPMENT_SOFTWARE_NUMBER = 0XF19C - ECU_INSTALLATION_DATE = 0XF19D - ODX_FILE = 0XF19E - ENTITY = 0XF19F + BOOT_SOFTWARE_IDENTIFICATION = 0xF180 + APPLICATION_SOFTWARE_IDENTIFICATION = 0xF181 + APPLICATION_DATA_IDENTIFICATION = 0xF182 + BOOT_SOFTWARE_FINGERPRINT = 0xF183 + APPLICATION_SOFTWARE_FINGERPRINT = 0xF184 + APPLICATION_DATA_FINGERPRINT = 0xF185 + ACTIVE_DIAGNOSTIC_SESSION = 0xF186 + VEHICLE_MANUFACTURER_SPARE_PART_NUMBER = 0xF187 + VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER = 0xF188 + VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER = 0xF189 + SYSTEM_SUPPLIER_IDENTIFIER = 0xF18A + ECU_MANUFACTURING_DATE = 0xF18B + ECU_SERIAL_NUMBER = 0xF18C + SUPPORTED_FUNCTIONAL_UNITS = 0xF18D + VEHICLE_MANUFACTURER_KIT_ASSEMBLY_PART_NUMBER = 0xF18E + VIN = 0xF190 + VEHICLE_MANUFACTURER_ECU_HARDWARE_NUMBER = 0xF191 + SYSTEM_SUPPLIER_ECU_HARDWARE_NUMBER = 0xF192 + SYSTEM_SUPPLIER_ECU_HARDWARE_VERSION_NUMBER = 0xF193 + SYSTEM_SUPPLIER_ECU_SOFTWARE_NUMBER = 0xF194 + SYSTEM_SUPPLIER_ECU_SOFTWARE_VERSION_NUMBER = 0xF195 + EXHAUST_REGULATION_OR_TYPE_APPROVAL_NUMBER = 0xF196 + SYSTEM_NAME_OR_ENGINE_TYPE = 0xF197 + REPAIR_SHOP_CODE_OR_TESTER_SERIAL_NUMBER = 0xF198 + PROGRAMMING_DATE = 0xF199 + CALIBRATION_REPAIR_SHOP_CODE_OR_CALIBRATION_EQUIPMENT_SERIAL_NUMBER = 0xF19A + CALIBRATION_DATE = 0xF19B + CALIBRATION_EQUIPMENT_SOFTWARE_NUMBER = 0xF19C + ECU_INSTALLATION_DATE = 0xF19D + ODX_FILE = 0xF19E + ENTITY = 0xF19F class TRANSMISSION_MODE_TYPE(IntEnum): SEND_AT_SLOW_RATE = 1 From 43adad311680e2e3f9ff7ecb3745fef1b36e4266 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Mon, 14 Oct 2019 17:30:30 -0700 Subject: [PATCH 28/31] fix WARNING_INDICATOR_REQUESTED name --- python/uds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/uds.py b/python/uds.py index 5081b9dd2b4d45..5acfb459575196 100644 --- a/python/uds.py +++ b/python/uds.py @@ -183,7 +183,7 @@ class DTC_STATUS_MASK_TYPE(IntEnum): TEST_NOT_COMPLETED_SINCE_LAST_CLEAR = 0x10 TEST_FAILED_SINCE_LAST_CLEAR = 0x20 TEST_NOT_COMPLETED_THIS_OPERATION_CYCLE = 0x40 - WARNING_INDICATOR_uds_requestED = 0x80 + WARNING_INDICATOR_REQUESTED = 0x80 ALL = 0xFF class DTC_SEVERITY_MASK_TYPE(IntEnum): From 4454e3a6bb090b9b2cc65ba4ec5943f47fe266b4 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Tue, 15 Oct 2019 12:07:19 -0700 Subject: [PATCH 29/31] better CAN comm abstraction --- python/uds.py | 257 ++++++++++++++++++++++++++++---------------------- 1 file changed, 143 insertions(+), 114 deletions(-) diff --git a/python/uds.py b/python/uds.py index 5acfb459575196..2fdb68e667b42a 100644 --- a/python/uds.py +++ b/python/uds.py @@ -267,6 +267,117 @@ class InvalidSubFunctioneError(Exception): 0x93: 'voltage too low', } +class IsoTpMessage(): + def __init__(self, can_tx_queue: Queue, can_rx_queue: Queue, timeout: float, debug: bool=False): + self.can_tx_queue = can_tx_queue + self.can_rx_queue = can_rx_queue + self.timeout = timeout + self.debug = debug + + def send(self, dat: bytes) -> None: + self.tx_dat = dat + self.tx_len = len(dat) + self.tx_idx = 0 + self.tx_done = False + + if self.debug: print(f"ISO-TP: REQUEST - {hexlify(self.tx_dat)}") + self._tx_first_frame() + + def _tx_first_frame(self) -> None: + if self.tx_len < 8: + # single frame (send all bytes) + if self.debug: print("ISO-TP: TX - single frame") + msg = (bytes([self.tx_len]) + self.tx_dat).ljust(8, b"\x00") + self.tx_done = True + else: + # first frame (send first 6 bytes) + if self.debug: print("ISO-TP: TX - first frame") + msg = (struct.pack("!H", 0x1000 | self.tx_len) + self.tx_dat[:6]).ljust(8, b"\x00") + self.can_tx_queue.put(msg) + + def recv(self) -> bytes: + self.rx_dat = b"" + self.rx_len = 0 + self.rx_idx = 0 + self.rx_done = False + + try: + while True: + self._isotp_rx_next() + if self.tx_done and self.rx_done: + return self.rx_dat + except Empty: + raise MessageTimeoutError("timeout waiting for response") + finally: + if self.debug: print(f"ISO-TP: RESPONSE - {hexlify(self.rx_dat)}") + + def _isotp_rx_next(self) -> None: + rx_data = self.can_rx_queue.get(block=True, timeout=self.timeout) + + # single rx_frame + if rx_data[0] >> 4 == 0x0: + self.rx_len = rx_data[0] & 0xFF + self.rx_dat = rx_data[1:1+self.rx_len] + self.rx_idx = 0 + self.rx_done = True + if self.debug: print(f"ISO-TP: RX - single frame - idx={self.rx_idx} done={self.rx_done}") + return + + # first rx_frame + if rx_data[0] >> 4 == 0x1: + self.rx_len = ((rx_data[0] & 0x0F) << 8) + rx_data[1] + self.rx_dat = rx_data[2:] + self.rx_idx = 0 + self.rx_done = False + if self.debug: print(f"ISO-TP: RX - first frame - idx={self.rx_idx} done={self.rx_done}") + if self.debug: print(f"ISO-TP: TX - flow control continue") + # send flow control message (send all bytes) + msg = b"\x30\x00\x00".ljust(8, b"\x00") + self.can_tx_queue.put(msg) + return + + # consecutive rx frame + if rx_data[0] >> 4 == 0x2: + assert self.rx_done == False, "isotp - rx: consecutive frame with no active frame" + self.rx_idx += 1 + assert self.rx_idx & 0xF == rx_data[0] & 0xF, "isotp - rx: invalid consecutive frame index" + rx_size = self.rx_len - len(self.rx_dat) + self.rx_dat += rx_data[1:1+min(rx_size, 7)] + if self.rx_len == len(self.rx_dat): + self.rx_done = True + if self.debug: print(f"ISO-TP: RX - consecutive frame - idx={self.rx_idx} done={self.rx_done}") + return + + # flow control + if rx_data[0] >> 4 == 0x3: + assert self.tx_done == False, "isotp - rx: flow control with no active frame" + assert rx_data[0] != 0x32, "isotp - rx: flow-control overflow/abort" + assert rx_data[0] == 0x30 or rx_data[0] == 0x31, "isotp - rx: flow-control transfer state indicator invalid" + if rx_data[0] == 0x30: + if self.debug: print("ISO-TP: RX - flow control continue") + delay_ts = rx_data[2] & 0x7F + # scale is 1 milliseconds if first bit == 0, 100 micro seconds if first bit == 1 + delay_div = 1000. if rx_data[2] & 0x80 == 0 else 10000. + # first frame = 6 bytes, each consecutive frame = 7 bytes + start = 6 + self.tx_idx * 7 + count = rx_data[1] + end = start + count * 7 if count > 0 else self.tx_len + for i in range(start, end, 7): + if delay_ts > 0 and i > start: + delay_s = delay_ts / delay_div + if self.debug: print(f"ISO-TP: TX - delay - seconds={delay_s}") + time.sleep(delay_s) + self.tx_idx += 1 + # consecutive tx frames + msg = (bytes([0x20 | (self.tx_idx & 0xF)]) + self.tx_dat[i:i+7]).ljust(8, b"\x00") + self.can_tx_queue.put(msg) + if end >= self.tx_len: + self.tx_done = True + if self.debug: print(f"ISO-TP: TX - consecutive frame - idx={self.tx_idx} done={self.tx_done}") + elif rx_data[0] == 0x31: + # wait (do nothing until next flow control message) + if self.debug: print("ISO-TP: TX - flow control wait") + class UdsClient(): def __init__(self, panda, tx_addr: int, rx_addr: int=None, bus: int=0, timeout: int=10, debug: bool=False): self.panda = panda @@ -282,20 +393,17 @@ def __init__(self, panda, tx_addr: int, rx_addr: int=None, bus: int=0, timeout: else: raise ValueError("invalid tx_addr: {}".format(tx_addr)) - self.tx_queue = Queue() - self.rx_queue = Queue() + self.can_tx_queue = Queue() + self.can_rx_queue = Queue() self.timeout = timeout self.debug = debug - self.can_reader_t = Thread(target=self._isotp_thread, args=(self.debug,)) - self.can_reader_t.daemon = True - self.can_reader_t.start() + self.can_thread = Thread(target=self._can_thread, args=(self.debug,)) + self.can_thread.daemon = True + self.can_thread.start() - def _isotp_thread(self, debug: bool=False): + def _can_thread(self, debug: bool=False): try: - rx_frame = {"size": 0, "data": b"", "idx": 0, "done": True} - tx_frame = {"size": 0, "data": b"", "idx": 0, "done": True} - # allow all output self.panda.set_safety_mode(0x1337) # clear tx buffer @@ -304,96 +412,23 @@ def _isotp_thread(self, debug: bool=False): self.panda.can_clear(0xFFFF) while True: - messages = self.panda.can_recv() - for rx_addr, rx_ts, rx_data, rx_bus in messages: + # send + while not self.can_tx_queue.empty(): + msg = self.can_tx_queue.get(block=False) + if debug: print("CAN-TX: {} - {}".format(hex(self.tx_addr), hexlify(msg))) + self.panda.can_send(self.tx_addr, msg, self.bus) + + # receive + msgs = self.panda.can_recv() + for rx_addr, rx_ts, rx_data, rx_bus in msgs: if rx_bus != self.bus or rx_addr != self.rx_addr or len(rx_data) == 0: continue - - if (debug): print("R: {} {}".format(hex(rx_addr), hexlify(rx_data))) - if rx_data[0] >> 4 == 0x0: - # single rx_frame - rx_frame["size"] = rx_data[0] & 0xFF - rx_frame["data"] = rx_data[1:1+rx_frame["size"]] - rx_frame["idx"] = 0 - rx_frame["done"] = True - self.rx_queue.put(rx_frame["data"]) - elif rx_data[0] >> 4 == 0x1: - # first rx_frame - rx_frame["size"] = ((rx_data[0] & 0x0F) << 8) + rx_data[1] - rx_frame["data"] = rx_data[2:] - rx_frame["idx"] = 0 - rx_frame["done"] = False - # send flow control message (send all bytes) - msg = b"\x30\x00\x00".ljust(8, b"\x00") - if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) - self.panda.can_send(self.tx_addr, msg, self.bus) - elif rx_data[0] >> 4 == 0x2: - # consecutive rx frame - assert rx_frame["done"] == False, "rx: no active frame" - # validate frame index - rx_frame["idx"] += 1 - assert rx_frame["idx"] & 0xF == rx_data[0] & 0xF, "rx: invalid consecutive frame index" - rx_size = rx_frame["size"] - len(rx_frame["data"]) - rx_frame["data"] += rx_data[1:1+min(rx_size, 7)] - if rx_frame["size"] == len(rx_frame["data"]): - rx_frame["done"] = True - self.rx_queue.put(rx_frame["data"]) - elif rx_data[0] >> 4 == 0x3: - # flow control - if tx_frame["done"] != False: - tx_frame["done"] = True - self.rx_queue.put(b"\x7F\xFF\xFFtx: no active frame") - if rx_data[0] == 0x32: - # 0x32 = overflow/abort - tx_frame["done"] = True - self.rx_queue.put(b"\x7F\xFF\xFFtx: flow-control error - overflow/abort") - if rx_data[0] != 0x30 and rx_data[0] != 0x31: - # 0x30 = continue - # 0x31 = wait - tx_frame["done"] = True - self.rx_queue.put(b"\x7F\xFF\xFFtx: flow-control error - invalid transfer state indicator") - if rx_data[0] == 0x30: - delay_ts = rx_data[2] & 0x7F - # scale is 1 milliseconds if first bit == 0, 100 micro seconds if first bit == 1 - delay_div = 1000. if rx_data[2] & 0x80 == 0 else 10000. - # first frame = 6 bytes, each consecutive frame = 7 bytes - start = 6 + tx_frame["idx"] * 7 - count = rx_data[1] - end = start + count * 7 if count > 0 else tx_frame["size"] - for i in range(start, end, 7): - if delay_ts > 0 and i > start: - if (debug): print("D: {}".format(delay_ts / delay_div)) - time.sleep(delay_ts / delay_div) - tx_frame["idx"] += 1 - # consecutive tx frames - msg = (bytes([0x20 | (tx_frame["idx"] & 0xF)]) + tx_frame["data"][i:i+7]).ljust(8, b"\x00") - if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) - self.panda.can_send(self.tx_addr, msg, self.bus) - if end >= tx_frame["size"]: - tx_frame["done"] = True - - if not self.tx_queue.empty(): - req = self.tx_queue.get(block=False) - # reset rx and tx frames - rx_frame = {"size": 0, "data": b"", "idx": 0, "done": True} - tx_frame = {"size": len(req), "data": req, "idx": 0, "done": False} - if tx_frame["size"] < 8: - # single frame - tx_frame["done"] = True - msg = (bytes([tx_frame["size"]]) + tx_frame["data"]).ljust(8, b"\x00") - if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) - self.panda.can_send(self.tx_addr, msg, self.bus) - else: - # first rx_frame - tx_frame["done"] = False - msg = (struct.pack("!H", 0x1000 | tx_frame["size"]) + tx_frame["data"][:6]).ljust(8, b"\x00") - if (debug): print("S: {} {}".format(hex(self.tx_addr), hexlify(msg))) - self.panda.can_send(self.tx_addr, msg, self.bus) + if debug: print("CAN-RX: {} - {}".format(hex(self.rx_addr), hexlify(rx_data))) + self.can_rx_queue.put(rx_data) else: time.sleep(0.01) finally: self.panda.close() - self.rx_queue.put(None) # generic uds request def _uds_request(self, service_type: SERVICE_TYPE, subfunction: int=None, data: bytes=None) -> bytes: @@ -402,16 +437,12 @@ def _uds_request(self, service_type: SERVICE_TYPE, subfunction: int=None, data: req += bytes([subfunction]) if data is not None: req += data - self.tx_queue.put(req) + # send request, wait for response + isotp_msg = IsoTpMessage(self.can_tx_queue, self.can_rx_queue, self.timeout, self.debug) + isotp_msg.send(req) while True: - try: - resp = self.rx_queue.get(block=True, timeout=self.timeout) - except Empty: - raise MessageTimeoutError("timeout waiting for response") - if resp is None: - raise MessageTimeoutError("timeout waiting for response") - + resp = isotp_msg.recv() resp_sid = resp[0] if len(resp) > 0 else None # negative response @@ -428,24 +459,22 @@ def _uds_request(self, service_type: SERVICE_TYPE, subfunction: int=None, data: error_desc = resp[3:] # wait for another message if response pending if error_code == 0x78: - time.sleep(0.1) continue raise NegativeResponseError('{} - {}'.format(service_desc, error_desc), service_id, error_code) - break - # positive response - if service_type+0x40 != resp_sid: - resp_sid_hex = hex(resp_sid) if resp_sid is not None else None - raise InvalidServiceIdError('invalid response service id: {}'.format(resp_sid_hex)) + # positive response + if service_type+0x40 != resp_sid: + resp_sid_hex = hex(resp_sid) if resp_sid is not None else None + raise InvalidServiceIdError('invalid response service id: {}'.format(resp_sid_hex)) - if subfunction is not None: - resp_sfn = resp[1] if len(resp) > 1 else None - if subfunction != resp_sfn: - resp_sfn_hex = hex(resp_sfn) if resp_sfn is not None else None - raise InvalidSubFunctioneError('invalid response subfunction: {}'.format(hex(resp_sfn))) + if subfunction is not None: + resp_sfn = resp[1] if len(resp) > 1 else None + if subfunction != resp_sfn: + resp_sfn_hex = hex(resp_sfn) if resp_sfn is not None else None + raise InvalidSubFunctioneError('invalid response subfunction: {}'.format(hex(resp_sfn))) - # return data (exclude service id and sub-function id) - return resp[(1 if subfunction is None else 2):] + # return data (exclude service id and sub-function id) + return resp[(1 if subfunction is None else 2):] # services def diagnostic_session_control(self, session_type: SESSION_TYPE): From 711810d2f0aeb9504cc92d5e7e036bf9ad02c5bc Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Tue, 15 Oct 2019 12:14:09 -0700 Subject: [PATCH 30/31] more uds debug --- python/uds.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/uds.py b/python/uds.py index 2fdb68e667b42a..932cb229b82ab6 100644 --- a/python/uds.py +++ b/python/uds.py @@ -459,6 +459,7 @@ def _uds_request(self, service_type: SERVICE_TYPE, subfunction: int=None, data: error_desc = resp[3:] # wait for another message if response pending if error_code == 0x78: + if self.debug: print("UDS-RX: response pending") continue raise NegativeResponseError('{} - {}'.format(service_desc, error_desc), service_id, error_code) From 0f361999bd013aac95a167fb6176b9ddb24e10a7 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Tue, 15 Oct 2019 23:38:24 -0700 Subject: [PATCH 31/31] timeout is float --- python/uds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/uds.py b/python/uds.py index 932cb229b82ab6..11ae3d3cfca20f 100644 --- a/python/uds.py +++ b/python/uds.py @@ -379,7 +379,7 @@ def _isotp_rx_next(self) -> None: if self.debug: print("ISO-TP: TX - flow control wait") class UdsClient(): - def __init__(self, panda, tx_addr: int, rx_addr: int=None, bus: int=0, timeout: int=10, debug: bool=False): + def __init__(self, panda, tx_addr: int, rx_addr: int=None, bus: int=0, timeout: float=10, debug: bool=False): self.panda = panda self.bus = bus self.tx_addr = tx_addr