示例#1
0
文件: base.py 项目: hejunbok/pyxcp
    def __init__(self, transportName, config=None):
        self.ctr = 0
        self.succeeded = True
        self.config = Configuration(MasterBaseType.PARAMETER_MAP or {}, config
                                    or {})
        self.logger = logging.getLogger("pyXCP")
        self.logger.setLevel(self.config.get("LOGLEVEL"))
        self.transport = createTransport(transportName, config)

        # In some cases the transport-layer needs to communicate with us.
        self.transport.parent = self
        self.service = None

        # (D)Word (un-)packers are byte-order dependent
        # -- byte-order is returned by CONNECT_Resp (COMM_MODE_BASIC)
        self.WORD_pack = None
        self.WORD_unpack = None
        self.DWORD_pack = None
        self.DWORD_unpack = None
        self.AG_pack = None
        self.AG_unpack = None
        # self.connected = False
        self.mta = types.MtaType(None, None)
        self.currentDaqPtr = None
        self.currentProtectionStatus = None
        self._seedNKeyDLL = None
示例#2
0
文件: base.py 项目: hejunbok/pyxcp
    def __init__(self, config=None):
        self.parent = None
        self.config = Configuration(BaseTransport.PARAMETER_MAP or {}, config or {})
        self.closeEvent = threading.Event()
        loglevel = self.config.get("LOGLEVEL")
        self.logger = Logger("transport.Base")
        self.logger.setLevel(loglevel)
        self.counterSend = 0
        self.counterReceived = -1
        create_daq_timestamps = self.config.get("CREATE_DAQ_TIMESTAMPS")
        self.create_daq_timestamps = False if create_daq_timestamps is None else create_daq_timestamps
        self.timing = Timing()
        self.resQueue = deque()
        self.daqQueue = deque()
        self.evQueue = deque()
        self.servQueue = deque()
        self.listener = threading.Thread(
            target=self.listen,
            args=(),
            kwargs={},
        )

        self.first_daq_timestamp = None

        self.datetime_origin, self.timestamp_origin = datetime.now(), time()
        self.pre_send_timestamp = time()
        self.post_send_timestamp = time()
        self.recv_timestamp = time()
示例#3
0
    def __init__(self, config=None):
        self.parent = None
        self.config = Configuration(BaseTransport.PARAMETER_MAP or {}, config
                                    or {})
        self.closeEvent = threading.Event()
        loglevel = self.config.get("LOGLEVEL")
        self._debug = loglevel == "DEBUG"

        self.logger = Logger("transport.Base")
        self.logger.setLevel(loglevel)
        self.counterSend = 0
        self.counterReceived = -1
        create_daq_timestamps = self.config.get("CREATE_DAQ_TIMESTAMPS")
        self.create_daq_timestamps = False if create_daq_timestamps is None else create_daq_timestamps
        timeout = self.config.get("TIMEOUT")
        self.alignment = self.config.get("ALIGNMENT")
        self.timeout = 2.0 if timeout is None else timeout
        self.timer_restart_event = threading.Event()
        self.timing = Timing()
        self.resQueue = deque()
        self.daqQueue = deque()
        self.evQueue = deque()
        self.servQueue = deque()
        self.listener = threading.Thread(
            target=self.listen,
            args=(),
            kwargs={},
        )
        self.cro_callback = None

        self.first_daq_timestamp = None
        if get_clock_info("time").resolution > 1e-5:
            ts, pc = time_perfcounter_correlation()
            self.timestamp_origin = ts
            self.datetime_origin = datetime.fromtimestamp(ts)
            self.perf_counter_origin = pc
        else:
            self.timestamp_origin = time()
            self.datetime_origin = datetime.fromtimestamp(
                self.timestamp_origin)

            # we will later use this to know if the current platform has a high
            # resolution time.time implementation
            self.perf_counter_origin = -1

        self.pre_send_timestamp = time()
        self.post_send_timestamp = time()
        self.recv_timestamp = time()
示例#4
0
文件: base.py 项目: sn4305/pyxcp
    def __init__(self, config=None, loglevel='WARN'):
        self.parent = None
        self.config = Configuration(self.PARAMETER_MAP or {}, config or {})
        self.closeEvent = threading.Event()
        self.logger = Logger("transport.Base")
        self.logger.setLevel(loglevel)
        self.counterSend = 0
        self.counterReceived = 0
        self.timing = Timing()
        self.resQueue = deque()
        self.daqQueue = deque()
        self.evQueue = deque()
        self.servQueue = deque()
        self.listener = threading.Thread(
            target=self.listen,
            args=(),
            kwargs={},
        )

        self.first_daq_timestamp = None
示例#5
0
文件: base.py 项目: hejunbok/pyxcp
class BaseTransport(metaclass=abc.ABCMeta):
    """Base class for transport-layers (Can, Eth, Sxi).

    Parameters
    ----------
    config: dict-like
        Parameters like bitrate.
    loglevel: ["INFO", "WARN", "DEBUG", "ERROR", "CRITICAL"]
        Controls the verbosity of log messages.

    """

    PARAMETER_MAP = {
        #                         Type    Req'd   Default
        "CREATE_DAQ_TIMESTAMPS": (bool,   False,  False),
        "LOGLEVEL":              (str,    False,  "WARN"),
    }

    def __init__(self, config=None):
        self.parent = None
        self.config = Configuration(BaseTransport.PARAMETER_MAP or {}, config or {})
        self.closeEvent = threading.Event()
        loglevel = self.config.get("LOGLEVEL")
        self.logger = Logger("transport.Base")
        self.logger.setLevel(loglevel)
        self.counterSend = 0
        self.counterReceived = -1
        create_daq_timestamps = self.config.get("CREATE_DAQ_TIMESTAMPS")
        self.create_daq_timestamps = False if create_daq_timestamps is None else create_daq_timestamps
        self.timing = Timing()
        self.resQueue = deque()
        self.daqQueue = deque()
        self.evQueue = deque()
        self.servQueue = deque()
        self.listener = threading.Thread(
            target=self.listen,
            args=(),
            kwargs={},
        )

        self.first_daq_timestamp = None

        self.datetime_origin, self.timestamp_origin = datetime.now(), time()
        self.pre_send_timestamp = time()
        self.post_send_timestamp = time()
        self.recv_timestamp = time()

    def __del__(self):
        self.finishListener()
        self.closeConnection()

    def loadConfig(self, config):
        """Load configuration data.
        """
        self.config = Configuration(self.PARAMETER_MAP or {}, config or {})

    def close(self):
        """Close the transport-layer connection and event-loop.
        """
        self.finishListener()
        if self.listener.is_alive():
            self.listener.join()
        self.closeConnection()

    @abc.abstractmethod
    def connect(self):
        pass

    def startListener(self):
        self.listener.start()

    def finishListener(self):
        if hasattr(self, "closeEvent"):
            self.closeEvent.set()

    def request(self, cmd, *data):
        frame = self._prepare_request(cmd, *data)
        self.timing.start()
        self.send(frame)

        try:
            xcpPDU = get(self.resQueue, timeout=2.0)
        except Empty:
            raise types.XcpTimeoutError("Response timed out.") from None

        self.timing.stop()

        pid = types.Response.parse(xcpPDU).type
        if pid == 'ERR' and cmd.name != 'SYNCH':
            err = types.XcpError.parse(xcpPDU[1:])
            raise types.XcpResponseError(err)
        else:
            pass    # Und nu??
        return xcpPDU[1:]

    def block_request(self, cmd, *data):
        """
        Implements packet transmission for block communication model (e.g. DOWNLOAD block mode)
        All parameters are the same as in request(), but it does not receive response.
        """

        # check response queue before each block request, so that if the slave device
        # has responded with a negative response (e.g. ACCESS_DENIED or SEQUENCE_ERROR), we can
        # process it.
        if self.resQueue:
            xcpPDU = self.resQueue.popleft()
            pid = types.Response.parse(xcpPDU).type
            if pid == 'ERR' and cmd.name != 'SYNCH':
                err = types.XcpError.parse(xcpPDU[1:])
                raise types.XcpResponseError(err)

        frame = self._prepare_request(cmd, *data)
        self.send(frame)

    def _prepare_request(self, cmd, *data):
        """
        Prepares a request to be sent
        """
        self.logger.debug(cmd.name)
        self.parent._setService(cmd)
        cmdlen = cmd.bit_length() // 8  # calculate bytes needed for cmd
        header = self.HEADER.pack(cmdlen + len(data), self.counterSend)
        self.counterSend = (self.counterSend + 1) & 0xffff

        frame = header + bytes(flatten(cmd.to_bytes(cmdlen, 'big'), data))
        self.logger.debug("-> {}".format(hexDump(frame)))
        return frame

    def block_receive(self, length_required: int) -> bytes:
        """
        Implements packet reception for block communication model
        (e.g. for XCP on CAN)

        Parameters
        ----------
        length_required: int
            number of bytes to be expected in block response packets

        Returns
        -------
        bytes
            all payload bytes received in block response packets

        Raises
        ------
        :class:`pyxcp.types.XcpTimeoutError`
        """
        TIMEOUT = 1.0   # TODO: parameter.
        block_response = b''
        start = time()
        while len(block_response) < length_required:
            if len(self.resQueue):
                partial_response = self.resQueue.popleft()
                block_response += partial_response[1:]
            else:
                if time() - start > TIMEOUT:
                    raise types.XcpTimeoutError("Response timed out [block_receive].") from None
                sleep(0.001)
        return block_response

    @abc.abstractmethod
    def send(self, frame):
        pass

    @abc.abstractmethod
    def closeConnection(self):
        """Does the actual connection shutdown.
        Needs to be implemented by any sub-class.
        """
        pass

    @abc.abstractmethod
    def listen(self):
        pass

    def processResponse(self, response, length, counter, recv_timestamp=None):
        if counter == self.counterReceived:
            self.logger.warn("Duplicate message counter {} received from the XCP slave".format(counter))
            return

        self.counterReceived = counter

        pid = response[0]
        if pid >= 0xFC:
            self.logger.debug(
                "<- L{} C{} {}".format(
                    length,
                    counter,
                    hexDump(response),
                )
            )
            if pid >= 0xfe:
                # self.resQueue.put(response)
                self.resQueue.append(response)
                self.recv_timestamp = recv_timestamp
            elif pid == 0xfd:
                # self.evQueue.put(response)
                self.evQueue.append(response)
            elif pid == 0xfc:
                # self.servQueue.put(response)
                self.servQueue.append(response)
        else:
            if self.first_daq_timestamp is None:
                self.first_daq_timestamp = recv_timestamp
            if self.create_daq_timestamps:
                timestamp = recv_timestamp
            else:
                timestamp = 0.0
            element = (response, counter, length, timestamp)
            self.daqQueue.append(element)
示例#6
0
文件: base.py 项目: hejunbok/pyxcp
 def loadConfig(self, config):
     """Load configuration data.
     """
     self.config = Configuration(self.PARAMETER_MAP or {}, config or {})
