|
| 1 | +#!/usr/bin/env python |
| 2 | +import time |
| 3 | +import struct |
| 4 | +from enum import Enum |
| 5 | +import threading |
| 6 | + |
| 7 | +# class Services(Enum): |
| 8 | +# DiagnosticSessionControl = 0x10 |
| 9 | +# ECUReset = 0x11 |
| 10 | +# SecurityAccess = 0x27 |
| 11 | +# CommunicationControl = 0x28 |
| 12 | +# TesterPresent = 0x3E |
| 13 | +# AccessTimingParameter = 0x83 |
| 14 | +# SecuredDataTransmission = 0x84 |
| 15 | +# ControlDTCSetting = 0x85 |
| 16 | +# ResponseOnEvent = 0x86 |
| 17 | +# LinkControl = 0x87 |
| 18 | +# ReadDataByIdentifier = 0x22 |
| 19 | +# ReadMemoryByAddress = 0x23 |
| 20 | +# ReadScalingDataByIdentifier = 0x24 |
| 21 | +# ReadDataByPeriodicIdentifier = 0x2A |
| 22 | +# DynamicallyDefineDataIdentifier = 0x2C |
| 23 | +# WriteDataByIdentifier = 0x2E |
| 24 | +# WriteMemoryByAddress = 0x3D |
| 25 | +# ClearDiagnosticInformation = 0x14 |
| 26 | +# ReadDTCInformation = 0x19 |
| 27 | +# InputOutputControlByIdentifier = 0x2F |
| 28 | +# RoutineControl = 0x31 |
| 29 | +# RequestDownload = 0x34 |
| 30 | +# RequestUpload = 0x35 |
| 31 | +# TransferData = 0x36 |
| 32 | +# RequestTransferExit = 0x37 |
| 33 | + |
| 34 | +_negative_response_codes = { |
| 35 | + '\x00': 'positive response', |
| 36 | + '\x10': 'general reject', |
| 37 | + '\x11': 'service not supported', |
| 38 | + '\x12': 'sub-function not supported', |
| 39 | + '\x13': 'incorrect message length or invalid format', |
| 40 | + '\x14': 'response too long', |
| 41 | + '\x21': 'busy repeat request', |
| 42 | + '\x22': 'conditions not correct', |
| 43 | + '\x24': 'request sequence error', |
| 44 | + '\x25': 'no response from subnet component', |
| 45 | + '\x26': 'failure prevents execution of requested action', |
| 46 | + '\x31': 'request out of range', |
| 47 | + '\x33': 'security access denied', |
| 48 | + '\x35': 'invalid key', |
| 49 | + '\x36': 'exceed numebr of attempts', |
| 50 | + '\x37': 'required time delay not expired', |
| 51 | + '\x70': 'upload download not accepted', |
| 52 | + '\x71': 'transfer data suspended', |
| 53 | + '\x72': 'general programming failure', |
| 54 | + '\x73': 'wrong block sequence counter', |
| 55 | + '\x78': 'request correctly received - response pending', |
| 56 | + '\x7e': 'sub-function not supported in active session', |
| 57 | + '\x7f': 'service not supported in active session', |
| 58 | + '\x81': 'rpm too high', |
| 59 | + '\x82': 'rpm too low', |
| 60 | + '\x83': 'engine is running', |
| 61 | + '\x84': 'engine is not running', |
| 62 | + '\x85': 'engine run time too low', |
| 63 | + '\x86': 'temperature too high', |
| 64 | + '\x87': 'temperature too low', |
| 65 | + '\x88': 'vehicle speed too high', |
| 66 | + '\x89': 'vehicle speed too low', |
| 67 | + '\x8a': 'throttle/pedal too high', |
| 68 | + '\x8b': 'throttle/pedal too low', |
| 69 | + '\x8c': 'transmission not in neutral', |
| 70 | + '\x8d': 'transmission not in gear', |
| 71 | + '\x8f': 'brake switch(es) not closed', |
| 72 | + '\x90': 'shifter lever not in park', |
| 73 | + '\x91': 'torque converter clutch locked', |
| 74 | + '\x92': 'voltage too high', |
| 75 | + '\x93': 'voltage too low', |
| 76 | +} |
| 77 | + |
| 78 | +# generic uds request |
| 79 | +def _request(address, service, subfunction, data=None): |
| 80 | + # TODO: send request |
| 81 | + # TODO: wait for response |
| 82 | + |
| 83 | + # raise exception on error |
| 84 | + if resp[0] == '\x7f' |
| 85 | + error_code = resp[2] |
| 86 | + error_desc = _negative_response_codes[error_code] |
| 87 | + raise Exception('[{}] {}'.format(hex(ord(error_code)), error_desc)) |
| 88 | + |
| 89 | + resp_sid = ord(resp[0]) if len(resp) > 0 else None |
| 90 | + if service != resp_sid + 0x40: |
| 91 | + resp_sid_hex = hex(resp_sid) if resp_sid is not None else None |
| 92 | + raise Exception('invalid response service id: {}'.format(resp_sid_hex)) |
| 93 | + |
| 94 | + if subfunction is None: |
| 95 | + resp_subf = ord(resp[1]) if len(resp) > 1 else None |
| 96 | + if subfunction != resp_subf: |
| 97 | + resp_subf_hex = hex(resp_subf) if resp_subf is not None else None |
| 98 | + raise Exception('invalid response subfunction: {}'.format(hex(resp_subf))) |
| 99 | + |
| 100 | + # return data (exclude service id and sub-function id) |
| 101 | + return resp[(1 if subfunction is None else 2):] |
| 102 | + |
| 103 | +# services |
| 104 | +class DIAGNOSTIC_SESSION_CONTROL_TYPE(Enum): |
| 105 | + DEFAULT = 1 |
| 106 | + PROGRAMMING = 2 |
| 107 | + EXTENDED_DIAGNOSTIC = 3 |
| 108 | + SAFETY_SYSTEM_DIAGNOSTIC = 4 |
| 109 | + |
| 110 | +def diagnostic_session_control(address, session_type): |
| 111 | + _request(address, service=0x10, subfunction=session_type) |
| 112 | + |
| 113 | +class ECU_RESET_TYPE(Enum): |
| 114 | + HARD = 1 |
| 115 | + KEY_OFF_ON = 2 |
| 116 | + SOFT = 3 |
| 117 | + ENABLE_RAPID_POWER_SHUTDOWN = 4 |
| 118 | + DISABLE_RAPID_POWER_SHUTDOWN = 5 |
| 119 | + |
| 120 | +def ecu_reset(address, reset_type): |
| 121 | + resp = _request(address, service=0x11, subfunction=reset_type) |
| 122 | + power_down_time = None |
| 123 | + if reset_type == RESET_TYPE.ENABLE_RAPID_POWER_SHUTDOWN |
| 124 | + power_down_time = ord(resp[0]) |
| 125 | + return {"power_down_time": power_down_time} |
| 126 | + |
| 127 | +class SECURITY_ACCESS_TYPE(Enum): |
| 128 | + REQUEST_SEED = 1 |
| 129 | + SEND_KEY = 2 |
| 130 | + |
| 131 | +def security_access(address, access_type, security_key=None): |
| 132 | + request_seed = access_type % 2 == 0 |
| 133 | + if request_seed and security_key is not None: |
| 134 | + raise ValueError('security_key not allowed') |
| 135 | + if not request_seed and security_key is None: |
| 136 | + raise ValueError('security_key is missing') |
| 137 | + resp = _request(address, service=0x27, subfunction=access_type, data=security_key) |
| 138 | + if request_seed: |
| 139 | + return {"security_seed": resp} |
| 140 | + |
| 141 | +class COMMUNICATION_CONTROL_TYPE(Enum): |
| 142 | + ENABLE_RX_ENABLE_TX = 0 |
| 143 | + ENABLE_RX_DISABLE_TX = 1 |
| 144 | + DISABLE_RX_ENABLE_TX = 2 |
| 145 | + DISABLE_RX_DISABLE_TX = 3 |
| 146 | + |
| 147 | +class COMMUNICATION_CONTROL_MESSAGE_TYPE(Enum): |
| 148 | + NORMAL = 1 |
| 149 | + NETWORK_MANAGEMENT = 2 |
| 150 | + NORMAL_AND_NETWORK_MANAGEMENT = 3 |
| 151 | + |
| 152 | +def communication_control(address, control_type, message_type): |
| 153 | + data = chr(message_type) |
| 154 | + _request(address, service=0x28, subfunction=control_type, data=data) |
| 155 | + |
| 156 | +def tester_present(address): |
| 157 | + _request(address, service=0x3E, subfunction=0x00) |
| 158 | + |
| 159 | +class ACCESS_TIMING_PARAMETER_TYPE(Enum): |
| 160 | + READ_EXTENDED_SET = 1 |
| 161 | + SET_TO_DEFAULT_VALUES = 2 |
| 162 | + READ_CURRENTLY_ACTIVE = 3 |
| 163 | + SET_TO_GIVEN_VALUES = 4 |
| 164 | + |
| 165 | +def access_timing_parameter(address, parameter_type, parameter_values): |
| 166 | + write_custom_values = parameter_type == ACCESS_TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES |
| 167 | + read_values = ( |
| 168 | + parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or |
| 169 | + parameter_type == ACCESS_TIMING_PARAMETER_TYPE.READ_EXTENDED_SET |
| 170 | + ) |
| 171 | + if not write_custom_values and parameter_values is not None: |
| 172 | + raise ValueError('parameter_values not allowed') |
| 173 | + if write_custom_values and parameter_values is None: |
| 174 | + raise ValueError('parameter_values is missing') |
| 175 | + resp = _request(address, service=0x83, subfunction=parameter_type, data=parameter_values) |
| 176 | + if read_values: |
| 177 | + # TODO: parse response into values? |
| 178 | + return {"parameter_values": resp} |
| 179 | + |
| 180 | +def secured_data_transmission(address, data): |
| 181 | + # TODO: split data into multiple input parameters? |
| 182 | + resp = _request(address, service=0x84, subfunction=None, data=data) |
| 183 | + # TODO: parse response into multiple output values? |
| 184 | + return {"data"=resp} |
| 185 | + |
| 186 | +class CONTROL_DTC_SETTING_TYPE(Enum): |
| 187 | + ON = 1 |
| 188 | + OFF = 2 |
| 189 | + |
| 190 | +def control_dtc_setting(address, setting_type): |
| 191 | + _request(address, service=0x85, subfunction=setting_type) |
| 192 | + |
| 193 | +class RESPONSE_ON_EVENT_TYPE(Enum): |
| 194 | + STOP_RESPONSE_ON_EVENT = 0 |
| 195 | + ON_DTC_STATUS_CHANGE = 1 |
| 196 | + ON_TIMER_INTERRUPT = 2 |
| 197 | + ON_CHANGE_OF_DATA_IDENTIFIER = 3 |
| 198 | + REPORT_ACTIVATED_EVENTS = 4 |
| 199 | + START_RESPONSE_ON_EVENT = 5 |
| 200 | + CLEAR_RESPONSE_ON_EVENT = 6 |
| 201 | + ON_COMPARISON_OF_VALUES = 7 |
| 202 | + |
| 203 | +def response_on_event(address, event_type, store_event, window_time, event_type_record, service_response_record): |
| 204 | + if store_event: |
| 205 | + event_type |= 0x20 |
| 206 | + # TODO: split record parameters into arrays |
| 207 | + data = char(window_time) + event_type_record + service_response_record |
| 208 | + resp = _request(address, service=0x86, subfunction=event_type, data=data) |
| 209 | + # TODO: parse the reset of response |
| 210 | + |
| 211 | +class LINK_CONTROL_TYPE(Enum): |
| 212 | + VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE = 1 |
| 213 | + VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE = 2 |
| 214 | + TRANSITION_BAUDRATE = 3 |
| 215 | + |
| 216 | +class LINK_CONTROL_BAUD_RATE(Enum): |
| 217 | + PC9600 = 1 |
| 218 | + PC19200 = 2 |
| 219 | + PC38400 = 3 |
| 220 | + PC57600 = 4 |
| 221 | + PC115200 = 5 |
| 222 | + CAN125000 = 16 |
| 223 | + CAN250000 = 17 |
| 224 | + CAN500000 = 18 |
| 225 | + CAN1000000 = 19 |
| 226 | + |
| 227 | +def link_control(address, control_type, baud_rate=None): |
| 228 | + if LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: |
| 229 | + # baud_rate = LINK_CONTROL_BAUD_RATE |
| 230 | + data = chr(baud_rate) |
| 231 | + elif LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE: |
| 232 | + # baud_rate = custom value (3 bytes big-endian) |
| 233 | + data = struct.pack('!I', baud_rate)[1:] |
| 234 | + else: |
| 235 | + data = None |
| 236 | + _request(address, service=0x87, subfunction=control_type, data=data) |
| 237 | + |
| 238 | +class DATA_IDENTIFIER(Enum): |
| 239 | + BOOT_SOFTWARE_IDENTIFICATION = 0XF180 |
| 240 | + APPLICATION_SOFTWARE_IDENTIFICATION = 0XF181 |
| 241 | + APPLICATION_DATA_IDENTIFICATION = 0XF182 |
| 242 | + BOOT_SOFTWARE_FINGERPRINT = 0XF183 |
| 243 | + APPLICATION_SOFTWARE_FINGERPRINT = 0XF184 |
| 244 | + APPLICATION_DATA_FINGERPRINT = 0XF185 |
| 245 | + ACTIVE_DIAGNOSTIC_SESSION = 0XF186 |
| 246 | + VEHICLE_MANUFACTURER_SPARE_PART_NUMBER = 0XF187 |
| 247 | + VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER = 0XF188 |
| 248 | + VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER = 0XF189 |
| 249 | + SYSTEM_SUPPLIER_IDENTIFIER = 0XF18A |
| 250 | + ECU_MANUFACTURING_DATE = 0XF18B |
| 251 | + ECU_SERIAL_NUMBER = 0XF18C |
| 252 | + SUPPORTED_FUNCTIONAL_UNITS = 0XF18D |
| 253 | + VEHICLE_MANUFACTURER_KIT_ASSEMBLY_PART_NUMBER = 0XF18E |
| 254 | + VIN = 0XF190 |
| 255 | + VEHICLE_MANUFACTURER_ECU_HARDWARE_NUMBER = 0XF191 |
| 256 | + SYSTEM_SUPPLIER_ECU_HARDWARE_NUMBER = 0XF192 |
| 257 | + SYSTEM_SUPPLIER_ECU_HARDWARE_VERSION_NUMBER = 0XF193 |
| 258 | + SYSTEM_SUPPLIER_ECU_SOFTWARE_NUMBER = 0XF194 |
| 259 | + SYSTEM_SUPPLIER_ECU_SOFTWARE_VERSION_NUMBER = 0XF195 |
| 260 | + EXHAUST_REGULATION_OR_TYPE_APPROVAL_NUMBER = 0XF196 |
| 261 | + SYSTEM_NAME_OR_ENGINE_TYPE = 0XF197 |
| 262 | + REPAIR_SHOP_CODE_OR_TESTER_SERIAL_NUMBER = 0XF198 |
| 263 | + PROGRAMMING_DATE = 0XF199 |
| 264 | + CALIBRATION_REPAIR_SHOP_CODE_OR_CALIBRATION_EQUIPMENT_SERIAL_NUMBER = 0XF19A |
| 265 | + CALIBRATION_DATE = 0XF19B |
| 266 | + CALIBRATION_EQUIPMENT_SOFTWARE_NUMBER = 0XF19C |
| 267 | + ECU_INSTALLATION_DATE = 0XF19D |
| 268 | + ODX_FILE = 0XF19E |
| 269 | + ENTITY = 0XF19F |
| 270 | + |
| 271 | +def read_data_by_identifier(address, data_identifier): |
| 272 | + # TODO: support list of identifiers |
| 273 | + data = struct.pack('!H', data_id) |
| 274 | + resp = _request(address, service=0x22, subfunction=None, data=data) |
| 275 | + resp_id = struct.unpack('!H', data[0:2])[0] if len(data) >= 2 else None |
| 276 | + if resp_id != data_id: |
| 277 | + raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) |
| 278 | + return resp[2:] |
| 279 | + |
| 280 | +def read_memory_by_address(address, memory_address, memory_size, memory_address_bytes=4, memory_size_bytes=4): |
| 281 | + if memory_address_bytes < 1 or memory_address_bytes > 4: |
| 282 | + raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) |
| 283 | + if memory_size_bytes < 1 or memory_size_bytes > 4: |
| 284 | + raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) |
| 285 | + data = struct.pack('!BB', memory_size_bytes, memory_address_bytes) |
| 286 | + |
| 287 | + if memory_address >= 1<<(memory_address_bytes*8) |
| 288 | + raise ValueError('invalid memory_address: {}'.format(memory_address)) |
| 289 | + data += struct.pack('!I', memory_address)[4-memory_address_bytes:] |
| 290 | + if memory_size >= 1<<(memory_size_bytes*8) |
| 291 | + raise ValueError('invalid memory_size: {}'.format(memory_address)) |
| 292 | + data += struct.pack('!I', memory_size)[4-memory_size_bytes:] |
| 293 | + |
| 294 | + resp = _request(address, service=0x23, subfunction=None, data=data) |
| 295 | + return resp |
| 296 | + |
| 297 | +def read_scaling_data_by_identifier(address): |
| 298 | + raise NotImplementedError() |
| 299 | + _request(address, service=0x24, subfunction=0x00) |
| 300 | + |
| 301 | +def read_data_by_periodic_identifier(address): |
| 302 | + raise NotImplementedError() |
| 303 | + _request(address, service=0x2A, subfunction=0x00) |
| 304 | + |
| 305 | +def dynamically_define_data_identifier(address): |
| 306 | + raise NotImplementedError() |
| 307 | + _request(address, service=0x2C, subfunction=0x00) |
| 308 | + |
| 309 | +def write_data_by_identifier(address): |
| 310 | + raise NotImplementedError() |
| 311 | + _request(address, service=0x2E, subfunction=0x00) |
| 312 | + |
| 313 | +def write_memory_by_address(address): |
| 314 | + raise NotImplementedError() |
| 315 | + _request(address, service=0x3D, subfunction=0x00) |
| 316 | + |
| 317 | +def clear_diagnostic_information(address): |
| 318 | + raise NotImplementedError() |
| 319 | + _request(address, service=0x14, subfunction=0x00) |
| 320 | + |
| 321 | +def read_dtc_information(address): |
| 322 | + raise NotImplementedError() |
| 323 | + _request(address, service=0x19, subfunction=0x00) |
| 324 | + |
| 325 | +class INPUT_OUTPUT_CONTROL_PARAMETER(Enum): |
| 326 | + RETURN_CONTROL_TO_ECU = 0 |
| 327 | + RESET_TO_DEFAULT = 1 |
| 328 | + FREEZE_CURRENT_STATE = 2 |
| 329 | + SHORT_TERM_ADJUSTMENT = 3 |
| 330 | + |
| 331 | +def input_output_control_by_identifier(address): |
| 332 | + raise NotImplementedError() |
| 333 | + _request(address, service=0x2F, subfunction=0x00) |
| 334 | + |
| 335 | +class ROUTINE_CONTROL_TYPE(Enum): |
| 336 | + ERASE_MEMORY = 0xFF00 |
| 337 | + CHECK_PROGRAMMING_DEPENDENCIES = 0xFF01 |
| 338 | + ERASE_MIRROR_MEMORY_DTCS = 0xFF02 |
| 339 | + |
| 340 | +def routine_control(address): |
| 341 | + raise NotImplementedError() |
| 342 | + _request(address, service=0x31, subfunction=0x00) |
| 343 | + |
| 344 | +def request_download(address): |
| 345 | + raise NotImplementedError() |
| 346 | + _request(address, service=0x34, subfunction=0x00) |
| 347 | + |
| 348 | +def request_upload(address): |
| 349 | + raise NotImplementedError() |
| 350 | + _request(address, service=0x35, subfunction=0x00) |
| 351 | + |
| 352 | +def transfer_data(address): |
| 353 | + raise NotImplementedError() |
| 354 | + _request(address, service=0x36, subfunction=0x00) |
| 355 | + |
| 356 | +def request_transfer_exit(address) |
| 357 | + raise NotImplementedError() |
| 358 | + _request(address, service=0x37, subfunction=0x00) |
| 359 | + |
| 360 | +if __name__ == "__main__": |
| 361 | + from . import uds |
| 362 | + # examples |
| 363 | + vin = uds.read_data_by_identifier(0x18da10f1, uds.DATA_IDENTIFIER.VIN) |
0 commit comments