def test_severity_behaviour(self): dtc = Dtc(1) self.assertEqual(dtc.severity.get_byte_as_int(), 0x00) dtc.severity.maintenance_only = True self.assertEqual(dtc.severity.get_byte_as_int(), 0x20) dtc.severity.check_at_next_exit = True self.assertEqual(dtc.severity.get_byte_as_int(), 0x60) dtc.severity.check_immediately = True self.assertEqual(dtc.severity.get_byte_as_int(), 0xE0) dtc.severity.set_byte(0x20) self.assertEqual(dtc.severity.maintenance_only, True) self.assertEqual(dtc.severity.check_at_next_exit, False) self.assertEqual(dtc.severity.check_immediately, False) dtc.severity.set_byte(0x60) self.assertEqual(dtc.severity.maintenance_only, True) self.assertEqual(dtc.severity.check_at_next_exit, True) self.assertEqual(dtc.severity.check_immediately, False) dtc.severity.set_byte(0xE0) self.assertEqual(dtc.severity.maintenance_only, True) self.assertEqual(dtc.severity.check_at_next_exit, True) self.assertEqual(dtc.severity.check_immediately, True)
def test_init(self): dtc = Dtc(0x1234) self.assertEqual(dtc.id, 0x1234) self.assertEqual(dtc.status.get_byte(), b'\x00') self.assertEqual(dtc.status.get_byte_as_int(), 0x00) self.assertEqual(dtc.severity.get_byte(), b'\x00') self.assertEqual(dtc.severity.get_byte_as_int(), 0x00) self.assertEqual(dtc.status.test_failed, False) self.assertEqual(dtc.status.test_failed_this_operation_cycle, False) self.assertEqual(dtc.status.pending, False) self.assertEqual(dtc.status.confirmed, False) self.assertEqual(dtc.status.test_not_completed_since_last_clear, False) self.assertEqual(dtc.status.test_failed_since_last_clear, False) self.assertEqual(dtc.status.test_not_completed_this_operation_cycle, False) self.assertEqual(dtc.status.warning_indicator_requested, False) with self.assertRaises(TypeError): Dtc()
def read_dtcs( flash_info: constants.FlashInfo, interface="CAN", callback=None, interface_path=None, status_mask=None, ): if status_mask is None: # Try to filter to reasonable failures only. status_mask = Dtc.Status( test_failed=True, test_failed_this_operation_cycle=True, pending=False, confirmed=True, test_not_completed_since_last_clear=False, test_failed_since_last_clear=True, test_not_completed_this_operation_cycle=False, warning_indicator_requested=True, ) conn = connection_setup( interface=interface, rxid=flash_info.control_module_identifier.rxid, txid=flash_info.control_module_identifier.txid, interface_path=interface_path, ) with Client(conn, request_timeout=5, config=configs.default_client_config) as client: if callback: callback( flasher_step="READING", flasher_status="Connected", flasher_progress=50, ) client.change_session(services.DiagnosticSessionControl.Session. extendedDiagnosticSession) response = client.get_dtc_by_status_mask(status_mask.get_byte_as_int()) if callback: callback( flasher_step="DONE", flasher_status="Read " + str(len(response.service_data.dtcs)) + " DTCs", flasher_progress=100, ) return dtc_handler.dtcs_to_human(response.service_data.dtcs)
def test_str_repr(self): dtc = Dtc(0x123456) dtc.status.pending = True str(dtc) dtc.__repr__()
def test_set_severity_with_byte_no_error(self): dtc = Dtc(1) for i in range(255): dtc.severity.set_byte(i)
def test_status_behaviour(self): dtc = Dtc(1) self.assertEqual(dtc.status.get_byte(), b'\x00') dtc.status.test_failed = True self.assertEqual(dtc.status.get_byte(), b'\x01') dtc.status.test_failed_this_operation_cycle = True self.assertEqual(dtc.status.get_byte(), b'\x03') dtc.status.pending = True self.assertEqual(dtc.status.get_byte(), b'\x07') dtc.status.confirmed = True self.assertEqual(dtc.status.get_byte(), b'\x0F') dtc.status.test_not_completed_since_last_clear = True self.assertEqual(dtc.status.get_byte(), b'\x1F') dtc.status.test_failed_since_last_clear = True self.assertEqual(dtc.status.get_byte(), b'\x3F') dtc.status.test_not_completed_this_operation_cycle = True self.assertEqual(dtc.status.get_byte(), b'\x7F') dtc.status.warning_indicator_requested = True self.assertEqual(dtc.status.get_byte(), b'\xFF') dtc.status.set_byte(0x01) self.assertEqual(dtc.status.test_failed, True) self.assertEqual(dtc.status.test_failed_this_operation_cycle, False) self.assertEqual(dtc.status.pending, False) self.assertEqual(dtc.status.confirmed, False) self.assertEqual(dtc.status.test_not_completed_since_last_clear, False) self.assertEqual(dtc.status.test_failed_since_last_clear, False) self.assertEqual(dtc.status.test_not_completed_this_operation_cycle, False) self.assertEqual(dtc.status.warning_indicator_requested, False) dtc.status.set_byte(0x03) self.assertEqual(dtc.status.test_failed, True) self.assertEqual(dtc.status.test_failed_this_operation_cycle, True) self.assertEqual(dtc.status.pending, False) self.assertEqual(dtc.status.confirmed, False) self.assertEqual(dtc.status.test_not_completed_since_last_clear, False) self.assertEqual(dtc.status.test_failed_since_last_clear, False) self.assertEqual(dtc.status.test_not_completed_this_operation_cycle, False) self.assertEqual(dtc.status.warning_indicator_requested, False) dtc.status.set_byte(0x07) self.assertEqual(dtc.status.test_failed, True) self.assertEqual(dtc.status.test_failed_this_operation_cycle, True) self.assertEqual(dtc.status.pending, True) self.assertEqual(dtc.status.confirmed, False) self.assertEqual(dtc.status.test_not_completed_since_last_clear, False) self.assertEqual(dtc.status.test_failed_since_last_clear, False) self.assertEqual(dtc.status.test_not_completed_this_operation_cycle, False) self.assertEqual(dtc.status.warning_indicator_requested, False) dtc.status.set_byte(0x0F) self.assertEqual(dtc.status.test_failed, True) self.assertEqual(dtc.status.test_failed_this_operation_cycle, True) self.assertEqual(dtc.status.pending, True) self.assertEqual(dtc.status.confirmed, True) self.assertEqual(dtc.status.test_not_completed_since_last_clear, False) self.assertEqual(dtc.status.test_failed_since_last_clear, False) self.assertEqual(dtc.status.test_not_completed_this_operation_cycle, False) self.assertEqual(dtc.status.warning_indicator_requested, False) dtc.status.set_byte(0x1F) self.assertEqual(dtc.status.test_failed, True) self.assertEqual(dtc.status.test_failed_this_operation_cycle, True) self.assertEqual(dtc.status.pending, True) self.assertEqual(dtc.status.confirmed, True) self.assertEqual(dtc.status.test_not_completed_since_last_clear, True) self.assertEqual(dtc.status.test_failed_since_last_clear, False) self.assertEqual(dtc.status.test_not_completed_this_operation_cycle, False) self.assertEqual(dtc.status.warning_indicator_requested, False) dtc.status.set_byte(0x3F) self.assertEqual(dtc.status.test_failed, True) self.assertEqual(dtc.status.test_failed_this_operation_cycle, True) self.assertEqual(dtc.status.pending, True) self.assertEqual(dtc.status.confirmed, True) self.assertEqual(dtc.status.test_not_completed_since_last_clear, True) self.assertEqual(dtc.status.test_failed_since_last_clear, True) self.assertEqual(dtc.status.test_not_completed_this_operation_cycle, False) self.assertEqual(dtc.status.warning_indicator_requested, False) dtc.status.set_byte(0x7F) self.assertEqual(dtc.status.test_failed, True) self.assertEqual(dtc.status.test_failed_this_operation_cycle, True) self.assertEqual(dtc.status.pending, True) self.assertEqual(dtc.status.confirmed, True) self.assertEqual(dtc.status.test_not_completed_since_last_clear, True) self.assertEqual(dtc.status.test_failed_since_last_clear, True) self.assertEqual(dtc.status.test_not_completed_this_operation_cycle, True) self.assertEqual(dtc.status.warning_indicator_requested, False) dtc.status.set_byte(0xFF) self.assertEqual(dtc.status.test_failed, True) self.assertEqual(dtc.status.test_failed_this_operation_cycle, True) self.assertEqual(dtc.status.pending, True) self.assertEqual(dtc.status.confirmed, True) self.assertEqual(dtc.status.test_not_completed_since_last_clear, True) self.assertEqual(dtc.status.test_failed_since_last_clear, True) self.assertEqual(dtc.status.test_not_completed_this_operation_cycle, True) self.assertEqual(dtc.status.warning_indicator_requested, True)
def test_set_status_with_byte_no_error(self): dtc = Dtc(1) for i in range(255): dtc.status.set_byte(i)
def interpret_response(cls, response, subfunction, extended_data_size=None, tolerate_zero_padding=True, ignore_all_zero_dtc=True, dtc_snapshot_did_size=2, didconfig=None): u""" Populates the response ``service_data`` property with an instance of :class:`ReadDTCInformation.ResponseData<udsoncan.services.ReadDTCInformation.ResponseData>` :param response: The received response to interpret :type response: :ref:`Response<Response>` :param subfunction: The service subfunction. Values are defined in :class:`ReadDTCInformation.Subfunction<udsoncan.services.ReadDTCInformation.Subfunction>` :type subfunction: int :param extended_data_size: Extended data size to expect. Extended data is implementation specific, therefore, size is not standardized :type extended_data_size: int :param tolerate_zero_padding: Ignore trailing zeros in the response data avoiding raising false :class:`InvalidResponseException<udsoncan.exceptions.InvalidResponseException>`. :type tolerate_zero_padding: bool :param ignore_all_zero_dtc: Discard any DTC entries that have an ID of 0. Avoid reading extra DTCs when using a transport protocol using zero padding. :type ignore_all_zero_dtc: bool :param dtc_snapshot_did_size: Number of bytes to encode the data identifier number. Other services such as :ref:`ReadDataByIdentifier<ReadDataByIdentifier>` encode DID over 2 bytes. UDS standard does not define the size of the snapshot DID, therefore, it must be supplied. :type dtc_snapshot_did_size: int :param didconfig: Definition of DID codecs. Dictionary mapping a DID (int) to a valid :ref:`DidCodec<DidCodec>` class or pack/unpack string :type didconfig: dict[int] = :ref:`DidCodec<DidCodec>` :raises InvalidResponseException: If response length is wrong or does not match DID configuration :raises ValueError: If parameters are out of range, missing or wrong types :raises ConfigError: If the server returns a snapshot DID not defined in ``didconfig`` """ from udsoncan import Dtc, DidCodec ServiceHelper.validate_int(subfunction, min=1, max=0x15, name=u'Subfunction') # Response grouping for responses that are encoded the same way response_subfn_dtc_availability_mask_plus_dtc_record = [ ReadDTCInformation.Subfunction.reportDTCByStatusMask, ReadDTCInformation.Subfunction.reportSupportedDTCs, ReadDTCInformation.Subfunction.reportFirstTestFailedDTC, ReadDTCInformation.Subfunction.reportFirstConfirmedDTC, ReadDTCInformation.Subfunction.reportMostRecentTestFailedDTC, ReadDTCInformation.Subfunction.reportMostRecentConfirmedDTC, ReadDTCInformation.Subfunction.reportMirrorMemoryDTCByStatusMask, ReadDTCInformation.Subfunction. reportEmissionsRelatedOBDDTCByStatusMask, ReadDTCInformation.Subfunction.reportDTCWithPermanentStatus ] response_subfn_number_of_dtc = [ ReadDTCInformation.Subfunction.reportNumberOfDTCByStatusMask, ReadDTCInformation.Subfunction. reportNumberOfDTCBySeverityMaskRecord, ReadDTCInformation.Subfunction. reportNumberOfMirrorMemoryDTCByStatusMask, ReadDTCInformation.Subfunction. reportNumberOfEmissionsRelatedOBDDTCByStatusMask, ] response_subfn_dtc_availability_mask_plus_dtc_record_with_severity = [ ReadDTCInformation.Subfunction.reportDTCBySeverityMaskRecord, ReadDTCInformation.Subfunction.reportSeverityInformationOfDTC ] response_subfn_dtc_plus_fault_counter = [ ReadDTCInformation.Subfunction.reportDTCFaultDetectionCounter ] response_subfn_dtc_plus_sapshot_record = [ ReadDTCInformation.Subfunction.reportDTCSnapshotIdentification ] response_sbfn_dtc_status_snapshots_records = [ ReadDTCInformation.Subfunction.reportDTCSnapshotRecordByDTCNumber ] response_sbfn_dtc_status_snapshots_records_record_first = [ ReadDTCInformation.Subfunction. reportDTCSnapshotRecordByRecordNumber ] response_subfn_mask_record_plus_extdata = [ ReadDTCInformation.Subfunction. reportDTCExtendedDataRecordByDTCNumber, ReadDTCInformation. Subfunction.reportMirrorMemoryDTCExtendedDataRecordByDTCNumber ] response.service_data = cls.ResponseData() # what will be returned if len(response.data) < 1: raise InvalidResponseException( response, u'Response must be at least 1 byte long (echo of subfunction)') response.service_data.subfunction_echo = ord( response.data[0]) # First byte is subfunction # Now for each response group, we have a different decoding algorithm if subfunction in response_subfn_dtc_availability_mask_plus_dtc_record + response_subfn_dtc_availability_mask_plus_dtc_record_with_severity: if subfunction in response_subfn_dtc_availability_mask_plus_dtc_record: dtc_size = 4 # DTC ID (3) + Status (1) elif subfunction in response_subfn_dtc_availability_mask_plus_dtc_record_with_severity: dtc_size = 6 # DTC ID (3) + Status (1) + Severity (1) + FunctionalUnit (1) if len(response.data) < 2: raise InvalidResponseException( response, u'Response must be at least 2 byte long (echo of subfunction and DTCStatusAvailabilityMask)' ) response.service_data.status_availability = Dtc.Status.from_byte( ord(response.data[1])) actual_byte = 2 # Increasing index while True: # Loop until we have read all dtcs if len(response.data) <= actual_byte: break # done elif len(response.data) < actual_byte + dtc_size: partial_dtc_length = len(response.data) - actual_byte if tolerate_zero_padding and response.data[ actual_byte:] == '\x00' * partial_dtc_length: break else: # We purposely ignore extra byte for subfunction reportSeverityInformationOfDTC as it is supposed to return 0 or 1 DTC. if subfunction != ReadDTCInformation.Subfunction.reportSeverityInformationOfDTC or actual_byte == 2: raise InvalidResponseException( response, u'Incomplete DTC record. Missing %d bytes to response to complete the record' % (dtc_size - partial_dtc_length)) else: dtc_bytes = response.data[actual_byte:actual_byte + dtc_size] if dtc_bytes == '\x00' * dtc_size and ignore_all_zero_dtc: pass # ignore else: if subfunction in response_subfn_dtc_availability_mask_plus_dtc_record: dtc = Dtc( struct.unpack(u'>L', '\x00' + dtc_bytes[0:3])[0]) dtc.status.set_byte(ord(dtc_bytes[3])) elif subfunction in response_subfn_dtc_availability_mask_plus_dtc_record_with_severity: dtc = Dtc( struct.unpack(u'>L', '\x00' + dtc_bytes[2:5])[0]) dtc.severity.set_byte(ord(dtc_bytes[0])) dtc.functional_unit = ord(dtc_bytes[1]) dtc.status.set_byte(ord(dtc_bytes[5])) response.service_data.dtcs.append(dtc) actual_byte += dtc_size response.service_data.dtc_count = len(response.service_data.dtcs) # The 2 following subfunction responses have different purposes but their constructions are very similar. elif subfunction in response_subfn_dtc_plus_fault_counter + response_subfn_dtc_plus_sapshot_record: dtc_size = 4 if len(response.data) < 1: raise InvalidResponseException( response, u'Response must be at least 1 byte long (echo of subfunction)' ) actual_byte = 1 # Increasing index dtc_map = dict( ) # This map is used to append snapshot to existing DTC. while True: # Loop until we have read all dtcs if len(response.data) <= actual_byte: break # done elif len(response.data) < actual_byte + dtc_size: partial_dtc_length = len(response.data) - actual_byte if tolerate_zero_padding and response.data[ actual_byte:] == '\x00' * partial_dtc_length: break else: raise InvalidResponseException( response, u'Incomplete DTC record. Missing %d bytes to response to complete the record' % (dtc_size - partial_dtc_length)) else: dtc_bytes = response.data[actual_byte:actual_byte + dtc_size] if dtc_bytes == '\x00' * dtc_size and ignore_all_zero_dtc: pass # ignore else: dtcid = struct.unpack(u'>L', '\x00' + dtc_bytes[0:3])[0] # We create the DTC or get its reference if already created. dtc_created = False if dtcid in dtc_map and subfunction in response_subfn_dtc_plus_sapshot_record: dtc = dtc_map[dtcid] else: dtc = Dtc(dtcid) dtc_map[dtcid] = dtc dtc_created = True # We either read the DTC fault counter or Snapshot record number. if subfunction in response_subfn_dtc_plus_fault_counter: dtc.fault_counter = ord(dtc_bytes[3]) elif subfunction in response_subfn_dtc_plus_sapshot_record: record_number = ord(dtc_bytes[3]) if dtc.snapshots is None: dtc.snapshots = [] dtc.snapshots.append(record_number) # Adds the DTC to the list. if dtc_created: response.service_data.dtcs.append(dtc) actual_byte += dtc_size response.service_data.dtc_count = len(response.service_data.dtcs) # This group of responses returns a number of DTCs available elif subfunction in response_subfn_number_of_dtc: if len(response.data) < 5: raise InvalidResponseException( response, u'Response must be exactly 5 bytes long ') response.service_data.status_availability = Dtc.Status.from_byte( ord(response.data[1])) response.service_data.dtc_format = ord(response.data[2]) response.service_data.dtc_count = struct.unpack( u'>H', response.data[3:5])[0] # This group of responses returns DTC snapshots # Responses include a DTC, many snapshot records. For each record, we find many Data Identifiers. # We create one Dtc.Snapshot for each DID. That'll be easier to work with. # <DTC,RecordNumber1,NumberOfDid_X,DID1,DID2,...DIDX, RecordNumber2,NumberOfDid_Y,DID1,DID2,...DIDY, etc> elif subfunction in response_sbfn_dtc_status_snapshots_records: if len(response.data) < 5: raise InvalidResponseException( response, u'Response must be at least 5 bytes long ') dtc = Dtc(struct.unpack(u'>L', '\x00' + response.data[1:4])[0]) dtc.status.set_byte(ord(response.data[4])) actual_byte = 5 # Increasing index ServiceHelper.validate_int(dtc_snapshot_did_size, min=1, max=8, name=u'dtc_snapshot_did_size') while True: # Loop until we have read all dtcs if len(response.data) <= actual_byte: break # done remaining_data = response.data[actual_byte:] if tolerate_zero_padding and remaining_data == '\x00' * len( remaining_data): break if len(remaining_data) < 2: raise InvalidResponseException( response, u'Incomplete response from server. Missing "number of identifier" and following data' ) record_number = ord(remaining_data[0]) number_of_did = ord(remaining_data[1]) # Validate record number and number of DID before continuing if number_of_did == 0: raise InvalidResponseException( response, u'Server returned snapshot record #%d with no data identifier included' % (record_number)) if len(remaining_data) < 2 + dtc_snapshot_did_size: raise InvalidResponseException( response, u'Incomplete response from server. Missing DID number and associated data.' ) actual_byte += 2 for i in xrange(number_of_did): remaining_data = response.data[actual_byte:] snapshot = Dtc.Snapshot( ) # One snapshot per DID for convenience. snapshot.record_number = record_number # As standard does not specify the length of the DID, we craft it based on a config did = 0 for j in xrange(dtc_snapshot_did_size): offset = dtc_snapshot_did_size - 1 - j did |= (ord(remaining_data[offset]) << (8 * j)) # Decode the data based on DID number. snapshot.did = did didconfig = ServiceHelper.check_did_config(did, didconfig) codec = DidCodec.from_config(didconfig[did]) data_offset = dtc_snapshot_did_size if len(remaining_data[data_offset:]) < len(codec): raise InvalidResponseException( response, u'Incomplete response. Data for DID 0x%04x is only %d bytes while %d bytes is expected' % (did, len( remaining_data[data_offset:]), len(codec))) snapshot.raw_data = remaining_data[ data_offset:data_offset + len(codec)] snapshot.data = codec.decode(snapshot.raw_data) dtc.snapshots.append(snapshot) actual_byte += dtc_snapshot_did_size + len(codec) response.service_data.dtcs.append(dtc) response.service_data.dtc_count = 1 # This group of responses returns DTC snapshots # Responses include a DTC, many snapshots records. For each record, we find many Data Identifiers. # We create one Dtc.Snapshot for each DID. That'll be easier to work with. # Similar to previous subfunction group, but order of information is changed. # <RecordNumber1, DTC1,NumberOfDid_X,DID1,DID2,...DIDX, RecordNumber2,DTC2, NumberOfDid_Y,DID1,DID2,...DIDY, etc> elif subfunction in response_sbfn_dtc_status_snapshots_records_record_first: ServiceHelper.validate_int(dtc_snapshot_did_size, min=1, max=8, name=u'dtc_snapshot_did_size') if len(response.data) < 2: raise InvalidResponseException( response, u'Response must be at least 2 bytes long. Subfunction echo + RecordNumber ' ) actual_byte = 1 # Increasing index while True: # Loop through response data if len(response.data) <= actual_byte: break # done remaining_data = response.data[actual_byte:] record_number = ord(remaining_data[0]) # If empty response but filled with 0, it is considered ok if remaining_data == '\x00' * len( remaining_data) and tolerate_zero_padding: break # If record number received but no DTC provided (allowed according to standard), we exit. if len(remaining_data ) == 1 or tolerate_zero_padding and remaining_data[ 1:] == '\x00' * len(remaining_data[1:]): break if len(remaining_data ) < 5: # Partial DTC (No DTC at all is checked above) raise InvalidResponseException( response, u'Incomplete response from server. Missing "DTCAndStatusRecord" and following data' ) if len(remaining_data) < 6: raise InvalidResponseException( response, u'Incomplete response from server. Missing number of data identifier' ) # DTC decoding dtc = Dtc( struct.unpack(u'>L', '\x00' + remaining_data[1:4])[0]) dtc.status.set_byte(ord(remaining_data[4])) number_of_did = ord(remaining_data[5]) actual_byte += 6 remaining_data = response.data[actual_byte:] if number_of_did == 0: raise InvalidResponseException( response, u'Server returned snapshot record #%d with no data identifier included' % (record_number)) if len(remaining_data) < dtc_snapshot_did_size: raise InvalidResponseException( response, u'Incomplete response from server. Missing DID and associated data' ) # We have a DTC and 0 DID, next loop if tolerate_zero_padding and remaining_data == '\x00' * len( remaining_data): break # For each DID for i in xrange(number_of_did): remaining_data = response.data[actual_byte:] snapshot = Dtc.Snapshot( ) # One snapshot epr DID for convenience snapshot.record_number = record_number # As standard does not specify the length of the DID, we craft it based on a config did = 0 for j in xrange(dtc_snapshot_did_size): offset = dtc_snapshot_did_size - 1 - j did |= (ord(remaining_data[offset]) << (8 * j)) # Decode the data based on DID number. snapshot.did = did didconfig = ServiceHelper.check_did_config(did, didconfig) codec = DidCodec.from_config(didconfig[did]) data_offset = dtc_snapshot_did_size if len(remaining_data[data_offset:]) < len(codec): raise InvalidResponseException( response, u'Incomplete response. Data for DID 0x%04x is only %d bytes while %d bytes is expected' % (did, len( remaining_data[data_offset:]), len(codec))) snapshot.raw_data = remaining_data[ data_offset:data_offset + len(codec)] snapshot.data = codec.decode(snapshot.raw_data) dtc.snapshots.append(snapshot) actual_byte += dtc_snapshot_did_size + len(codec) response.service_data.dtcs.append(dtc) response.service_data.dtc_count = len(response.service_data.dtcs) # These subfunctions include DTC ExtraData. We give it raw to user. elif subfunction in response_subfn_mask_record_plus_extdata: cls.assert_extended_data_size(extended_data_size, subfunction) if len(response.data) < 5: raise InvalidResponseException( response, u'Incomplete response from server. Missing DTCAndStatusRecord' ) # DTC decoding dtc = Dtc(struct.unpack(u'>L', '\x00' + response.data[1:4])[0]) dtc.status.set_byte(ord(response.data[4])) actual_byte = 5 # Increasing index while actual_byte < len(response.data): # Loop through data remaining_data = response.data[actual_byte:] record_number = ord(remaining_data[0]) if record_number == 0: if remaining_data == '\x00' * len( remaining_data) and tolerate_zero_padding: break else: raise InvalidResponseException( response, u'Extended data record number given by the server is 0 but this value is a reserved value.' ) actual_byte += 1 remaining_data = response.data[actual_byte:] if len(remaining_data) < extended_data_size: raise InvalidResponseException( response, u'Incomplete response from server. Length of extended data for DTC 0x%06x with record number 0x%02x is %d bytes but smaller than given data_size of %d bytes' % (dtc.id, record_number, len(remaining_data), extended_data_size)) exdata = Dtc.ExtendedData() exdata.record_number = record_number exdata.raw_data = remaining_data[0:extended_data_size] dtc.extended_data.append(exdata) actual_byte += extended_data_size response.service_data.dtcs.append(dtc) response.service_data.dtc_count = len(response.service_data.dtcs)