示例#7
0
class MasterBaseType:
    """Common part of lowlevel XCP API.

    Parameters
    ----------
    transportName : str
        XCP transport layer name ['can', 'eth', 'sxi']
    config: dict
    """

    PARAMETER_MAP = {
        #                         Type    Req'd   Default
        "LOGLEVEL": (str, False, "WARN"),
    }

    def __init__(self, transportName, config=None):
        self.ctr = 0
        self.succeeded = True
        self.config = Configuration(MasterBaseType.PARAMETER_MAP or {}, config
                                    or {})
        self.logger = logging.getLogger("pyXCP")
        self.logger.setLevel(self.config.get("LOGLEVEL"))
        self.transport = createTransport(transportName, config)

        # In some cases the transport-layer needs to communicate with us.
        self.transport.parent = self
        self.service = None

        # (D)Word (un-)packers are byte-order dependent
        # -- byte-order is returned by CONNECT_Resp (COMM_MODE_BASIC)
        self.WORD_pack = None
        self.WORD_unpack = None
        self.DWORD_pack = None
        self.DWORD_unpack = None
        self.AG_pack = None
        self.AG_unpack = None
        #self.connected = False

    def __enter__(self):
        """Context manager entry part.
        """
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit part.
        """
        #if self.connected:
        #    self.disconnect()
        self.close()
        if exc_type is None:
            return
        else:
            self.succeeded = False
            # print("=" * 79)
            # print("Exception while in Context-Manager:\n")
            self.logger.error(''.join(
                traceback.format_exception(exc_type, exc_val, exc_tb)))
            # print("=" * 79)
            # return True

    def _setService(self, service):
        """Records the currently processed service.

        Parameters
        ----------
        service: `pydbc.types.Command`

        Note
        ----
        Internal Function, only to be used by transport-layer.
        """
        self.service = service

    def close(self):
        """Closes transport layer connection.
        """
        self.transport.close()

    # Mandatory Commands.
    @wrapped
    def connect(self):
        """Build up connection to an XCP slave.

        Before the actual XCP traffic starts a connection is required.

        Parameters
        ----------
        None

        Returns
        -------
        :py:obj:`pyxcp.types.ConnectResponse`
            Describes fundamental client properties.

        Note
        ----
        Every XCP slave supports at most one connection,
        more attempts to connect are silently ignored.

        """
        self.transport.connect()
        response = self.transport.request(types.Command.CONNECT, 0x00)

        # First get byte-order
        resultPartial = types.ConnectResponsePartial.parse(response)
        byteOrder = resultPartial.commModeBasic.byteOrder

        result = types.ConnectResponse.parse(response, byteOrder=byteOrder)
        self.slaveProperties = SlaveProperties(byteOrder=byteOrder,
                                               maxCto=result.maxCto,
                                               maxDto=result.maxDto)
        byteOrderPrefix = "<" if byteOrder == types.ByteOrder.INTEL else ">"

        self.slaveProperties.supportsPgm = result.resource.pgm
        self.slaveProperties.supportsStim = result.resource.stim
        self.slaveProperties.supportsDaq = result.resource.daq
        self.slaveProperties.supportsCalpag = result.resource.calpag
        self.slaveProperties.slaveBlockMode = \
            result.commModeBasic.slaveBlockMode
        self.slaveProperties.addressGranularity = \
            result.commModeBasic.addressGranularity
        self.slaveProperties.protocolLayerVersion = result.protocolLayerVersion
        self.slaveProperties.transportLayerVersion = \
            result.transportLayerVersion
        self.slaveProperties.optionalCommMode = \
            result.commModeBasic.optional

        self.WORD_pack = makeWordPacker(byteOrderPrefix)
        self.DWORD_pack = makeDWordPacker(byteOrderPrefix)
        self.WORD_unpack = makeWordUnpacker(byteOrderPrefix)
        self.DWORD_unpack = makeDWordUnpacker(byteOrderPrefix)

        if self.slaveProperties.addressGranularity == \
                types.AddressGranularity.BYTE:
            self.AG_pack = struct.Struct("<B").pack
            self.AG_unpack = struct.Struct("<B").unpack
        elif self.slaveProperties.addressGranularity == \
                types.AddressGranularity.WORD:
            self.AG_pack = self.WORD_pack
            self.AG_unpack = self.WORD_unpack
        elif self.slaveProperties.addressGranularity == \
                types.AddressGranularity.DWORD:
            self.AG_pack = self.DWORD_pack
            self.AG_unpack = self.DWORD_unpack
        #self.connected = True
        return result

    @wrapped
    def disconnect(self):
        """Releases the connection to the XCP slave.

        Thereafter, no further communication with the slave is possible
        (besides `connect`).


        Note
        -----
        If DISCONNECT is currently not possible, ERR_CMD_BUSY will be returned.
        """
        response = self.transport.request(types.Command.DISCONNECT)
        #self.connected = False
        return response

    @wrapped
    def getStatus(self):
        """Get current status information of the slave device.

        This includes the status of the resource protection, pending store
        requests and the general status of data acquisition and stimulation.

        Returns
        -------
        :obj:`pyxcp.types.GetStatusResponse`
        """
        response = self.transport.request(types.Command.GET_STATUS)
        result = types.GetStatusResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        return result

    @wrapped
    def synch(self):
        """Synchronize command execution after timeout conditions.

        """
        response = self.transport.request(types.Command.SYNCH)
        return response

    @wrapped
    def getCommModeInfo(self):
        """Get optional information on different Communication Modes supported
        by the slave.

        Returns
        -------
        :obj:`pyxcp.types.GetCommModeInfoResponse`
        """
        response = self.transport.request(types.Command.GET_COMM_MODE_INFO)
        result = types.GetCommModeInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        self.slaveProperties.interleavedMode = result.commModeOptional.interleavedMode
        self.slaveProperties.masterBlockMode = result.commModeOptional.masterBlockMode
        self.slaveProperties.maxBs = result.maxBs
        self.slaveProperties.minSt = result.minSt
        self.slaveProperties.queueSize = result.queueSize
        self.slaveProperties.xcpDriverVersionNumber = result.xcpDriverVersionNumber
        return result

    @wrapped
    def getId(self, mode: int):
        """This command is used for automatic session configuration and for
        slave device identification.

        Parameters
        ----------
        mode : int
            The following identification types may be requested:
            - 0        ASCII text
            - 1        ASAM-MC2 filename without path and extension
            - 2        ASAM-MC2 filename with path and extension
            - 3        URL where the ASAM-MC2 file can be found
            - 4        ASAM-MC2 file to upload
            - 128..255 User defined

        Returns
        -------
        :obj:`pydbc.types.GetIDResponse`
        """
        response = self.transport.request(types.Command.GET_ID, mode)
        result = types.GetIDResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        result.length = self.DWORD_unpack(response[3:7])[0]
        return result

    @wrapped
    def setRequest(self, mode: int, sessionConfigurationId: int):
        """Request to save to non-volatile memory.

        Parameters
        ----------
        mode : int (bitfield)
            - 1  Request to store calibration data
            - 2  Request to store DAQ list, no resume
            - 4  Request to store DAQ list, resume enabled
            - 8  Request to clear DAQ configuration
        sessionConfigurationId : int

        """
        response = self.transport.request(types.Command.SET_REQUEST, mode,
                                          sessionConfigurationId >> 8,
                                          sessionConfigurationId & 0xff)
        return response

    @wrapped
    def getSeed(self, first: int, resource: int):
        """Get seed from slave for unlocking a protected resource.

        Parameters
        ----------
        first : int
            - 0 - first part of seed
            - 1 - remaining part
        resource : int
            - Mode = =0 - Resource
            - Mode == 1 - Don't care

        Returns
        -------
        `pydbc.types.GetSeedResponse`
        """
        response = self.transport.request(types.Command.GET_SEED, first,
                                          resource)
        return types.GetSeedResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def unlock(self, length: int, key: bytes):
        """Send key to slave for unlocking a protected resource.

        Parameters
        ----------
        length : int
            indicates the (remaining) number of key bytes.
        key : bytes

        Returns
        -------
        :obj:`pydbc.types.ResourceType`

        Note
        ----
        The master has to use :meth:`unlock` in a defined sequence together
        with :meth:`getSeed`. The master only can send an :meth:`unlock` sequence
        if previously there was a :meth:`getSeed` sequence. The master has
        to send the first `unlocking` after a :meth:`getSeed` sequence with
        a Length containing the total length of the key.
        """
        response = self.transport.request(types.Command.UNLOCK, length, *key)
        return types.ResourceType.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def setMta(self, address: int, addressExt: int = 0x00):
        """Set Memory Transfer Address in slave.

        Parameters
        ----------
        address : int
        addressExt : int

        Note
        ----
        The MTA is used by :meth:`buildChecksum`, :meth:`upload`, :meth:`download`, :meth:`downloadNext`,
        :meth:`downloadMax`, :meth:`modifyBits`, :meth:`programClear`, :meth:`program`, :meth:`programNext`
        and :meth:`programMax`.

        """
        addr = self.DWORD_pack(address)
        response = self.transport.request(types.Command.SET_MTA, 0, 0,
                                          addressExt, *addr)
        return response

    @wrapped
    def upload(self, length: int):
        """Transfer data from slave to master.

        Parameters
        ----------
        length : int

        Note
        ----
        Adress is set via :meth:`setMta` (Some services like :meth:`getID` also set the MTA).

        Returns
        -------
        bytes
        """

        response = self.transport.request(types.Command.UPLOAD, length)
        if length > (self.slaveProperties.maxCto - 1):
            block_response = self.transport.block_receive(
                length_required=(length - len(response)))
            response += block_response
        return response

    @wrapped
    def shortUpload(self, length: int, address: int, addressExt: int = 0x00):
        """Transfer data from slave to master.
        As opposed to :meth:`upload` this service also includes address information.

        Parameters
        ----------
        address : int
        addressExt : int

        Returns
        -------
        bytes
        """
        addr = self.DWORD_pack(address)
        response = self.transport.request(types.Command.SHORT_UPLOAD, length,
                                          0, addressExt, *addr)
        return response

    @wrapped
    def buildChecksum(self, blocksize: int):
        """Build checksum over memory range.

        Parameters
        ----------
        blocksize : int

        Returns
        -------
        :obj:`~pyxcp.types.BuildChecksumResponse`

        .. note:: Adress is set via `setMta`

        See Also
        --------
        :mod:`~pyxcp.checksum`
        """
        bs = self.DWORD_pack(blocksize)
        response = self.transport.request(types.Command.BUILD_CHECKSUM, 0, 0,
                                          0, *bs)
        return types.BuildChecksumResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def transportLayerCmd(self, subCommand: int, data: bytes):
        """Execute transfer-layer specific command.

        Parameters
        ----------
        subCommand : int
        data : bytes

        Note
        ----
        For details refer to XCP specification.
        """
        response = self.transport.request(types.Command.TRANSPORT_LAYER_CMD,
                                          subCommand, *data)
        return response

    @wrapped
    def userCmd(self, subCommand: int, data: bytes):
        """Execute proprietary command implemented in your XCP client.

        Parameters
        ----------
        subCommand : int
        data : bytes


        .. note:: For details refer to your XCP client vendor.
        """

        response = self.transport.request(types.Command.USER_CMD, subCommand,
                                          *data)
        return response

    @wrapped
    def getVersion(self):
        """Get version information.

        This command returns detailed information about the implemented
        protocol layer version of the XCP slave and the transport layer
        currently in use.

        Returns
        -------
        :obj:`~types.GetVersionResponse`
        """

        response = self.transport.request(types.Command.GET_VERSION)
        result = types.GetVersionResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        self.slaveProperties.protocolMajor = result.protocolMajor
        self.slaveProperties.protocolMinor = result.protocolMinor
        self.slaveProperties.transportMajor = result.transportMajor
        self.slaveProperties.transportMinor = result.transportMinor
        return result

    def fetch(self, length: int, limitPayload: int = None):  # TODO: pull
        """Convenience function for data-transfer from slave to master
        (Not part of the XCP Specification).

        Parameters
        ----------
        length : int
        limitPayload : int
            transfer less bytes then supported by transport-layer

        Returns
        -------
        bytes

        Note
        ----
        address is not included because of services implicitly setting address information like :meth:`getID` .
        """
        if limitPayload and limitPayload < 8:
            raise ValueError(
                "Payload must be at least 8 bytes - given: {}".format(
                    limitPayload))

        slaveBlockMode = self.slaveProperties.slaveBlockMode
        if slaveBlockMode:
            maxPayload = 255
        else:
            maxPayload = self.slaveProperties.maxCto - 1
        payload = min(limitPayload, maxPayload) if limitPayload else maxPayload
        chunkSize = payload
        chunks = range(length // chunkSize)
        remaining = length % chunkSize
        result = []
        for _ in chunks:
            data = self.upload(chunkSize)
            result.extend(data)
        if remaining:
            data = self.upload(remaining)
            result.extend(data)
        return bytes(result)

    # Calibration Commands (CAL)
    @wrapped
    def download(self, data: bytes, blockModeLength=None):
        """Transfer data from master to slave.

        Parameters
        ----------
        data : bytes
            Data to send to slave.
        blockModeLength : int or None
            for block mode, the download request must contain the length of the whole block,
            not just the length in the current packet. The whole block length can be given here for block-mode
            transfers. For normal mode, the length indicates the actual packet's payload length.

        Note
        ----
        Adress is set via :meth:`setMta`
        """

        if blockModeLength is None:
            # standard mode
            length = len(data)
            response = self.transport.request(types.Command.DOWNLOAD, length,
                                              *data)
            return response
        else:
            # block mode
            if not isinstance(blockModeLength, int):
                raise TypeError('blockModeLength must be int!')
            self.transport.block_request(types.Command.DOWNLOAD,
                                         blockModeLength, *data)
            return None

    @wrapped
    def downloadNext(self, data: bytes, remainingBlockLength, last=False):
        """Transfer data from master to slave (block mode).

        Parameters
        ----------
        data : bytes
        remainingBlockLength : int
            This parameter has to be given the remaining length in the block
        last : bool
            The block mode implementation shall indicate the last packet in the block with this parameter, because
            the slave device will send the response after this.
        """

        if last:
            # last DOWNLOAD_NEXT packet in a block: the slave device has to send the response after this.
            response = self.transport.request(types.Command.DOWNLOAD_NEXT,
                                              remainingBlockLength, *data)
            return response
        else:
            # the slave device won't respond to consecutive DOWNLOAD_NEXT packets in block mode,
            # so we must not wait for any response
            self.transport.block_request(types.Command.DOWNLOAD_NEXT,
                                         remainingBlockLength, *data)
            return None

    @wrapped
    def downloadMax(self, data: bytes):
        """Transfer data from master to slave (fixed size).

        Parameters
        ----------
        data : bytes
        """
        response = self.transport.request(types.Command.DOWNLOAD_MAX, *data)
        return response

    # Page Switching Commands (PAG)
    @wrapped
    def setCalPage(self, mode: int, logicalDataSegment: int,
                   logicalDataPage: int):
        """Set calibration page.

        Parameters
        ----------
        mode : int (bitfield)
            - 0x01 - The given page will be used by the slave device application.
            - 0x02 - The slave device XCP driver will access the given page.
            - 0x80 - The logical segment number is ignored. The command applies to all segments
        logicalDataSegment : int
        logicalDataPage : int
        """
        response = self.transport.request(types.Command.SET_CAL_PAGE, mode,
                                          logicalDataSegment, logicalDataPage)
        return response

    @wrapped
    def getCalPage(self, mode: int, logicalDataSegment: int):
        """Get calibration page

        Parameters
        ----------
        mode : int
        logicalDataSegment : int
        """
        response = self.transport.request(types.Command.GET_CAL_PAGE, mode,
                                          logicalDataSegment)
        return response[2]

    @wrapped
    def getPagProcessorInfo(self):
        """Get general information on PAG processor.

        Returns
        -------
        `pydbc.types.GetPagProcessorInfoResponse`
    """
        response = self.transport.request(types.Command.GET_PAG_PROCESSOR_INFO)
        return types.GetPagProcessorInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getSegmentInfo(self, mode, segmentNumber, segmentInfo, mappingIndex):
        """Get specific information for a segment.

        Parameters
        ----------
        mode : int
            - 0 = get basic address info for this segment
            - 1 = get standard info for this segment
            - 2 = get address mapping info for this segment

        segmentNumber : int
        segmentInfo : int
            Mode 0:
                - 0 = address
                - 1 = length

            Mode 1:
                - don't care

            Mode 2:
                - 0 = source address
                - 1 = destination address
                - 2 = length address

        mappingIndex : int
            - Mode 0: don't care
            - Mode 1: don't care
            - Mode 2: identifier for address mapping range that mapping_info belongs to.

        """
        response = self.transport.request(types.Command.GET_SEGMENT_INFO, mode,
                                          segmentNumber, segmentInfo,
                                          mappingIndex)
        if mode == 0:
            return types.GetSegmentInfoMode0Response.parse(
                response, byteOrder=self.slaveProperties.byteOrder)
        elif mode == 1:
            return types.GetSegmentInfoMode1Response.parse(
                response, byteOrder=self.slaveProperties.byteOrder)
        elif mode == 2:
            return types.GetSegmentInfoMode2Response.parse(
                response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getPageInfo(self, segmentNumber, pageNumber):
        """Get specific information for a page.

        Parameters
        ----------
        segmentNumber : int
        pageNumber : int
        """
        response = self.transport.request(types.Command.GET_PAGE_INFO, 0,
                                          segmentNumber, pageNumber)
        return (types.PageProperties.parse(
            bytes([response[0]]),
            byteOrder=self.slaveProperties.byteOrder), response[1])

    @wrapped
    def setSegmentMode(self, mode, segmentNumber):
        """Set mode for a segment.

        Parameters
        ----------
        mode : int (bitfield)
            1 = enable FREEZE Mode
        segmentNumber : int
        """
        response = self.transport.request(types.Command.SET_SEGMENT_MODE, mode,
                                          segmentNumber)
        return response

    @wrapped
    def getSegmentMode(self, segmentNumber):
        """Get mode for a segment.

        Parameters
        ----------
        segmentNumber : int
        """
        response = self.transport.request(types.Command.GET_SEGMENT_MODE, 0,
                                          segmentNumber)
        return response[1]

    @wrapped
    def copyCalPage(self, srcSegment, srcPage, dstSegment, dstPage):
        """Copy page.

        Parameters
        ----------
        srcSegment : int
        srcPage : int
        dstSegment : int
        dstPage : int
        """
        response = self.transport.request(types.Command.COPY_CAL_PAGE,
                                          srcSegment, srcPage, dstSegment,
                                          dstPage)
        return response

    # DAQ
    @wrapped
    def clearDaqList(self, daqListNumber):
        """Clear DAQ list configuration.

        Parameters
        ----------
        daqListNumber : int
        """
        daqList = self.WORD_pack(daqListNumber)
        response = self.transport.request(types.Command.CLEAR_DAQ_LIST, 0,
                                          *daqList)
        return response

    @wrapped
    def writeDaq(self, bitOffset, entrySize, addressExt, address):
        """Write element in ODT entry.

        Parameters
        ----------
        bitOffset : int
            Position of bit in 32-bit variable referenced by the address and
            extension below
        entrySize : int
        addressExt : int
        address : int
        """
        addr = self.DWORD_pack(address)
        response = self.transport.request(types.Command.WRITE_DAQ, bitOffset,
                                          entrySize, addressExt, *addr)
        return response

    @wrapped
    def getDaqListMode(self, daqListNumber):
        """Get mode from DAQ list.

        Parameters
        ----------
        daqListNumber : int

        Returns
        -------
        `pyxcp.types.GetDaqListModeResponse`
        """
        dln = self.WORD_pack(daqListNumber)
        response = self.transport.request(types.Command.GET_DAQ_LIST_MODE, 0,
                                          *dln)
        return types.GetDaqListModeResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def startStopDaqList(self, mode, daqListNumber):
        """Start /stop/select DAQ list.

        Parameters
        ----------
        mode : int
            0 = stop
            1 = start
            2 = select
        daqListNumber : int
        """
        dln = self.WORD_pack(daqListNumber)
        response = self.transport.request(types.Command.START_STOP_DAQ_LIST,
                                          mode, *dln)
        return types.StartStopDaqListResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def startStopSynch(self, mode):
        """Start/stop DAQ lists (synchronously).

        Parameters
        ----------
        mode : int
            0 = stop all
            1 = start selected
            2 = stop selected
        """
        response = self.transport.request(types.Command.START_STOP_SYNCH, mode)
        return response

    @wrapped
    def writeDaqMultiple(self, daqElements):
        """Write multiple elements in ODT.

        Parameters
        ----------
        daqElements : list of DAQ elements
        """

        data = bytearray()
        data.append(len(daqElements))

        for daqElement in daqElements:
            data.extend(
                types.DaqElement.build(
                    daqElement, byteOrder=self.slaveProperties.byteOrder))

        response = self.transport.request(types.Command.WRITE_DAQ_MULTIPLE,
                                          *data)
        return response

    # optional
    @wrapped
    def getDaqClock(self):
        """Get DAQ clock from slave.

        Returns
        -------
        int
            Current timestamp, format specified by `getDaqResolutionInfo`
        """
        response = self.transport.request(types.Command.GET_DAQ_CLOCK)
        result = types.GetDaqClockResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        return result.timestamp

    @wrapped
    def readDaq(self):
        """Read element from ODT entry.

        Returns
        -------
        `pyxcp.types.ReadDaqResponse`
        """
        response = self.transport.request(types.Command.READ_DAQ)
        return types.ReadDaqResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getDaqProcessorInfo(self):
        """Get general information on DAQ processor.

        Returns
        -------
        `pyxcp.types.GetDaqProcessorInfoResponse`
        """
        response = self.transport.request(types.Command.GET_DAQ_PROCESSOR_INFO)
        return types.GetDaqProcessorInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getDaqResolutionInfo(self):
        """Get general information on DAQ processing resolution.

        Returns
        -------
        `pyxcp.types.GetDaqResolutionInfoResponse`
        """
        response = self.transport.request(
            types.Command.GET_DAQ_RESOLUTION_INFO)
        return types.GetDaqResolutionInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getDaqListInfo(self, daqListNumber):
        """Get specific information for a DAQ list.

        Parameters
        ----------
        daqListNumber : int
        """
        dln = self.WORD_pack(daqListNumber)
        response = self.transport.request(types.Command.GET_DAQ_LIST_INFO, 0,
                                          *dln)
        return types.GetDaqListInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getDaqEventInfo(self, eventChannelNumber):
        """Get specific information for an event channel.

        Parameters
        ----------
        eventChannelNumber : int

        Returns
        -------
        `pyxcp.types.GetEventChannelInfoResponse`
        """
        ecn = self.WORD_pack(eventChannelNumber)
        response = self.transport.request(types.Command.GET_DAQ_EVENT_INFO, 0,
                                          *ecn)
        return types.GetEventChannelInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def dtoCtrProperties(self, modifier, eventChannel, relatedEventChannel,
                         mode):
        """DTO CTR properties

        Parameters
        ----------
        modifier :
        eventChannel : int
        relatedEventChannel : int
        mode :

        Returns
        -------
        `pyxcp.types.DtoCtrPropertiesResponse`
        """
        data = bytearray()
        data.append(modifier)
        data.extend(self.WORD_pack(eventChannel))
        data.extend(self.WORD_pack(relatedEventChannel))
        data.append(mode)
        response = self.transport.request(types.Command.DTO_CTR_PROPERTIES,
                                          *data)
        return types.DtoCtrPropertiesResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def setDaqPackedMode(self,
                         daqListNumber,
                         daqPackedMode,
                         dpmTimestampMode=None,
                         dpmSampleCount=None):
        """Set DAQ List Packed Mode.

        Parameters
        ----------
        daqListNumber : int
        daqPackedMode : int
        """
        params = []
        dln = self.WORD_pack(daqListNumber)
        params.extend(dln)
        params.append(daqPackedMode)

        if daqPackedMode == 1 or daqPackedMode == 2:
            params.append(dpmTimestampMode)
            dsc = self.WORD_pack(dpmSampleCount)
            params.extend(dsc)

        response = self.transport.request(types.Command.SET_DAQ_PACKED_MODE,
                                          *params)
        return response

    @wrapped
    def getDaqPackedMode(self, daqListNumber):
        """Get DAQ List Packed Mode.

        This command returns information of the currently active packed mode of
        the addressed DAQ list.

        Parameters
        ----------
        daqListNumber : int
        """
        dln = self.WORD_pack(daqListNumber)
        response = self.transport.request(types.Command.GET_DAQ_PACKED_MODE,
                                          *dln)
        result = types.GetDaqPackedModeResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        return result

    # dynamic
    @wrapped
    def freeDaq(self):
        """Clear dynamic DAQ configuration.
        """
        response = self.transport.request(types.Command.FREE_DAQ)
        return response

    @wrapped
    def allocDaq(self, daqCount):
        """Allocate DAQ lists.

        Parameters
        ----------
        daqCount : int
            number of DAQ lists to be allocated
        """
        dq = self.WORD_pack(daqCount)
        response = self.transport.request(types.Command.ALLOC_DAQ, 0, *dq)
        return response

    # PGM
    @wrapped
    def programStart(self):
        """Indicate the beginning of a programming sequence.

        Returns
        -------
        `pyxcp.types.ProgramStartResponse`
        """
        response = self.transport.request(types.Command.PROGRAM_START)
        return types.ProgramStartResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def programClear(self, mode: int, clearRange: int):
        """Clear a part of non-volatile memory.

        Parameters
        ----------
        mode : int
            0x00 = the absolute access mode is active (default)
            0x01 = the functional access mode is active
        clearRange : int
        """
        cr = self.DWORD_pack(clearRange)
        response = self.transport.request(types.Command.PROGRAM_CLEAR, mode, 0,
                                          0, *cr)
        # ERR_ACCESS_LOCKED
        return response

    @wrapped
    def program(self, data: bytes):
        """
        """
        d = bytearray()
        d.append(len(data))
        if self.slaveProperties.addressGranularity == \
                types.AddressGranularity.DWORD:
            d.extend(b'\x00\x00')  # alignment bytes
        for e in data:
            d.extend(self.AG_pack(e))
        return self.transport.request(types.Command.PROGRAM, *d)

    def programReset(self):
        """Indicate the end of a programming sequence."""
        return self.transport.request(types.Command.PROGRAM_RESET)

    def getPgmProcessorInfo(self):
        """Get general information on PGM processor."""
        response = self.transport.request(types.Command.GET_PGM_PROCESSOR_INFO)
        return types.GetPgmProcessorInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    def getSectorInfo(self, mode, sectorNumber):
        """Get specific information for a sector."""
        response = self.transport.request(types.Command.GET_SECTOR_INFO, mode,
                                          sectorNumber)
        if mode == 0 or mode == 1:
            return types.GetSectorInfoResponseMode01.parse(
                response, byteOrder=self.slaveProperties.byteOrder)
        elif mode == 2:
            return types.GetSectorInfoResponseMode2.parse(
                response, byteOrder=self.slaveProperties.byteOrder)

    def programPrepare(self, codesize):
        """Prepare non-volatile memory programming."""
        cs = self.WORD_pack(codesize)
        return self.transport.request(types.Command.PROGRAM_PREPARE, 0x00, *cs)

    def programFormat(self, compressionMethod, encryptionMethod,
                      programmingMethod, accessMethod):
        return self.transport.request(types.Command.PROGRAM_FORMAT,
                                      compressionMethod, encryptionMethod,
                                      programmingMethod, accessMethod)

    def programNext(self, data):
        d = bytearray()
        d.append(len(data))
        if self.slaveProperties.addressGranularity == \
                types.AddressGranularity.DWORD:
            d.extend(b'\x00\x00')  # alignment bytes
        for e in data:
            d.extend(self.AG_pack(e))
        return self.transport.request(types.Command.PROGRAM_NEXT, *d)

    def programMax(self, data):
        d = bytearray()
        if self.slaveProperties.addressGranularity == \
                types.AddressGranularity.WORD:
            d.extend(b'\x00')  # alignment bytes
        elif self.slaveProperties.addressGranularity == \
                types.AddressGranularity.DWORD:
            d.extend(b'\x00\x00\x00')  # alignment bytes
        for e in data:
            d.extend(self.AG_pack(e))
        return self.transport.request(types.Command.PROGRAM_MAX, *d)

    def programVerify(self, verMode, verType, verValue):
        data = bytearray()
        data.extend(self.WORD_pack(verType))
        data.extend(self.DWORD_pack(verValue))
        return self.transport.request(types.Command.PROGRAM_VERIFY, verMode,
                                      *data)

    def timeCorrelationProperties(self, setProperties, getPropertiesRequest,
                                  clusterId):
        response = self.transport.request(
            types.Command.TIME_CORRELATION_PROPERTIES, setProperties,
            getPropertiesRequest, 0, *self.WORD_pack(clusterId))
        return types.TimeCorrelationPropertiesResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @broadcasted
    @wrapped
    def getSlaveID(self, mode: int):
        self.transportLayerCmd(0xff, 'X', 'C', 'P', mode)

    # Convenience Functions.
    def verify(self, addr, length):
        """Convenience function for verification of a data-transfer from slave
        to master (Not part of the XCP Specification).

        Parameters
        ----------
        addr : int
        length : int

        Returns
        -------
        bool
        """
        self.setMta(addr)
        cs = self.buildChecksum(length)
        self.logger.debug("BuildChecksum return'd: 0x{:08X} [{}]".format(
            cs.checksum, cs.checksumType))
        self.setMta(addr)
        data = self.fetch(length)
        cc = checksum.check(data, cs.checksumType)
        self.logger.debug("Our checksum          : 0x{:08X}".format(cc))
        return cs.checksum == cc
示例#8
0
class BaseTransport(metaclass=abc.ABCMeta):
    """Base class for transport-layers (Can, Eth, Sxi).

    Parameters
    ----------
    config: dict-like
        Parameters like bitrate.
    loglevel: ["INFO", "WARN", "DEBUG", "ERROR", "CRITICAL"]
        Controls the verbosity of log messages.

    """

    PARAMETER_MAP = {
        #                         Type    Req'd   Default
        "CREATE_DAQ_TIMESTAMPS": (bool, False, False),
        "LOGLEVEL": (str, False, "WARN"),
        "TIMEOUT": (float, False, 2.0),
        "ALIGNMENT": (int, False, 1),
    }

    def __init__(self, config=None):
        self.parent = None
        self.config = Configuration(BaseTransport.PARAMETER_MAP or {}, config
                                    or {})
        self.closeEvent = threading.Event()
        loglevel = self.config.get("LOGLEVEL")
        self._debug = loglevel == "DEBUG"

        self.logger = Logger("transport.Base")
        self.logger.setLevel(loglevel)
        self.counterSend = 0
        self.counterReceived = -1
        create_daq_timestamps = self.config.get("CREATE_DAQ_TIMESTAMPS")
        self.create_daq_timestamps = False if create_daq_timestamps is None else create_daq_timestamps
        timeout = self.config.get("TIMEOUT")
        self.alignment = self.config.get("ALIGNMENT")
        self.timeout = 2.0 if timeout is None else timeout
        self.timer_restart_event = threading.Event()
        self.timing = Timing()
        self.resQueue = deque()
        self.daqQueue = deque()
        self.evQueue = deque()
        self.servQueue = deque()
        self.listener = threading.Thread(
            target=self.listen,
            args=(),
            kwargs={},
        )
        self.cro_callback = None

        self.first_daq_timestamp = None
        if get_clock_info("time").resolution > 1e-5:
            ts, pc = time_perfcounter_correlation()
            self.timestamp_origin = ts
            self.datetime_origin = datetime.fromtimestamp(ts)
            self.perf_counter_origin = pc
        else:
            self.timestamp_origin = time()
            self.datetime_origin = datetime.fromtimestamp(
                self.timestamp_origin)

            # we will later use this to know if the current platform has a high
            # resolution time.time implementation
            self.perf_counter_origin = -1

        self.pre_send_timestamp = time()
        self.post_send_timestamp = time()
        self.recv_timestamp = time()

    def __del__(self):
        self.finishListener()
        self.closeConnection()

    def loadConfig(self, config):
        """Load configuration data."""
        self.config = Configuration(self.PARAMETER_MAP or {}, config or {})

    def close(self):
        """Close the transport-layer connection and event-loop."""
        self.finishListener()
        if self.listener.is_alive():
            self.listener.join()
        self.closeConnection()

    @abc.abstractmethod
    def connect(self):
        pass

    def startListener(self):
        self.listener.start()

    def finishListener(self):
        if hasattr(self, "closeEvent"):
            self.closeEvent.set()

    def _request_internal(self, cmd, ignore_timeout=False, *data):
        frame = self._prepare_request(cmd, *data)
        self.timing.start()
        self.send(frame)

        try:
            xcpPDU = get(
                self.resQueue,
                timeout=self.timeout,
                restart_event=self.timer_restart_event,
            )
        except Empty:
            if not ignore_timeout:
                raise types.XcpTimeoutError(
                    "Response timed out (timeout={}s)".format(
                        self.timeout)) from None
            else:
                self.timing.stop()
                return
        self.timing.stop()
        pid = types.Response.parse(xcpPDU).type
        if pid == "ERR" and cmd.name != "SYNCH":
            err = types.XcpError.parse(xcpPDU[1:])
            raise types.XcpResponseError(err)
        else:
            pass  # Und nu??
        return xcpPDU[1:]

    def request(self, cmd, *data):
        return self._request_internal(cmd, False, *data)

    def request_optional_response(self, cmd, *data):
        return self._request_internal(cmd, True, *data)

    def block_request(self, cmd, *data):
        """
        Implements packet transmission for block communication model (e.g. DOWNLOAD block mode)
        All parameters are the same as in request(), but it does not receive response.
        """

        # check response queue before each block request, so that if the slave device
        # has responded with a negative response (e.g. ACCESS_DENIED or SEQUENCE_ERROR), we can
        # process it.
        if self.resQueue:
            xcpPDU = self.resQueue.popleft()
            pid = types.Response.parse(xcpPDU).type
            if pid == "ERR" and cmd.name != "SYNCH":
                err = types.XcpError.parse(xcpPDU[1:])
                raise types.XcpResponseError(err)

        frame = self._prepare_request(cmd, *data)
        self.send(frame)

    def _prepare_request(self, cmd, *data):
        """
        Prepares a request to be sent
        """
        if self._debug:
            self.logger.debug(cmd.name)
        self.parent._setService(cmd)

        cmdlen = cmd.bit_length() // 8  # calculate bytes needed for cmd
        packet = bytes(flatten(cmd.to_bytes(cmdlen, "big"), data))

        header = self.HEADER.pack(len(packet), self.counterSend)
        self.counterSend = (self.counterSend + 1) & 0xFFFF

        frame = header + packet

        remainder = len(frame) % self.alignment
        if remainder:
            frame += b"\0" * (self.alignment - remainder)

        if self._debug:
            self.logger.debug("-> {}".format(hexDump(frame)))
        return frame

    def block_receive(self, length_required: int) -> bytes:
        """
        Implements packet reception for block communication model
        (e.g. for XCP on CAN)

        Parameters
        ----------
        length_required: int
            number of bytes to be expected in block response packets

        Returns
        -------
        bytes
            all payload bytes received in block response packets

        Raises
        ------
        :class:`pyxcp.types.XcpTimeoutError`
        """
        block_response = b""
        start = time()
        while len(block_response) < length_required:
            if len(self.resQueue):
                partial_response = self.resQueue.popleft()
                block_response += partial_response[1:]
            else:
                if time() - start > self.timeout:
                    raise types.XcpTimeoutError(
                        "Response timed out [block_receive].") from None
                sleep(0.001)
        return block_response

    @abc.abstractmethod
    def send(self, frame):
        pass

    @abc.abstractmethod
    def closeConnection(self):
        """Does the actual connection shutdown.
        Needs to be implemented by any sub-class.
        """
        pass

    @abc.abstractmethod
    def listen(self):
        pass

    def process_event_packet(self, packet):
        packet = packet[1:]
        ev_type = packet[0]
        self.logger.debug("EVENT-PACKET: {}".format(hexDump(packet)))
        if ev_type == types.Event.EV_CMD_PENDING:
            self.timer_restart_event.set()

    def processResponse(self, response, length, counter, recv_timestamp=None):
        if counter == self.counterReceived:
            self.logger.warn(
                "Duplicate message counter {} received from the XCP slave".
                format(counter))
            if self._debug:
                self.logger.debug("<- L{} C{} {}".format(
                    length,
                    counter,
                    hexDump(response[:512]),
                ))
            return

        self.counterReceived = counter

        pid = response[0]
        if pid >= 0xFC:
            if self._debug:
                self.logger.debug("<- L{} C{} {}".format(
                    length,
                    counter,
                    hexDump(response),
                ))
            if pid >= 0xFE:
                # self.resQueue.put(response)
                self.resQueue.append(response)
                self.recv_timestamp = recv_timestamp
            elif pid == 0xFD:
                # self.evQueue.put(response)
                self.process_event_packet(response)
                self.evQueue.append(response)
            elif pid == 0xFC:
                # self.servQueue.put(response)
                self.servQueue.append(response)
        else:
            if self._debug:
                self.logger.debug("<- L{} C{} ODT_Data[0:8] {}".format(
                    length,
                    counter,
                    hexDump(response[:8]),
                ))
            if self.first_daq_timestamp is None:
                self.first_daq_timestamp = recv_timestamp
            if self.create_daq_timestamps:
                timestamp = recv_timestamp
            else:
                timestamp = 0.0
            element = (response, counter, length, timestamp)
            if self.cro_callback:
                self.cro_callback("DAQ", *element)
            self.daqQueue.append(element)
示例#9
0
文件: base.py 项目: hejunbok/pyxcp
class MasterBaseType:
    """Common part of lowlevel XCP API.

    Parameters
    ----------
    transportName : str
        XCP transport layer name ['can', 'eth', 'sxi']
    config: dict
    """

    PARAMETER_MAP = {
        #                         Type    Req'd   Default
        "LOGLEVEL": (str, False, "WARN"),
    }

    def __init__(self, transportName, config=None):
        self.ctr = 0
        self.succeeded = True
        self.config = Configuration(MasterBaseType.PARAMETER_MAP or {}, config
                                    or {})
        self.logger = logging.getLogger("pyXCP")
        self.logger.setLevel(self.config.get("LOGLEVEL"))
        self.transport = createTransport(transportName, config)

        # In some cases the transport-layer needs to communicate with us.
        self.transport.parent = self
        self.service = None

        # (D)Word (un-)packers are byte-order dependent
        # -- byte-order is returned by CONNECT_Resp (COMM_MODE_BASIC)
        self.WORD_pack = None
        self.WORD_unpack = None
        self.DWORD_pack = None
        self.DWORD_unpack = None
        self.AG_pack = None
        self.AG_unpack = None
        # self.connected = False
        self.mta = types.MtaType(None, None)
        self.currentDaqPtr = None
        self.currentProtectionStatus = None
        self._seedNKeyDLL = None

    def __enter__(self):
        """Context manager entry part.
        """
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit part.
        """
        # if self.connected:
        #    self.disconnect()
        self.close()
        if exc_type is None:
            return
        else:
            self.succeeded = False
            # print("=" * 79)
            # print("Exception while in Context-Manager:\n")
            self.logger.error(''.join(
                traceback.format_exception(exc_type, exc_val, exc_tb)))
            # print("=" * 79)
            # return True

    def _setService(self, service):
        """Records the currently processed service.

        Parameters
        ----------
        service: `pydbc.types.Command`

        Note
        ----
        Internal Function, only to be used by transport-layer.
        """
        self.service = service

    def close(self):
        """Closes transport layer connection.
        """
        self.transport.close()

    # Mandatory Commands.
    @wrapped
    def connect(self, mode=0x00):
        """Build up connection to an XCP slave.

        Before the actual XCP traffic starts a connection is required.

        Parameters
        ----------
        mode : int
            connection mode; default is 0x00 (normal mode)

        Returns
        -------
        :py:obj:`pyxcp.types.ConnectResponse`
            Describes fundamental client properties.

        Note
        ----
        Every XCP slave supports at most one connection,
        more attempts to connect are silently ignored.

        """
        self.transport.connect()

        response = self.transport.request(types.Command.CONNECT, mode & 0xFF)

        # First get byte-order
        resultPartial = types.ConnectResponsePartial.parse(response)
        byteOrder = resultPartial.commModeBasic.byteOrder

        result = types.ConnectResponse.parse(response, byteOrder=byteOrder)
        self.slaveProperties = SlaveProperties(byteOrder=byteOrder,
                                               maxCto=result.maxCto,
                                               maxDto=result.maxDto)
        byteOrderPrefix = "<" if byteOrder == types.ByteOrder.INTEL else ">"

        self.slaveProperties.supportsPgm = result.resource.pgm
        self.slaveProperties.supportsStim = result.resource.stim
        self.slaveProperties.supportsDaq = result.resource.daq
        self.slaveProperties.supportsCalpag = result.resource.calpag
        self.slaveProperties.slaveBlockMode = \
            result.commModeBasic.slaveBlockMode
        self.slaveProperties.addressGranularity = \
            result.commModeBasic.addressGranularity
        self.slaveProperties.protocolLayerVersion = result.protocolLayerVersion
        self.slaveProperties.transportLayerVersion = \
            result.transportLayerVersion
        self.slaveProperties.optionalCommMode = \
            result.commModeBasic.optional
        self.slaveProperties.maxWriteDaqMultipleElements = \
            0 if self.slaveProperties.maxCto < 10 else int((self.slaveProperties.maxCto - 2) // 8)

        self.WORD_pack = makeWordPacker(byteOrderPrefix)
        self.DWORD_pack = makeDWordPacker(byteOrderPrefix)
        self.WORD_unpack = makeWordUnpacker(byteOrderPrefix)
        self.DWORD_unpack = makeDWordUnpacker(byteOrderPrefix)

        if self.slaveProperties.addressGranularity == \
                types.AddressGranularity.BYTE:
            self.AG_pack = struct.Struct("<B").pack
            self.AG_unpack = struct.Struct("<B").unpack
        elif self.slaveProperties.addressGranularity == \
                types.AddressGranularity.WORD:
            self.AG_pack = self.WORD_pack
            self.AG_unpack = self.WORD_unpack
        elif self.slaveProperties.addressGranularity == \
                types.AddressGranularity.DWORD:
            self.AG_pack = self.DWORD_pack
            self.AG_unpack = self.DWORD_unpack
        # self.connected = True
        return result

    @wrapped
    def disconnect(self):
        """Releases the connection to the XCP slave.

        Thereafter, no further communication with the slave is possible
        (besides `connect`).


        Note
        -----
        If DISCONNECT is currently not possible, ERR_CMD_BUSY will be returned.
        """
        response = self.transport.request(types.Command.DISCONNECT)
        # self.connected = False
        return response

    @wrapped
    def getStatus(self):
        """Get current status information of the slave device.

        This includes the status of the resource protection, pending store
        requests and the general status of data acquisition and stimulation.

        Returns
        -------
        :obj:`pyxcp.types.GetStatusResponse`
        """
        response = self.transport.request(types.Command.GET_STATUS)
        result = types.GetStatusResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        self._setProtectionStatus(result.resourceProtectionStatus)
        return result

    @wrapped
    def synch(self):
        """Synchronize command execution after timeout conditions.

        """
        response = self.transport.request(types.Command.SYNCH)
        return response

    @wrapped
    def getCommModeInfo(self):
        """Get optional information on different Communication Modes supported
        by the slave.

        Returns
        -------
        :obj:`pyxcp.types.GetCommModeInfoResponse`
        """
        response = self.transport.request(types.Command.GET_COMM_MODE_INFO)
        result = types.GetCommModeInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        self.slaveProperties.interleavedMode = result.commModeOptional.interleavedMode
        self.slaveProperties.masterBlockMode = result.commModeOptional.masterBlockMode
        self.slaveProperties.maxBs = result.maxBs
        self.slaveProperties.minSt = result.minSt
        self.slaveProperties.queueSize = result.queueSize
        self.slaveProperties.xcpDriverVersionNumber = result.xcpDriverVersionNumber
        return result

    @wrapped
    def getId(self, mode: int):
        """This command is used for automatic session configuration and for
        slave device identification.

        Parameters
        ----------
        mode : int
            The following identification types may be requested:
            - 0        ASCII text
            - 1        ASAM-MC2 filename without path and extension
            - 2        ASAM-MC2 filename with path and extension
            - 3        URL where the ASAM-MC2 file can be found
            - 4        ASAM-MC2 file to upload
            - 128..255 User defined

        Returns
        -------
        :obj:`pydbc.types.GetIDResponse`
        """
        response = self.transport.request(types.Command.GET_ID, mode)
        result = types.GetIDResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        result.length = self.DWORD_unpack(response[3:7])[0]
        return result

    @wrapped
    def setRequest(self, mode: int, sessionConfigurationId: int):
        """Request to save to non-volatile memory.

        Parameters
        ----------
        mode : int (bitfield)
            - 1  Request to store calibration data
            - 2  Request to store DAQ list, no resume
            - 4  Request to store DAQ list, resume enabled
            - 8  Request to clear DAQ configuration
        sessionConfigurationId : int

        """
        response = self.transport.request(types.Command.SET_REQUEST, mode,
                                          sessionConfigurationId >> 8,
                                          sessionConfigurationId & 0xff)
        return response

    @wrapped
    def getSeed(self, first: int, resource: int):
        """Get seed from slave for unlocking a protected resource.

        Parameters
        ----------
        first : int
            - 0 - first part of seed
            - 1 - remaining part
        resource : int
            - Mode = =0 - Resource
            - Mode == 1 - Don't care

        Returns
        -------
        `pydbc.types.GetSeedResponse`
        """
        response = self.transport.request(types.Command.GET_SEED, first,
                                          resource)
        return types.GetSeedResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def unlock(self, length: int, key: bytes):
        """Send key to slave for unlocking a protected resource.

        Parameters
        ----------
        length : int
            indicates the (remaining) number of key bytes.
        key : bytes

        Returns
        -------
        :obj:`pydbc.types.ResourceType`

        Note
        ----
        The master has to use :meth:`unlock` in a defined sequence together
        with :meth:`getSeed`. The master only can send an :meth:`unlock` sequence
        if previously there was a :meth:`getSeed` sequence. The master has
        to send the first `unlocking` after a :meth:`getSeed` sequence with
        a Length containing the total length of the key.
        """
        response = self.transport.request(types.Command.UNLOCK, length, *key)
        result = types.ResourceType.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        self._setProtectionStatus(result)
        return result

    @wrapped
    def setMta(self, address: int, addressExt: int = 0x00):
        """Set Memory Transfer Address in slave.

        Parameters
        ----------
        address : int
        addressExt : int

        Note
        ----
        The MTA is used by :meth:`buildChecksum`, :meth:`upload`, :meth:`download`, :meth:`downloadNext`,
        :meth:`downloadMax`, :meth:`modifyBits`, :meth:`programClear`, :meth:`program`, :meth:`programNext`
        and :meth:`programMax`.

        """
        self.mta = types.MtaType(
            address,
            addressExt)  # Keep track of MTA (needed for error-handling).
        addr = self.DWORD_pack(address)
        response = self.transport.request(types.Command.SET_MTA, 0, 0,
                                          addressExt, *addr)
        return response

    @wrapped
    def upload(self, length: int):
        """Transfer data from slave to master.

        Parameters
        ----------
        length : int

        Note
        ----
        Adress is set via :meth:`setMta` (Some services like :meth:`getID` also set the MTA).

        Returns
        -------
        bytes
        """

        response = self.transport.request(types.Command.UPLOAD, length)
        if length > (self.slaveProperties.maxCto - 1):
            block_response = self.transport.block_receive(
                length_required=(length - len(response)))
            response += block_response
        return response

    @wrapped
    def shortUpload(self, length: int, address: int, addressExt: int = 0x00):
        """Transfer data from slave to master.
        As opposed to :meth:`upload` this service also includes address information.

        Parameters
        ----------
        address : int
        addressExt : int

        Returns
        -------
        bytes
        """
        addr = self.DWORD_pack(address)
        response = self.transport.request(types.Command.SHORT_UPLOAD, length,
                                          0, addressExt, *addr)
        return response

    @wrapped
    def buildChecksum(self, blocksize: int):
        """Build checksum over memory range.

        Parameters
        ----------
        blocksize : int

        Returns
        -------
        :obj:`~pyxcp.types.BuildChecksumResponse`

        .. note:: Adress is set via `setMta`

        See Also
        --------
        :mod:`~pyxcp.checksum`
        """
        bs = self.DWORD_pack(blocksize)
        response = self.transport.request(types.Command.BUILD_CHECKSUM, 0, 0,
                                          0, *bs)
        return types.BuildChecksumResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def transportLayerCmd(self, subCommand: int, data: bytes):
        """Execute transfer-layer specific command.

        Parameters
        ----------
        subCommand : int
        data : bytes

        Note
        ----
        For details refer to XCP specification.
        """
        response = self.transport.request(types.Command.TRANSPORT_LAYER_CMD,
                                          subCommand, *data)
        return response

    @wrapped
    def userCmd(self, subCommand: int, data: bytes):
        """Execute proprietary command implemented in your XCP client.

        Parameters
        ----------
        subCommand : int
        data : bytes


        .. note:: For details refer to your XCP client vendor.
        """

        response = self.transport.request(types.Command.USER_CMD, subCommand,
                                          *data)
        return response

    @wrapped
    def getVersion(self):
        """Get version information.

        This command returns detailed information about the implemented
        protocol layer version of the XCP slave and the transport layer
        currently in use.

        Returns
        -------
        :obj:`~types.GetVersionResponse`
        """

        response = self.transport.request(types.Command.GET_VERSION)
        result = types.GetVersionResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        self.slaveProperties.protocolMajor = result.protocolMajor
        self.slaveProperties.protocolMinor = result.protocolMinor
        self.slaveProperties.transportMajor = result.transportMajor
        self.slaveProperties.transportMinor = result.transportMinor
        return result

    def fetch(self, length: int, limitPayload: int = None):  # TODO: pull
        """Convenience function for data-transfer from slave to master
        (Not part of the XCP Specification).

        Parameters
        ----------
        length : int
        limitPayload : int
            transfer less bytes then supported by transport-layer

        Returns
        -------
        bytes

        Note
        ----
        address is not included because of services implicitly setting address information like :meth:`getID` .
        """
        if limitPayload and limitPayload < 8:
            raise ValueError(
                "Payload must be at least 8 bytes - given: {}".format(
                    limitPayload))

        slaveBlockMode = self.slaveProperties.slaveBlockMode
        if slaveBlockMode:
            maxPayload = 255
        else:
            maxPayload = self.slaveProperties.maxCto - 1
        payload = min(limitPayload, maxPayload) if limitPayload else maxPayload
        chunkSize = payload
        chunks = range(length // chunkSize)
        remaining = length % chunkSize
        result = []
        for _ in chunks:
            data = self.upload(chunkSize)
            result.extend(data)
        if remaining:
            data = self.upload(remaining)
            result.extend(data)
        return bytes(result)

    pull = fetch  # fetch() may be completely replaced by pull() someday.

    def push(self, data: bytes):
        """Convenience function for data-transfer from master to slave.
        (Not part of the XCP Specification).

        Parameters
        ----------
        data : bytes
            Arbitrary number of bytes.

        Returns
        -------

        Note
        ----
        address is not included because of services implicitly setting address information like :meth:`getID` .
        """
        # TODO: consider minST.

        total_length = len(data)
        master_block_mode = self.slaveProperties.masterBlockMode
        if master_block_mode:
            max_payload = min(
                self.slaveProperties.maxBs * (self.slaveProperties.maxCto - 2),
                255)
        else:
            max_payload = self.slaveProperties.maxCto - 2
        offset = 0
        if master_block_mode:
            payload_length = self.slaveProperties.maxCto - 2
            rem2 = total_length
            blocks = range(total_length // max_payload)
            remaining_block_size = total_length % max_payload
            for idx in blocks:
                data_slice = data[offset:offset + max_payload]
                self._block_downloader(data_slice)
                offset += max_payload
                rem2 -= max_payload
            if remaining_block_size:
                data_slice = data[offset:offset + remaining_block_size]
                self._block_downloader(data_slice)
        else:
            chunk_size = max_payload
            chunks = range(total_length // chunk_size)
            remaining = total_length % chunk_size
            for _ in chunks:
                frame_data = data[offset:offset + max_payload]
                self.download(frame_data, max_payload)
                offset += max_payload
            if remaining:
                frame_data = data[offset:offset + remaining]
                self.download(frame_data, remaining)

    def _block_downloader(self, data: bytes):
        """
        """
        length = len(data)
        payload_length = self.slaveProperties.maxCto - 2
        chunks = range(length // payload_length)
        offset = 0
        remaining = length % payload_length
        rem2 = length
        idx = 0
        for idx in chunks:
            frame_data = data[offset:offset + payload_length]
            if idx == 0:
                self.download(
                    frame_data,
                    length)  # Transmit the complete length in the first CTO.
            else:
                self.downloadNext(frame_data, rem2)
            offset += payload_length
            rem2 -= payload_length
        if remaining:
            frame_data = data[offset:offset + remaining]
            if idx == 0:
                # length of data is smaller than maxCto - 2
                self.download(frame_data, remaining)
            else:
                self.downloadNext(frame_data, remaining)

    @wrapped
    def download(self, data: bytes, blockModeLength=None):
        """Transfer data from master to slave.

        Parameters
        ----------
        data : bytes
            Data to send to slave.
        blockModeLength : int or None
            for block mode, the download request must contain the length of the whole block,
            not just the length in the current packet. The whole block length can be given here for block-mode
            transfers. For normal mode, the length indicates the actual packet's payload length.

        Note
        ----
        Adress is set via :meth:`setMta`
        """

        if blockModeLength is None:
            # standard mode
            length = len(data)
            response = self.transport.request(types.Command.DOWNLOAD, length,
                                              *data)
            return response
        else:
            # block mode
            if not isinstance(blockModeLength, int):
                raise TypeError('blockModeLength must be int!')
            self.transport.block_request(types.Command.DOWNLOAD,
                                         blockModeLength, *data)
            return None

    @wrapped
    def downloadNext(self, data: bytes, remainingBlockLength, last=False):
        """Transfer data from master to slave (block mode).

        Parameters
        ----------
        data : bytes
        remainingBlockLength : int
            This parameter has to be given the remaining length in the block
        last : bool
            The block mode implementation shall indicate the last packet in the block with this parameter, because
            the slave device will send the response after this.
        """

        if last:
            # last DOWNLOAD_NEXT packet in a block: the slave device has to send the response after this.
            response = self.transport.request(types.Command.DOWNLOAD_NEXT,
                                              remainingBlockLength, *data)
            return response
        else:
            # the slave device won't respond to consecutive DOWNLOAD_NEXT packets in block mode,
            # so we must not wait for any response
            self.transport.block_request(types.Command.DOWNLOAD_NEXT,
                                         remainingBlockLength, *data)
            return None

    @wrapped
    def downloadMax(self, data: bytes):
        """Transfer data from master to slave (fixed size).

        Parameters
        ----------
        data : bytes
        """
        response = self.transport.request(types.Command.DOWNLOAD_MAX, *data)
        return response

    # Page Switching Commands (PAG)
    @wrapped
    def setCalPage(self, mode: int, logicalDataSegment: int,
                   logicalDataPage: int):
        """Set calibration page.

        Parameters
        ----------
        mode : int (bitfield)
            - 0x01 - The given page will be used by the slave device application.
            - 0x02 - The slave device XCP driver will access the given page.
            - 0x80 - The logical segment number is ignored. The command applies to all segments
        logicalDataSegment : int
        logicalDataPage : int
        """
        response = self.transport.request(types.Command.SET_CAL_PAGE, mode,
                                          logicalDataSegment, logicalDataPage)
        return response

    @wrapped
    def getCalPage(self, mode: int, logicalDataSegment: int):
        """Get calibration page

        Parameters
        ----------
        mode : int
        logicalDataSegment : int
        """
        response = self.transport.request(types.Command.GET_CAL_PAGE, mode,
                                          logicalDataSegment)
        return response[2]

    @wrapped
    def getPagProcessorInfo(self):
        """Get general information on PAG processor.

        Returns
        -------
        `pydbc.types.GetPagProcessorInfoResponse`
    """
        response = self.transport.request(types.Command.GET_PAG_PROCESSOR_INFO)
        return types.GetPagProcessorInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getSegmentInfo(self, mode, segmentNumber, segmentInfo, mappingIndex):
        """Get specific information for a segment.

        Parameters
        ----------
        mode : int
            - 0 = get basic address info for this segment
            - 1 = get standard info for this segment
            - 2 = get address mapping info for this segment

        segmentNumber : int
        segmentInfo : int
            Mode 0:
                - 0 = address
                - 1 = length

            Mode 1:
                - don't care

            Mode 2:
                - 0 = source address
                - 1 = destination address
                - 2 = length address

        mappingIndex : int
            - Mode 0: don't care
            - Mode 1: don't care
            - Mode 2: identifier for address mapping range that mapping_info belongs to.

        """
        response = self.transport.request(types.Command.GET_SEGMENT_INFO, mode,
                                          segmentNumber, segmentInfo,
                                          mappingIndex)
        if mode == 0:
            return types.GetSegmentInfoMode0Response.parse(
                response, byteOrder=self.slaveProperties.byteOrder)
        elif mode == 1:
            return types.GetSegmentInfoMode1Response.parse(
                response, byteOrder=self.slaveProperties.byteOrder)
        elif mode == 2:
            return types.GetSegmentInfoMode2Response.parse(
                response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getPageInfo(self, segmentNumber, pageNumber):
        """Get specific information for a page.

        Parameters
        ----------
        segmentNumber : int
        pageNumber : int
        """
        response = self.transport.request(types.Command.GET_PAGE_INFO, 0,
                                          segmentNumber, pageNumber)
        return (types.PageProperties.parse(
            bytes([response[0]]),
            byteOrder=self.slaveProperties.byteOrder), response[1])

    @wrapped
    def setSegmentMode(self, mode, segmentNumber):
        """Set mode for a segment.

        Parameters
        ----------
        mode : int (bitfield)
            1 = enable FREEZE Mode
        segmentNumber : int
        """
        response = self.transport.request(types.Command.SET_SEGMENT_MODE, mode,
                                          segmentNumber)
        return response

    @wrapped
    def getSegmentMode(self, segmentNumber):
        """Get mode for a segment.

        Parameters
        ----------
        segmentNumber : int
        """
        response = self.transport.request(types.Command.GET_SEGMENT_MODE, 0,
                                          segmentNumber)
        return response[1]

    @wrapped
    def copyCalPage(self, srcSegment, srcPage, dstSegment, dstPage):
        """Copy page.

        Parameters
        ----------
        srcSegment : int
        srcPage : int
        dstSegment : int
        dstPage : int
        """
        response = self.transport.request(types.Command.COPY_CAL_PAGE,
                                          srcSegment, srcPage, dstSegment,
                                          dstPage)
        return response

    # DAQ
    @wrapped
    def clearDaqList(self, daqListNumber):
        """Clear DAQ list configuration.

        Parameters
        ----------
        daqListNumber : int
        """
        daqList = self.WORD_pack(daqListNumber)
        response = self.transport.request(types.Command.CLEAR_DAQ_LIST, 0,
                                          *daqList)
        return response

    @wrapped
    def writeDaq(self, bitOffset, entrySize, addressExt, address):
        """Write element in ODT entry.

        Parameters
        ----------
        bitOffset : int
            Position of bit in 32-bit variable referenced by the address and
            extension below
        entrySize : int
        addressExt : int
        address : int
        """
        addr = self.DWORD_pack(address)
        response = self.transport.request(types.Command.WRITE_DAQ, bitOffset,
                                          entrySize, addressExt, *addr)
        return response

    @wrapped
    def getDaqListMode(self, daqListNumber):
        """Get mode from DAQ list.

        Parameters
        ----------
        daqListNumber : int

        Returns
        -------
        `pyxcp.types.GetDaqListModeResponse`
        """
        dln = self.WORD_pack(daqListNumber)
        response = self.transport.request(types.Command.GET_DAQ_LIST_MODE, 0,
                                          *dln)
        return types.GetDaqListModeResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def startStopDaqList(self, mode, daqListNumber):
        """Start /stop/select DAQ list.

        Parameters
        ----------
        mode : int
            0 = stop
            1 = start
            2 = select
        daqListNumber : int
        """
        dln = self.WORD_pack(daqListNumber)
        response = self.transport.request(types.Command.START_STOP_DAQ_LIST,
                                          mode, *dln)
        return types.StartStopDaqListResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def startStopSynch(self, mode):
        """Start/stop DAQ lists (synchronously).

        Parameters
        ----------
        mode : int
            0 = stop all
            1 = start selected
            2 = stop selected
        """
        response = self.transport.request(types.Command.START_STOP_SYNCH, mode)
        return response

    @wrapped
    def writeDaqMultiple(self, daqElements):
        """Write multiple elements in ODT.

        Parameters
        ----------
        daqElements : list of DAQ elements
        """
        if len(daqElements) > self.slaveProperties.maxWriteDaqMultipleElements:
            raise ValueError("At most {} daqElements are permitted.".format(
                self.slaveProperties.maxWriteDaqMultipleElements))
        data = bytearray()
        data.append(len(daqElements))

        for daqElement in daqElements:
            data.extend(
                types.DaqElement.build(
                    daqElement, byteOrder=self.slaveProperties.byteOrder))

        response = self.transport.request(types.Command.WRITE_DAQ_MULTIPLE,
                                          *data)
        return response

    # optional
    @wrapped
    def getDaqClock(self):
        """Get DAQ clock from slave.

        Returns
        -------
        int
            Current timestamp, format specified by `getDaqResolutionInfo`
        """
        response = self.transport.request(types.Command.GET_DAQ_CLOCK)
        result = types.GetDaqClockResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        return result.timestamp

    @wrapped
    def readDaq(self):
        """Read element from ODT entry.

        Returns
        -------
        `pyxcp.types.ReadDaqResponse`
        """
        response = self.transport.request(types.Command.READ_DAQ)
        return types.ReadDaqResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getDaqProcessorInfo(self):
        """Get general information on DAQ processor.

        Returns
        -------
        `pyxcp.types.GetDaqProcessorInfoResponse`
        """
        response = self.transport.request(types.Command.GET_DAQ_PROCESSOR_INFO)
        return types.GetDaqProcessorInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getDaqResolutionInfo(self):
        """Get general information on DAQ processing resolution.

        Returns
        -------
        `pyxcp.types.GetDaqResolutionInfoResponse`
        """
        response = self.transport.request(
            types.Command.GET_DAQ_RESOLUTION_INFO)
        return types.GetDaqResolutionInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getDaqListInfo(self, daqListNumber):
        """Get specific information for a DAQ list.

        Parameters
        ----------
        daqListNumber : int
        """
        dln = self.WORD_pack(daqListNumber)
        response = self.transport.request(types.Command.GET_DAQ_LIST_INFO, 0,
                                          *dln)
        return types.GetDaqListInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getDaqEventInfo(self, eventChannelNumber):
        """Get specific information for an event channel.

        Parameters
        ----------
        eventChannelNumber : int

        Returns
        -------
        `pyxcp.types.GetEventChannelInfoResponse`
        """
        ecn = self.WORD_pack(eventChannelNumber)
        response = self.transport.request(types.Command.GET_DAQ_EVENT_INFO, 0,
                                          *ecn)
        return types.GetEventChannelInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def dtoCtrProperties(self, modifier, eventChannel, relatedEventChannel,
                         mode):
        """DTO CTR properties

        Parameters
        ----------
        modifier :
        eventChannel : int
        relatedEventChannel : int
        mode :

        Returns
        -------
        `pyxcp.types.DtoCtrPropertiesResponse`
        """
        data = bytearray()
        data.append(modifier)
        data.extend(self.WORD_pack(eventChannel))
        data.extend(self.WORD_pack(relatedEventChannel))
        data.append(mode)
        response = self.transport.request(types.Command.DTO_CTR_PROPERTIES,
                                          *data)
        return types.DtoCtrPropertiesResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def setDaqPackedMode(self,
                         daqListNumber,
                         daqPackedMode,
                         dpmTimestampMode=None,
                         dpmSampleCount=None):
        """Set DAQ List Packed Mode.

        Parameters
        ----------
        daqListNumber : int
        daqPackedMode : int
        """
        params = []
        dln = self.WORD_pack(daqListNumber)
        params.extend(dln)
        params.append(daqPackedMode)

        if daqPackedMode == 1 or daqPackedMode == 2:
            params.append(dpmTimestampMode)
            dsc = self.WORD_pack(dpmSampleCount)
            params.extend(dsc)

        response = self.transport.request(types.Command.SET_DAQ_PACKED_MODE,
                                          *params)
        return response

    @wrapped
    def getDaqPackedMode(self, daqListNumber):
        """Get DAQ List Packed Mode.

        This command returns information of the currently active packed mode of
        the addressed DAQ list.

        Parameters
        ----------
        daqListNumber : int
        """
        dln = self.WORD_pack(daqListNumber)
        response = self.transport.request(types.Command.GET_DAQ_PACKED_MODE,
                                          *dln)
        result = types.GetDaqPackedModeResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)
        return result

    # dynamic
    @wrapped
    def freeDaq(self):
        """Clear dynamic DAQ configuration.
        """
        response = self.transport.request(types.Command.FREE_DAQ)
        return response

    @wrapped
    def allocDaq(self, daqCount):
        """Allocate DAQ lists.

        Parameters
        ----------
        daqCount : int
            number of DAQ lists to be allocated
        """
        dq = self.WORD_pack(daqCount)
        response = self.transport.request(types.Command.ALLOC_DAQ, 0, *dq)
        return response

    # PGM
    @wrapped
    def programStart(self):
        """Indicate the beginning of a programming sequence.

        Returns
        -------
        `pyxcp.types.ProgramStartResponse`
        """
        response = self.transport.request(types.Command.PROGRAM_START)
        return types.ProgramStartResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def programClear(self, mode: int, clearRange: int):
        """Clear a part of non-volatile memory.

        Parameters
        ----------
        mode : int
            0x00 = the absolute access mode is active (default)
            0x01 = the functional access mode is active
        clearRange : int
        """
        cr = self.DWORD_pack(clearRange)
        response = self.transport.request(types.Command.PROGRAM_CLEAR, mode, 0,
                                          0, *cr)
        # ERR_ACCESS_LOCKED
        return response

    @wrapped
    def program(self, data: bytes):
        """
        """
        d = bytearray()
        d.append(len(data))
        if self.slaveProperties.addressGranularity == \
                types.AddressGranularity.DWORD:
            d.extend(b'\x00\x00')  # alignment bytes
        for e in data:
            d.extend(self.AG_pack(e))
        return self.transport.request(types.Command.PROGRAM, *d)

    @wrapped
    def programReset(self):
        """Indicate the end of a programming sequence."""
        return self.transport.request(types.Command.PROGRAM_RESET)

    @wrapped
    def getPgmProcessorInfo(self):
        """Get general information on PGM processor."""
        response = self.transport.request(types.Command.GET_PGM_PROCESSOR_INFO)
        return types.GetPgmProcessorInfoResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def getSectorInfo(self, mode, sectorNumber):
        """Get specific information for a sector."""
        response = self.transport.request(types.Command.GET_SECTOR_INFO, mode,
                                          sectorNumber)
        if mode == 0 or mode == 1:
            return types.GetSectorInfoResponseMode01.parse(
                response, byteOrder=self.slaveProperties.byteOrder)
        elif mode == 2:
            return types.GetSectorInfoResponseMode2.parse(
                response, byteOrder=self.slaveProperties.byteOrder)

    @wrapped
    def programPrepare(self, codesize):
        """Prepare non-volatile memory programming."""
        cs = self.WORD_pack(codesize)
        return self.transport.request(types.Command.PROGRAM_PREPARE, 0x00, *cs)

    @wrapped
    def programFormat(self, compressionMethod, encryptionMethod,
                      programmingMethod, accessMethod):
        return self.transport.request(types.Command.PROGRAM_FORMAT,
                                      compressionMethod, encryptionMethod,
                                      programmingMethod, accessMethod)

    @wrapped
    def programNext(self, data):
        d = bytearray()
        d.append(len(data))
        if self.slaveProperties.addressGranularity == \
                types.AddressGranularity.DWORD:
            d.extend(b'\x00\x00')  # alignment bytes
        for e in data:
            d.extend(self.AG_pack(e))
        return self.transport.request(types.Command.PROGRAM_NEXT, *d)

    @wrapped
    def programMax(self, data):
        d = bytearray()
        if self.slaveProperties.addressGranularity == \
                types.AddressGranularity.WORD:
            d.extend(b'\x00')  # alignment bytes
        elif self.slaveProperties.addressGranularity == \
                types.AddressGranularity.DWORD:
            d.extend(b'\x00\x00\x00')  # alignment bytes
        for e in data:
            d.extend(self.AG_pack(e))
        return self.transport.request(types.Command.PROGRAM_MAX, *d)

    @wrapped
    def programVerify(self, verMode, verType, verValue):
        data = bytearray()
        data.extend(self.WORD_pack(verType))
        data.extend(self.DWORD_pack(verValue))
        return self.transport.request(types.Command.PROGRAM_VERIFY, verMode,
                                      *data)

    @wrapped
    def timeCorrelationProperties(self, setProperties, getPropertiesRequest,
                                  clusterId):
        response = self.transport.request(
            types.Command.TIME_CORRELATION_PROPERTIES, setProperties,
            getPropertiesRequest, 0, *self.WORD_pack(clusterId))
        return types.TimeCorrelationPropertiesResponse.parse(
            response, byteOrder=self.slaveProperties.byteOrder)

    @broadcasted
    @wrapped
    def getSlaveID(self, mode: int):
        self.transportLayerCmd(0xff, 'X', 'C', 'P', mode)

    # Convenience Functions.
    def verify(self, addr, length):
        """Convenience function for verification of a data-transfer from slave
        to master (Not part of the XCP Specification).

        Parameters
        ----------
        addr : int
        length : int

        Returns
        -------
        bool
        """
        self.setMta(addr)
        cs = self.buildChecksum(length)
        self.logger.debug("BuildChecksum return'd: 0x{:08X} [{}]".format(
            cs.checksum, cs.checksumType))
        self.setMta(addr)
        data = self.fetch(length)
        cc = checksum.check(data, cs.checksumType)
        self.logger.debug("Our checksum          : 0x{:08X}".format(cc))
        return cs.checksum == cc

    def getDaqInfo(self):
        """Get DAQ information: processor, resolution, events.
        """
        result = {}
        dpi = self.getDaqProcessorInfo()
        processorInfo = {
            "minDaq": dpi["minDaq"],
            "maxDaq": dpi["maxDaq"],
            "properties": {
                "configType": dpi["daqProperties"]["daqConfigType"],
                "overloadEvent": dpi["daqProperties"]["overloadEvent"],
                "overloadMsb": dpi["daqProperties"]["overloadMsb"],
                "prescalerSupported":
                dpi["daqProperties"]["prescalerSupported"],
                "pidOffSupported": dpi["daqProperties"]["pidOffSupported"],
                "timestampSupported":
                dpi["daqProperties"]["timestampSupported"],
                "bitStimSupported": dpi["daqProperties"]["bitStimSupported"],
                "resumeSupported": dpi["daqProperties"]["resumeSupported"],
            },
            "keyByte": {
                "identificationField":
                dpi["daqKeyByte"]["Identification_Field"],
                "addressExtension": dpi["daqKeyByte"]["Address_Extension"],
                "optimisationType": dpi["daqKeyByte"]["Optimisation_Type"],
            },
        }
        result["processor"] = processorInfo

        dri = self.getDaqResolutionInfo()
        resolutionInfo = {
            "timestampTicks": dri["timestampTicks"],
            "maxOdtEntrySizeDaq": dri["maxOdtEntrySizeDaq"],
            "maxOdtEntrySizeStim": dri["maxOdtEntrySizeStim"],
            "granularityOdtEntrySizeDaq": dri["granularityOdtEntrySizeDaq"],
            "granularityOdtEntrySizeStim": dri["granularityOdtEntrySizeStim"],
            "timestampMode": {
                "unit": dri["timestampMode"]["unit"],
                "fixed": dri["timestampMode"]["fixed"],
                "size": dri["timestampMode"]["size"],
            }
        }
        result["resolution"] = resolutionInfo

        channels = []
        for ecn in range(dpi.maxEventChannel):
            eci = self.getDaqEventInfo(ecn)
            name = self.fetch(eci.eventChannelNameLength)
            channel = {
                "name": name.decode("latin-1"),
                "priority": eci["eventChannelPriority"],
                "unit": eci["eventChannelTimeUnit"],
                "cycle": eci["eventChannelTimeCycle"],
                "maxDaqList": eci["maxDaqList"],
                "properties": {
                    "consistency": eci["daqEventProperties"]["consistency"],
                    "daq": eci["daqEventProperties"]["daq"],
                    "stim": eci["daqEventProperties"]["stim"],
                    "packed": eci["daqEventProperties"]["packed"],
                }
            }
            channels.append(channel)
        result["channels"] = channels
        return result

    def getCurrentProtectionStatus(self):
        """
        """
        if self.currentProtectionStatus is None:
            status = self.getStatus()
            self._setProtectionStatus(status.resourceProtectionStatus)
        return self.currentProtectionStatus

    def _setProtectionStatus(self, protection):
        """
        """
        self.currentProtectionStatus = {
            "dbg": protection.dbg,
            "pgm": protection.pgm,
            "stim": protection.stim,
            "daq": protection.daq,
            "calpag": protection.calpag,
        }

    def cond_unlock(self, resources=None):
        """Conditionally unlock resources, i.e. only unlock locked resources.

        Precondition: Must assign :attr:`seedNKeyDLL`, e.g. ``master.seedNKeyDLL = "SeedNKeyXcp.dll"``

        Parameters
        ----------
        resources: str
            Comma or space separated list of resources, e.g. "DAQ, CALPAG".
            The names are not case-sensitive.
            Valid identifiers are: "calpag", "daq", "dbg", "pgm", "stim".

            If omitted, try to unlock every available resource.

        Raises
        ------
        ValueError
            Invalid resource name.

        `dllif.SeedNKeyError`
            In case of DLL related issues.
        """
        import re
        from pyxcp.dllif import getKey, SeedNKeyResult, SeedNKeyError

        MAX_PAYLOAD = self.slaveProperties["maxCto"] - 2

        if self._seedNKeyDLL is None:
            raise RuntimeError(
                "No seed and key DLL specified, cannot proceed.")
        if resources is None:
            result = []
            if self.slaveProperties['supportsCalpag']:
                result.append("calpag")
            if self.slaveProperties['supportsDaq']:
                result.append("daq")
            if self.slaveProperties['supportsStim']:
                result.append("stim")
            if self.slaveProperties['supportsPgm']:
                result.append("pgm")
            resources = ",".join(result)
        protection_status = self.getCurrentProtectionStatus()
        resource_names = [r.lower() for r in re.split(r"[ ,]", resources) if r]
        for name in resource_names:
            if not name in types.RESOURCE_VALUES:
                raise ValueError("Invalid resource name '{}'.".format(name))
            if protection_status[name] == False:
                continue
            resource_value = types.RESOURCE_VALUES[name]
            result = self.getSeed(types.XcpGetSeedMode.FIRST_PART,
                                  resource_value)
            seed = list(result.seed)
            length = result.length
            if length == 0:
                continue
            if length > MAX_PAYLOAD:
                remaining = length - len(seed)
                while remaining > 0:
                    result = self.getSeed(types.XcpGetSeedMode.REMAINING,
                                          resource_value)
                    seed.extend(list(result.seed))
                    remaining = result.length
            result, key = getKey(self._seedNKeyDLL, resource_value,
                                 bytes(seed))
            if result == SeedNKeyResult.ACK:
                key = list(key)
                total_length = len(key)
                offset = 0
                while offset < total_length:
                    data = key[offset:offset + MAX_PAYLOAD]
                    key_length = len(data)
                    offset += key_length
                    res = self.unlock(key_length, data)
            else:
                raise SeedNKeyError("SeedAndKey DLL returned: {}".format(
                    SeedNKeyResult(retCode).name))

    @property
    def seedNKeyDLL(self):
        return self._seedNKeyDLL

    @seedNKeyDLL.setter
    def seedNKeyDLL(self, name):
        self._seedNKeyDLL = name