Exemple #1
0
    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"")
Exemple #2
0
	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
Exemple #5
0
	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)
Exemple #6
0
    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
Exemple #7
0
    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)
Exemple #8
0
	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
Exemple #9
0
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