def test_DIDCodec_bad_values(self): with self.assertRaises(NotImplementedError): codec = DidCodec() codec.encode(u"asd") with self.assertRaises(NotImplementedError): codec = DidCodec() codec.decode("asd") with self.assertRaises(ValueError): DidCodec.from_config(u"")
def make_request(cls, did, value, didconfig): """ Generate a request for WriteDataByIdentifier :param did: The data identifier to write :type did: int :param value: Value given to the :ref:`DidCodec <HelperClass_DidCodec>`.encode method :type value: object :param didconfig: Definition of DID codecs. Dictionary mapping a DID (int) to a valid :ref:`DidCodec <HelperClass_DidCodec>` class or pack/unpack string :type didconfig: dict[int] = :ref:`DidCodec <HelperClass_DidCodec>` :raises ValueError: If parameters are out of range or missing :raises ConfigError: If didlist contains a DID not defined in didconfig """ from udsoncan import Request, DidCodec ServiceHelper.validate_int(did, min=0, max=0xFFFF, name='Data Identifier') req = Request(cls) didconfig = ServiceHelper.check_did_config(did, didconfig=didconfig) # Make sure all DID are correctly defined in client config req.data = struct.pack('>H', did) # encode DID number codec = DidCodec.from_config(didconfig[did]) req.data += codec.encode(value) return req
def make_request(cls, did, value, didconfig): """ Generates a request for WriteDataByIdentifier :param did: The data identifier to write :type did: int :param value: Value given to the :ref:`DidCodec <DidCodec>`.encode method. If involved codec is defined with a pack string (default codec), multiple values may be passed with a tuple. :type value: object :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 ValueError: If parameters are out of range, missing or wrong type :raises ConfigError: If ``didlist`` contains a DID not defined in ``didconfig`` """ from udsoncan import Request, DidCodec ServiceHelper.validate_int(did, min=0, max=0xFFFF, name='Data Identifier') req = Request(cls) didconfig = ServiceHelper.check_did_config( did, didconfig=didconfig ) # Make sure all DIDs are correctly defined in client config req.data = struct.pack('>H', did) # encode DID number codec = DidCodec.from_config(didconfig[did]) if codec.__class__ == DidCodec and isinstance(value, tuple): req.data += codec.encode(*value) # Fixes issue #29 else: req.data += codec.encode(value) return req
def interpret_response(cls, response, didlist, didconfig, tolerate_zero_padding=True): """ Populates the response ``service_data`` property with an instance of :class:`ReadDataByIdentifier.ResponseData<udsoncan.services.ReadDataByIdentifier.ResponseData>` :param response: The received response to interpret :type response: :ref:`Response<Response>` :param didlist: List of data identifier used for the request. :type didlist: list[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>` :param tolerate_zero_padding: Ignore trailing zeros in the response data avoiding raising false :class:`InvalidResponseException<udsoncan.exceptions.InvalidResponseException>`. :type tolerate_zero_padding: bool :raises ValueError: If parameters are out of range, missing or wrong type :raises ConfigError: If ``didlist`` parameter or response contain a DID not defined in ``didconfig``. :raises InvalidResponseException: If response data is incomplete or if DID data does not match codec length. """ from udsoncan import DidCodec didlist = cls.validate_didlist_input(didlist) didconfig = ServiceHelper.check_did_config(didlist, didconfig) response.service_data = cls.ResponseData() response.service_data.values = {} # Parsing algorithm to extract DID value offset = 0 while True: if len(response.data) <= offset: break # Done if len(response.data) <= offset +1: if tolerate_zero_padding and response.data[-1] == 0: # One extra byte, but its a 0 and we accept that. So we're done break raise InvalidResponseException(response, "Response given by server is incomplete.") did = struct.unpack('>H', response.data[offset:offset+2])[0] # Get the DID number if did == 0 and did not in didconfig and tolerate_zero_padding: # We read two zeros and that is not a DID bu we accept that. So we're done. if response.data[offset:] == b'\x00' * (len(response.data) - offset): break if did not in didconfig: # Already checked in check_did_config. Paranoid check raise ConfigError(key=did, msg='Actual data identifier configuration contains no definition for data identifier 0x%04x' % did) codec = DidCodec.from_config(didconfig[did]) offset+=2 if len(response.data) < offset+len(codec): raise InvalidResponseException(response, "Value fo data identifier 0x%04x was incomplete according to definition in configuration" % did) subpayload = response.data[offset:offset+len(codec)] offset += len(codec) # Codec must define a __len__ function that metches the encoded payload length. val = codec.decode(subpayload) response.service_data.values[did] = val return response
def interpret_response(cls, response, control_param=None, tolerate_zero_padding=True, ioconfig=None): """ Populates the response `service_data` property with an instance of `InputOutputControlByIdentifier.ResponseData` :param response: The received response to interpret :type response: Response :param control_param: Same optional control parameter value given to make_request() :type control_param: int :param tolerate_zero_padding: Ignore trailing zeros in the response data avoiding reading an extra did with value 0. :type tolerate_zero_padding: bool :param ioconfig: Definition of DID codecs. Dictionary mapping a DID (int) to a valid :ref:`DidCodec<HelperClass_DidCodec>` class or pack/unpack string. It is possible to use composite DidCodec by specifying a dict with entries : codec, mask, mask_size. :type ioconfig: dict[int] = :ref:`DidCodec<HelperClass_DidCodec>`, dict :raises ValueError: If parameters are out of range or missing :raises ConfigError: If did echoed back by the server is not in the ioconfig definition :raises InvalidResponseException: If response data is incomplete or if DID data does not match codec length. """ from udsoncan import DidCodec min_response_size = 2 if control_param is not None else 1 # Spec specifies that if first by is a ControlParameter, it must be echoed back by the server if len(response.data) < min_response_size: raise InvalidResponseException(response, "Response must be at least %d bytes long" % min_response_size) response.service_data = cls.ResponseData() response.service_data.did_echo = struct.unpack(">H", response.data[0:2])[0] did = response.service_data.did_echo ioconfig = ServiceHelper.check_io_config(did, ioconfig) # IO dids are defined in client config. codec = DidCodec.from_config(ioconfig[did]) # Get IO codec from config next_byte = 2 if control_param is not None: if len(response.data) < next_byte: raise InvalidResponseException(response, 'Response should include an echo of the InputOutputControlParameter (0x%02x)' % control_param) response.service_data.control_param_echo = response.data[next_byte] next_byte +=1 if len(response.data) >= next_byte: remaining_data = response.data[next_byte:] if len(remaining_data) > len(codec): if remaining_data[len(codec):] == b'\x00' * (len(remaining_data) - len(codec)): if tolerate_zero_padding: remaining_data = remaining_data[0:len(codec)] try: response.service_data.decoded_data = codec.decode(remaining_data) except Exception as e: raise InvalidResponseException(response, 'Response from server could not be decoded. Exception is : %s' % e)
def make_request(cls, didlist, didconfig): """ Generates a request for ReadDataByIdentifier :param didlist: List of data identifier to read. :type didlist: list[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 ValueError: If parameters are out of range, missing or wrong type :raises ConfigError: If didlist contains a DID not defined in didconfig """ from udsoncan import Request from udsoncan import DidCodec didlist = cls.validate_didlist_input(didlist) req = Request(cls) ServiceHelper.check_did_config(didlist, didconfig) did_reading_all_data = None for did in didlist: if did not in didconfig: # Already checked in check_did_config. Paranoid check raise ConfigError( key=did, msg= 'Actual data identifier configuration contains no definition for data identifier 0x%04x' % did) codec = DidCodec.from_config(didconfig[did]) try: length = len(codec) if did_reading_all_data is not None: raise ValueError( 'Did 0x%04X is configured to read the rest of the payload (__len__ raisong ReadAllRemainingData), but a subsequent DID is requested (0x%04x)' % (did_reading_all_data, did)) except DidCodec.ReadAllRemainingData: if did_reading_all_data is not None: raise ValueError( 'It is impossible to read 2 DIDs configured to read the rest of the payload (__len__ raising ReadAllRemainingData). Dids are : 0x%04X and 0x%04X' % (did_reading_all_data, did)) did_reading_all_data = did req.data = struct.pack('>' + 'H' * len(didlist), *didlist) #Encode list of DID return req
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)
def make_request(cls, did, control_param=None, values=None, masks=None, ioconfig=None): """ Generate a request for InputOutputControlByIdentifier :param did: Data identifier to read :type did: int :param control_param: Optional parameter that can be a value from InputOutputControlByIdentifier.ControlParam :type control_param: int :param values: Optional values to send to the server. This parameter will be given to :ref:`DidCodec<HelperClass_DidCodec>`.encode() method. It can be: - A list for positional arguments - A dict for named arguments - An instance of :ref:`IOValues<HelperClass_IOValues>` for mixed arguments :type values: list, dict, :ref:`IOValues<HelperClass_IOValues>` :param masks: Optional mask record for composite values. The mask definition must be included in ioconfig It can be: - A list naming the bit mask to set - A dict with the mask name as a key and a boolean setting or clearing the mask as the value - An instance of :ref:`IOMask<HelperClass_IOMask>` - A boolean value to set all mask to the same value. :type masks: list, dict, :ref:`IOMask<HelperClass_IOMask>`, bool :param ioconfig: Definition of DID codecs. Dictionary mapping a DID (int) to a valid :ref:`DidCodec<HelperClass_DidCodec>` class or pack/unpack string. It is possible to use composite :ref:`DidCodec<HelperClass_DidCodec>` by specifying a dict with entries : codec, mask, mask_size. :type ioconfig: dict[int] = :ref:`DidCodec<HelperClass_DidCodec>`, dict :raises ValueError: If parameters are out of range or missing :raises ConfigError: If given did is not defined within ioconfig """ from udsoncan import Request, IOMasks, IOValues, DidCodec ServiceHelper.validate_int(did, min=0, max=0xffff, name='DID') if control_param is not None: if not isinstance(control_param, int): raise ValueError("control_param must be a valid integer") if control_param < 0 or control_param > 3: raise ValueError('control_param must either be returnControlToECU(0), resetToDefault(1), freezeCurrentState(2), shortTermAdjustment(3). %d given.' % control_param) if values is not None: if isinstance(values, list): values = IOValues(*values) if isinstance(values, dict): values = IOValues(**values) if not isinstance(values, IOValues): raise ValueError("values must be an instance of IOValues") if masks is not None: if isinstance(masks, list): masks = IOMasks(*masks) if isinstance(masks, dict): masks = IOMasks(**masks) if not isinstance(masks, IOMasks) and not isinstance(masks, bool): raise ValueError("masks must be an instance of IOMask or a boolean value") if values is None and masks is not None: raise ValueError('An IOValue must be given if a IOMask is provided.') request = Request(service=cls) request.data = b'' ioconfig = ServiceHelper.check_io_config(did, ioconfig) # IO dids are defined in client config. request.data += struct.pack('>H', did) # This parameters is optional according to standard if control_param is not None: request.data += struct.pack('B', control_param) codec = DidCodec.from_config(ioconfig[did]) # Get IO codec from config if values is not None: request.data += codec.encode(*values.args, **values.kwargs) if masks is not None: # Skip the masks byte if none is given. if isinstance(masks, bool): byte = b'\xFF' if masks == True else b'\x00' if 'mask_size' in ioconfig[did]: request.data += (byte * ioconfig[did]['mask_size']) else: raise ConfigError('mask_size', msg='Given mask is boolean value, indicating that all mask should be set to same value, but no mask_size is defined in configuration. Cannot guess how many bits to set.') elif isinstance(masks, IOMasks): if 'mask' not in ioconfig[did]: raise ConfigError('mask', msg='Cannot apply given mask. Input/Output configuration does not define their position (and size).') masks_config = ioconfig[did]['mask'] given_masks = masks.get_dict() numeric_val = 0 for mask_name in given_masks: if mask_name not in masks_config: raise ConfigError('mask_size', msg='Cannot set mask bit for mask %s. The configuration does not define its position' % (mask_name)) if given_masks[mask_name] == True: numeric_val |= masks_config[mask_name] minsize = math.ceil(math.log(numeric_val+1, 2)/8.0) size = minsize if 'mask_size' not in ioconfig[did] else ioconfig[did]['mask_size'] request.data += numeric_val.to_bytes(size, 'big') return request
def read_by_identifier(uds_i, addr, decoder=None, debug=False, resp_timeout=2): ''' retval= dict with value or values reval= -1 ->NegativeResponseException -2 -> timeout -3 -> unknown -4 -> general exception ''' try: if debug: logger.debug("ReadDataByIdentifier:{}".format(addr)) #:2x response = None timeout = False response = uds_i.request(ReadDataByIdentifier().Request(addr), resp_timeout) start = time.time() while response is None: try: response = uds_i.decode_response() logger.debug("wait for data...response:{}".format(response)) except ResponsePendingException as e: # response pending, go for next response = None timediff = time.time() - start if timediff > resp_timeout: #if debug: logger.debug(".........timeout {}".format(timediff)) timeout = True break #logger.debug ("Response:{}".format(response)) if isinstance(response, GenericResponse): id = response['dataIdentifier'] if debug: logger.debug('-- Response [{} / 0x{:2x} / {} ,{}]'.format( response.name, response.SID, id, response.values())) value = None if decoder is not None: codec = DidCodec.from_config(decoder) binarydata = struct.pack('%sB' % len(response['dataRecord']), *response['dataRecord']) datahex = ''.join('{:02X}'.format(a) for a in response['dataRecord']) logger.debug("Data: 0x{}".format(datahex)) value = codec.decode(binarydata) else: value = response["dataRecordASCII"] return {"value": value} elif isinstance(response, NegativeResponseException): if debug: logger.debug('\n[!!] %s' % response) return -1 elif response is None: if timeout == True: logger.info('Timeout {}\n'.format(response)) if debug: logger.debug('\n[??] Unknown Service: %s\n' % response) return -2 else: if debug: logger.debug('\n[!?] Strange stuff: %s\n' % response) return -3 except Exception as e: if debug: logger.debug("exception:{}".format(e)) return -4