Custom CAN bus objects#

By default, pyXCP automatically creates and manages CAN bus objects for you.

In most situations, this means you don’t need to worry about setting up the bus manually.

There are, however, two special cases where you might want to provide your own CAN bus object instead:

  • You are already working with a CAN bus object in another application, such as UDSonCAN.

  • You need to use an interface that is not supported by python-can.

In these situations, pyXCP allows you to integrate your existing setup rather than creating a new one. The following examples illustrate how you can pass your own CAN bus object to pyXCP:

Using an existing CAN bus object#

import can

from pyxcp.cmdline import ArgumentParser

can_if = can.Bus(interface="kvaser", channel="0", fd=False, bitrate=500000)

ap = ArgumentParser(description="external interface test")

with ap.run(transport_layer_interface=can_if) as x:
    x.connect()
    x.disconnect()

Using an unsupported interface#

If your CAN interface is not supported by python-can, you can still integrate it by writing minimal wrapper code that provides the methods pyXCP requires (e.g. send() and recv()).

This is done by implementing the interface CanInterfaceBase (basically a subset of python-can’s BusABC).

class CanInterfaceBase(ABC):
    """
    Base class for custom CAN interfaces.
    This is basically a subset of python-CANs `BusABC`.
    """

    @abstractmethod
    def set_filters(self, filters: Optional[List[Dict[str, Union[int, bool]]]] = None) -> None:
        """Apply filtering to all messages received by this Bus.

        filters:
            A list of dictionaries, each containing a 'can_id', 'can_mask', and 'extended' field, e.g.:
            [{"can_id": 0x11, "can_mask": 0x21, "extended": False}]
        """

    @abstractmethod
    def recv(self, timeout: Optional[float] = None) -> Optional[Message]:
        """Block waiting for a message from the Bus."""

    @abstractmethod
    def send(self, msg: Message) -> None:
        """Transmit a message to the CAN bus."""

    @property
    @abstractmethod
    def filters(self) -> Optional[List[Dict[str, Union[int, bool]]]]:
        """Modify the filters of this bus."""

    @property
    @abstractmethod
    def state(self) -> BusState:
        """Return the current state of the hardware."""

Import it as follows:

from pyxcp.transport.can import CanInterfaceBase

Use this [example](pyxcp/blob/master/pyxcp/examples/xcp_user_supplied_driver.py as a starting point, or the following sucessfully tested code:

#!/usr/bin/env python

import can

from pyxcp.cmdline import ArgumentParser
from pyxcp.transport.can import CanInterfaceBase


class WrappedKvaserInterface(CanInterfaceBase):

    def __init__(self):
        self.canif = can.Bus(interface="kvaser", channel="0", bitrate=500000)

    def set_filters(self, filters):
        self.canif.set_filters(filters)

    def recv(self, timeout: float = None):
        return self.canif.recv(timeout)

    def send(self, msg: can.message.Message):
        self.canif.send(msg)

    @property
    def filters(self):
        return self.canif.filters

    @property
    def state(self):
        return self.canif.state

    def close(self):
        self.canif.shutdown()


custom_interface = WrappedKvaserInterface()

ap = ArgumentParser(description="Wrapped Kvaser CAN driver.")
with ap.run(transport_layer_interface=custom_interface) as x:
    x.connect()
    x.disconnect()

custom_interface.close()

In both variants, you pass your CAN bus object to the run() method via the transport_layer_interface parameter.

Some important notes: - It is the user’s responsibility to properly initialize and shut down the CAN bus interface. - In addition, pyXCP merges its filter configuration with the existing one, so users must ensure that no unwanted traffic is passed to the external application. The original filter configuration is restored when pyXCP exits. - Choose “custom” as your CAN interface: python  c.Transport.Can.interface="custom"