diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 39eae343f..5365620cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,21 +13,17 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] experimental: [false] - python-version: [ - "3.7", - "3.8", - "3.9", - "3.10", - "3.11", - "pypy-3.7", - "pypy-3.8", - "pypy-3.9", - ] + python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"] + include: + # Only test on a single configuration while there are just pre-releases + - os: ubuntu-latest + experimental: true + python-version: "3.11.0-alpha - 3.11.0" fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -38,16 +34,16 @@ jobs: run: | tox -e gh - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v2 with: fail_ci_if_error: true static-code-analysis: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v3 with: python-version: "3.10" - name: Install dependencies @@ -67,9 +63,6 @@ jobs: - name: mypy 3.10 run: | mypy --python-version 3.10 . - - name: mypy 3.11 - run: | - mypy --python-version 3.11 . - name: pylint run: | pylint --rcfile=.pylintrc \ @@ -82,9 +75,9 @@ jobs: format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v3 with: python-version: "3.10" - name: Install dependencies @@ -94,38 +87,3 @@ jobs: - name: Code Format Check with Black run: | black --check --verbose . - - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -e .[canalystii,gs_usb] - pip install -r doc/doc-requirements.txt - - name: Build documentation - run: | - python -m sphinx -an doc build - - uses: actions/upload-artifact@v3 - with: - name: sphinx-out - path: ./build/ - retention-days: 5 - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Build wheel and sdist - run: pipx run build - - name: Check build artifacts - run: pipx run twine check --strict dist/* diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml index 68c6f56d8..b86789662 100644 --- a/.github/workflows/format-code.yml +++ b/.github/workflows/format-code.yml @@ -9,9 +9,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v2 with: python-version: "3.10" - name: Install dependencies @@ -22,7 +22,7 @@ jobs: run: | black --verbose . - name: Commit Formated Code - uses: EndBug/add-and-commit@v9 + uses: EndBug/add-and-commit@v7 with: message: "Format code with black" # Ref https://git-scm.com/docs/git-add#_examples diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index 74cb9dbdd..000000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,31 +0,0 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Set the version of Python and other tools you might need -build: - os: ubuntu-22.04 - tools: - python: "3.10" - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: doc/conf.py - -# If using Sphinx, optionally build your docs in additional formats such as PDF -formats: - - pdf - - epub - -# Optionally declare the Python requirements required to build your docs -python: - install: - - requirements: doc/doc-requirements.txt - - method: pip - path: . - extra_requirements: - - canalystii - - gs_usb diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 239d1d7d5..c15186e2c 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -39,7 +39,7 @@ class CyclicTask(abc.ABC): def stop(self) -> None: """Cancel this periodic task. - :raises ~can.exceptions.CanError: + :raises can.CanError: If stop is called on an already stopped task. """ diff --git a/can/bus.py b/can/bus.py index c0793c5f7..b9ccfcfad 100644 --- a/can/bus.py +++ b/can/bus.py @@ -66,10 +66,8 @@ def __init__( Any backend dependent configurations are passed in this dictionary :raises ValueError: If parameters are out of range - :raises ~can.exceptions.CanInterfaceNotImplementedError: - If the driver cannot be accessed - :raises ~can.exceptions.CanInitializationError: - If the bus cannot be initialized + :raises can.CanInterfaceNotImplementedError: If the driver cannot be accessed + :raises can.CanInitializationError: If the bus cannot be initialized """ self._periodic_tasks: List[_SelfRemovingCyclicTask] = [] self.set_filters(can_filters) @@ -83,11 +81,9 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]: :param timeout: seconds to wait for a message or None to wait indefinitely - :return: - :obj:`None` on timeout or a :class:`~can.Message` object. + :return: ``None`` on timeout or a :class:`Message` object. - :raises ~can.exceptions.CanOperationError: - If an error occurred while reading + :raises can.CanOperationError: If an error occurred while reading """ start = time() time_left = timeout @@ -152,8 +148,7 @@ def _recv_internal( 2. a bool that is True if message filtering has already been done and else False - :raises ~can.exceptions.CanOperationError: - If an error occurred while reading + :raises can.CanOperationError: If an error occurred while reading :raises NotImplementedError: if the bus provides it's own :meth:`~can.BusABC.recv` implementation (legacy implementation) @@ -176,8 +171,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: Might not be supported by all interfaces. None blocks indefinitely. - :raises ~can.exceptions.CanOperationError: - If an error occurred while sending + :raises can.CanOperationError: If an error occurred while sending """ raise NotImplementedError("Trying to write to a readonly bus?") @@ -195,8 +189,8 @@ def send_periodic( - the (optional) duration expires - the Bus instance goes out of scope - the Bus instance is shutdown - - :meth:`stop_all_periodic_tasks` is called - - the task's :meth:`~can.broadcastmanager.CyclicTask.stop` method is called. + - :meth:`BusABC.stop_all_periodic_tasks()` is called + - the task's :meth:`CyclicTask.stop()` method is called. :param msgs: Message(s) to transmit @@ -210,8 +204,7 @@ def send_periodic( Disable to instead manage tasks manually. :return: A started task instance. Note the task can be stopped (and depending on - the backend modified) by calling the task's - :meth:`~can.broadcastmanager.CyclicTask.stop` method. + the backend modified) by calling the task's :meth:`stop` method. .. note:: @@ -281,8 +274,8 @@ def _send_periodic_internal( no duration is provided, the task will continue indefinitely. :return: A started task instance. Note the task can be stopped (and - depending on the backend modified) by calling the - :meth:`~can.broadcastmanager.CyclicTask.stop` method. + depending on the backend modified) by calling the :meth:`stop` + method. """ if not hasattr(self, "_lock_send_periodic"): # Create a send lock for this bus, but not for buses which override this method @@ -295,7 +288,7 @@ def _send_periodic_internal( return task def stop_all_periodic_tasks(self, remove_tasks: bool = True) -> None: - """Stop sending any messages that were started using :meth:`send_periodic`. + """Stop sending any messages that were started using **bus.send_periodic**. .. note:: The result is undefined if a single task throws an exception while being stopped. diff --git a/can/exceptions.py b/can/exceptions.py index dc08be3b8..5a7aa0b7c 100644 --- a/can/exceptions.py +++ b/can/exceptions.py @@ -74,7 +74,7 @@ class CanInitializationError(CanError): """Indicates an error the occurred while initializing a :class:`can.BusABC`. If initialization fails due to a driver or platform missing/being unsupported, - a :exc:`~can.exceptions.CanInterfaceNotImplementedError` is raised instead. + a :class:`can.CanInterfaceNotImplementedError` is raised instead. If initialization fails due to a value being out of range, a :class:`ValueError` is raised. diff --git a/can/interface.py b/can/interface.py index 527f84d20..f1a0087e3 100644 --- a/can/interface.py +++ b/can/interface.py @@ -60,44 +60,37 @@ class Bus(BusABC): # pylint: disable=abstract-method Instantiates a CAN Bus of the given ``interface``, falls back to reading a configuration file from default locations. + """ - :param channel: - Channel identification. Expected type is backend dependent. - Set to ``None`` to let it be resolved automatically from the default - :ref:`configuration`. + @staticmethod + def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg + cls: Any, channel: Optional[Channel] = None, *args: Any, **kwargs: Any + ) -> BusABC: + """ + Takes the same arguments as :class:`can.BusABC.__init__`. + Some might have a special meaning, see below. - :param interface: - See :ref:`interface names` for a list of supported interfaces. - Set to ``None`` to let it be resolved automatically from the default - :ref:`configuration`. + :param channel: + Set to ``None`` to let it be resolved automatically from the default + configuration. That might fail, see below. - :param args: - ``interface`` specific positional arguments. + Expected type is backend dependent. - :param kwargs: - ``interface`` specific keyword arguments. + :param dict kwargs: + Should contain an ``interface`` key with a valid interface name. If not, + it is completed using :meth:`can.util.load_config`. - :raises ~can.exceptions.CanInterfaceNotImplementedError: - if the ``interface`` isn't recognized or cannot be loaded + :raises: can.CanInterfaceNotImplementedError + if the ``interface`` isn't recognized or cannot be loaded - :raises ~can.exceptions.CanInitializationError: - if the bus cannot be instantiated + :raises: can.CanInitializationError + if the bus cannot be instantiated - :raises ValueError: - if the ``channel`` could not be determined - """ + :raises: ValueError + if the ``channel`` could not be determined + """ - @staticmethod - def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg - cls: Any, - channel: Optional[Channel] = None, - interface: Optional[str] = None, - *args: Any, - **kwargs: Any, - ) -> BusABC: # figure out the rest of the configuration; this might raise an error - if interface is not None: - kwargs["interface"] = interface if channel is not None: kwargs["channel"] = channel if "context" in kwargs: diff --git a/can/interfaces/cantact.py b/can/interfaces/cantact.py index 9ad7fbef8..056a64a6b 100644 --- a/can/interfaces/cantact.py +++ b/can/interfaces/cantact.py @@ -59,7 +59,7 @@ def __init__( Bitrate in bits/s :param bool monitor: If true, operate in listen-only monitoring mode - :param BitTiming bit_timing: + :param BitTiming bit_timing Optional BitTiming to use for custom bit timing setting. Overrides bitrate if not None. """ diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index bdb05cda5..cb0447b49 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -372,8 +372,10 @@ class IXXATBus(BusABC): .. warning:: This interface does implement efficient filtering of messages, but - the filters have to be set in ``__init__`` using the ``can_filters`` parameter. - Using :meth:`~can.BusABC.set_filters` does not work. + the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` + using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` + does not work. + """ CHANNEL_BITRATES = { diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 802168630..37085d74a 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -411,8 +411,9 @@ class IXXATBus(BusABC): .. warning:: This interface does implement efficient filtering of messages, but - the filters have to be set in ``__init__`` using the ``can_filters`` parameter. - Using :meth:`~can.BusABC.set_filters` does not work. + the filters have to be set in :meth:`~can.interfaces.ixxat.IXXATBus.__init__` + using the ``can_filters`` parameter. Using :meth:`~can.interfaces.ixxat.IXXATBus.set_filters` + does not work. """ diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index f60a43bc5..a951e39de 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -657,7 +657,7 @@ def shutdown(self): canBusOff(self._write_handle) canClose(self._write_handle) - def get_stats(self) -> structures.BusStatistics: + def get_stats(self): """Retrieves the bus statistics. Use like so: @@ -667,6 +667,7 @@ def get_stats(self) -> structures.BusStatistics: std_data: 0, std_remote: 0, ext_data: 0, ext_remote: 0, err_frame: 0, bus_load: 0.0%, overruns: 0 :returns: bus statistics. + :rtype: can.interfaces.kvaser.structures.BusStatistics """ canRequestBusStatistics(self._write_handle) stats = structures.BusStatistics() diff --git a/can/interfaces/kvaser/structures.py b/can/interfaces/kvaser/structures.py index 996f16c37..c7d363dd4 100644 --- a/can/interfaces/kvaser/structures.py +++ b/can/interfaces/kvaser/structures.py @@ -7,8 +7,11 @@ class BusStatistics(ctypes.Structure): - """This structure is used with the method - :meth:`~can.interfaces.kvaser.canlib.KvaserBus.get_stats`. + """ + This structure is used with the method :meth:`KvaserBus.get_stats`. + + .. seealso:: :meth:`KvaserBus.get_stats` + """ _fields_ = [ diff --git a/can/interfaces/nican.py b/can/interfaces/nican.py index ea13e28e8..0beeee429 100644 --- a/can/interfaces/nican.py +++ b/can/interfaces/nican.py @@ -179,8 +179,10 @@ class NicanBus(BusABC): .. warning:: This interface does implement efficient filtering of messages, but - the filters have to be set in ``__init__`` using the ``can_filters`` parameter. - Using :meth:`~can.BusABC.set_filters` does not work. + the filters have to be set in :meth:`~can.interfaces.nican.NicanBus.__init__` + using the ``can_filters`` parameter. Using :meth:`~can.interfaces.nican.NicanBus.set_filters` + does not work. + """ def __init__( @@ -206,9 +208,9 @@ def __init__( ``is_error_frame`` set to True and ``arbitration_id`` will identify the error (default True) - :raise ~can.exceptions.CanInterfaceNotImplementedError: + :raise can.CanInterfaceNotImplementedError: If the current operating system is not supported or the driver could not be loaded. - :raise ~can.interfaces.nican.NicanInitializationError: + :raise can.interfaces.nican.NicanInitializationError: If the bus could not be set up. """ if nican is None: diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index c60b9e6c9..e5b877762 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -113,8 +113,8 @@ def __init__( """A PCAN USB interface to CAN. On top of the usual :class:`~can.Bus` methods provided, - the PCAN interface includes the :meth:`flash` - and :meth:`status` methods. + the PCAN interface includes the :meth:`~can.interface.pcan.PcanBus.flash` + and :meth:`~can.interface.pcan.PcanBus.status` methods. :param str channel: The can interface name. An example would be 'PCAN_USBBUS1'. diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index 0d3ad1b77..709fad78d 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -5,10 +5,9 @@ import io import time import logging -from typing import Optional from can import BusABC, Message -from ..exceptions import CanInterfaceNotImplementedError, CanOperationError +from ..exceptions import CanInterfaceNotImplementedError logger = logging.getLogger(__name__) @@ -378,11 +377,12 @@ def fileno(self): except Exception as exception: raise CanOperationError("Cannot fetch fileno") from exception - def get_serial_number(self, timeout: Optional[int]) -> Optional[str]: + def get_serial_number(self, timeout): """Get serial number of the slcan interface. - + :type timeout: int or None :param timeout: seconds to wait for serial number or None to wait indefinitely + :rtype str or None :return: None on timeout or a str object. """ diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index c1507b4fa..ec4bb8671 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -74,10 +74,8 @@ def __init__( :param rtscts: turn hardware handshake (RTS/CTS) on and off - :raises ~can.exceptions.CanInitializationError: - If the given parameters are invalid. - :raises ~can.exceptions.CanInterfaceNotImplementedError: - If the serial module is not installed. + :raises can.CanInitializationError: If the given parameters are invalid. + :raises can.CanInterfaceNotImplementedError: If the serial module is not installed. """ if not serial: @@ -165,10 +163,10 @@ def _recv_internal( This parameter will be ignored. The timeout value of the channel is used. :returns: - Received message and :obj:`False` (because no filtering as taken place). + Received message and `False` (because no filtering as taken place). .. warning:: - Flags like ``is_extended_id``, ``is_remote_frame`` and ``is_error_frame`` + Flags like is_extended_id, is_remote_frame and is_error_frame will not be set over this function, the flags in the return message are the default values. """ diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 212c4c85c..63ea4ca42 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -323,10 +323,10 @@ def get_serial_number(self, timeout: Optional[float]) -> Optional[str]: """Get serial number of the slcan interface. :param timeout: - seconds to wait for serial number or :obj:`None` to wait indefinitely + seconds to wait for serial number or ``None`` to wait indefinitely :return: - :obj:`None` on timeout or a :class:`str` object. + ``None`` on timeout or a :class:`~builtin.str` object. """ cmd = "N" self._write(cmd) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 549998dc8..c7c038520 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -402,7 +402,7 @@ def stop(self) -> None: """Stop a task by sending TX_DELETE message to Linux kernel. This will delete the entry for the transmission of the CAN-message - with the specified ``task_id`` identifier. The message length + with the specified :attr:`~task_id` identifier. The message length for the command TX_DELETE is {[bcm_msg_head]} (only the header). """ log.debug("Stopping periodic task") @@ -444,7 +444,7 @@ def start(self) -> None: message to Linux kernel prior to scheduling. :raises ValueError: - If the task referenced by ``task_id`` is already running. + If the task referenced by :attr:`~task_id` is already running. """ self._tx_setup(self.messages) @@ -617,10 +617,9 @@ def __init__( If setting some socket options fails, an error will be printed but no exception will be thrown. This includes enabling: - - - that own messages should be received, - - CAN-FD frames and - - error frames. + - that own messages should be received, + - CAN-FD frames and + - error frames. :param channel: The can interface name with which to create this bus. @@ -740,7 +739,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: Wait up to this many seconds for the transmit queue to be ready. If not given, the call may fail immediately. - :raises ~can.exceptions.CanError: + :raises can.CanError: if the message could not be written. """ log.debug("We've been asked to write a message to the bus") diff --git a/can/interfaces/socketcan/utils.py b/can/interfaces/socketcan/utils.py index 55e7eb392..b718bb69e 100644 --- a/can/interfaces/socketcan/utils.py +++ b/can/interfaces/socketcan/utils.py @@ -38,7 +38,7 @@ def pack_filters(can_filters: Optional[typechecking.CanFilters] = None) -> bytes return struct.pack(can_filter_fmt, *filter_data) -_PATTERN_CAN_INTERFACE = re.compile(r"(sl|v|vx)?can\d+") +_PATTERN_CAN_INTERFACE = re.compile(r"v?can\d+") def find_available_interfaces() -> Iterable[str]: diff --git a/can/interfaces/systec/ucanbus.py b/can/interfaces/systec/ucanbus.py index fee110b08..88224b856 100644 --- a/can/interfaces/systec/ucanbus.py +++ b/can/interfaces/systec/ucanbus.py @@ -88,10 +88,10 @@ def __init__(self, channel, can_filters=None, **kwargs): :raises ValueError: If invalid input parameter were passed. - :raises ~can.exceptions.CanInterfaceNotImplementedError: + :raises can.CanInterfaceNotImplementedError: If the platform is not supported. - :raises ~can.exceptions.CanInitializationError: + :raises can.CanInitializationError: If hardware or CAN interface initialization failed. """ try: @@ -181,7 +181,7 @@ def send(self, msg, timeout=None): :param float timeout: Transmit timeout in seconds (value 0 switches off the "auto delete") - :raises ~can.exceptions.CanOperationError: + :raises can.CanOperationError: If the message could not be sent. """ try: @@ -243,7 +243,7 @@ def flush_tx_buffer(self): """ Flushes the transmit buffer. - :raises ~can.exceptions.CanError: + :raises can.CanError: If flushing of the transmit buffer failed. """ log.info("Flushing transmit buffer") diff --git a/can/interfaces/usb2can/usb2canInterface.py b/can/interfaces/usb2can/usb2canInterface.py index 504b61c7b..e51d485cd 100644 --- a/can/interfaces/usb2can/usb2canInterface.py +++ b/can/interfaces/usb2can/usb2canInterface.py @@ -67,22 +67,22 @@ class Usb2canBus(BusABC): This interface only works on Windows. Please use socketcan on Linux. - :param channel: + :param str channel (optional): The device's serial number. If not provided, Windows Management Instrumentation will be used to identify the first such device. - :param bitrate: + :param int bitrate (optional): Bitrate of channel in bit/s. Values will be limited to a maximum of 1000 Kb/s. Default is 500 Kbs - :param flags: + :param int flags (optional): Flags to directly pass to open function of the usb2can abstraction layer. - :param dll: + :param str dll (optional): Path to the DLL with the CANAL API to load Defaults to 'usb2can.dll' - :param serial: + :param str serial (optional): Alias for `channel` that is provided for legacy reasons. If both `serial` and `channel` are set, `serial` will be used and channel will be ignored. @@ -91,19 +91,18 @@ class Usb2canBus(BusABC): def __init__( self, - channel: Optional[str] = None, - dll: str = "usb2can.dll", - flags: int = 0x00000008, + channel=None, + dll="usb2can.dll", + flags=0x00000008, *_, - bitrate: int = 500000, - serial: Optional[str] = None, + bitrate=500000, **kwargs, ): self.can = Usb2CanAbstractionLayer(dll) # get the serial number of the device - device_id = serial or channel + device_id = kwargs.get("serial", channel) # search for a serial number if the device_id is None or empty if not device_id: diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index a6708cb42..8a3ae34ca 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -9,7 +9,6 @@ import can from ...exceptions import error_check -from ...typechecking import StringPathLike log = logging.getLogger("can.usb2can") @@ -109,13 +108,12 @@ class Usb2CanAbstractionLayer: Documentation: http://www.8devices.com/media/products/usb2can/downloads/CANAL_API.pdf """ - def __init__(self, dll: StringPathLike = "usb2can.dll") -> None: + def __init__(self, dll="usb2can.dll"): """ - :param dll: - the path to the usb2can DLL to load + :type dll: str or path-like + :param dll (optional): the path to the usb2can DLL to load - :raises ~can.exceptions.CanInterfaceNotImplementedError: - if the DLL could not be loaded + :raises can.CanInterfaceNotImplementedError: if the DLL could not be loaded """ try: self.__m_dllBasic = windll.LoadLibrary(dll) @@ -130,15 +128,11 @@ def open(self, configuration: str, flags: int): """ Opens a CAN connection using `CanalOpen()`. - :param configuration: - the configuration: "device_id; baudrate" - :param flags: - the flags to be set - :returns: - Valid handle for CANAL API functions on success + :param configuration: the configuration: "device_id; baudrate" + :param flags: the flags to be set - :raises ~can.exceptions.CanInterfaceNotImplementedError: - if any error occurred + :raises can.CanInitializationError: if any error occurred + :returns: Valid handle for CANAL API functions on success """ try: # we need to convert this into bytes, since the underlying DLL cannot diff --git a/can/interfaces/vector/__init__.py b/can/interfaces/vector/__init__.py index c5eae7140..cdeb1d3cb 100644 --- a/can/interfaces/vector/__init__.py +++ b/can/interfaces/vector/__init__.py @@ -1,12 +1,5 @@ """ """ -from .canlib import ( - VectorBus, - get_channel_configs, - VectorChannelConfig, - VectorBusParams, - VectorCanParams, - VectorCanFdParams, -) +from .canlib import VectorBus, VectorChannelConfig from .exceptions import VectorError, VectorOperationError, VectorInitializationError diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 8f699f3f7..9cecaa83d 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -21,7 +21,6 @@ Any, Dict, Callable, - cast, ) WaitForSingleObject: Optional[Callable[[int, int], int]] @@ -44,7 +43,7 @@ deprecated_args_alias, time_perfcounter_correlation, ) -from can.typechecking import AutoDetectedConfig, CanFilters +from can.typechecking import AutoDetectedConfig, CanFilters, Channel # Define Module Logger # ==================== @@ -138,11 +137,11 @@ def __init__( :param tseg2_dbr: Bus timing value tseg2 (data) - :raise ~can.exceptions.CanInterfaceNotImplementedError: + :raise can.CanInterfaceNotImplementedError: If the current operating system is not supported or the driver could not be loaded. - :raise ~can.exceptions.CanInitializationError: + :raise can.CanInitializationError: If the bus could not be set up. - This may or may not be a :class:`~can.interfaces.vector.VectorInitializationError`. + This may or may not be a :class:`can.interfaces.vector.VectorInitializationError`. """ if os.name != "nt" and not kwargs.get("_testing", False): raise CanInterfaceNotImplementedError( @@ -153,7 +152,6 @@ def __init__( if xldriver is None: raise CanInterfaceNotImplementedError("The Vector API has not been loaded") self.xldriver = xldriver # keep reference so mypy knows it is not None - self.xldriver.xlOpenDriver() self.poll_interval = poll_interval @@ -167,7 +165,7 @@ def __init__( self.channels = [int(ch) for ch in channel] else: raise TypeError( - f"Invalid type for parameter 'channel': {type(channel).__name__}" + f"Invalid type for channels parameter: {type(channel).__name__}" ) self._app_name = app_name.encode() if app_name is not None else b"" @@ -176,118 +174,136 @@ def __init__( ", ".join(f"CAN {ch + 1}" for ch in self.channels), ) - channel_configs = get_channel_configs() + if serial is not None: + app_name = None + channel_index = [] + channel_configs = get_channel_configs() + for channel_config in channel_configs: + if channel_config.serialNumber == serial: + if channel_config.hwChannel in self.channels: + channel_index.append(channel_config.channelIndex) + if channel_index: + if len(channel_index) != len(self.channels): + LOG.info( + "At least one defined channel wasn't found on the specified hardware." + ) + self.channels = channel_index + else: + # Is there any better way to raise the error? + raise CanInitializationError( + "None of the configured channels could be found on the specified hardware." + ) + self.xldriver.xlOpenDriver() + self.port_handle = xlclass.XLportHandle(xldefine.XL_INVALID_PORTHANDLE) self.mask = 0 self.fd = fd - self.channel_masks: Dict[int, int] = {} - self.index_to_channel: Dict[int, int] = {} + # Get channels masks + self.channel_masks: Dict[Optional[Channel], int] = {} + self.index_to_channel = {} for channel in self.channels: - channel_index = self._find_global_channel_idx( - channel=channel, - serial=serial, - app_name=app_name, - channel_configs=channel_configs, - ) - LOG.debug("Channel index %d found", channel) - - channel_mask = 1 << channel_index - self.channel_masks[channel] = channel_mask - self.index_to_channel[channel_index] = channel - self.mask |= channel_mask + if app_name: + # Get global channel index from application channel + hw_type, hw_index, hw_channel = self.get_application_config( + app_name, channel + ) + LOG.debug("Channel index %d found", channel) + idx = self.xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel) + if idx < 0: + # Undocumented behavior! See issue #353. + # If hardware is unavailable, this function returns -1. + # Raise an exception as if the driver + # would have signalled XL_ERR_HW_NOT_PRESENT. + raise VectorInitializationError( + xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT, + xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.name, + "xlGetChannelIndex", + ) + else: + # Channel already given as global channel + idx = channel + mask = 1 << idx + self.channel_masks[channel] = mask + self.index_to_channel[idx] = channel + self.mask |= mask permission_mask = xlclass.XLaccess() # Set mask to request channel init permission if needed if bitrate or fd: permission_mask.value = self.mask - - interface_version = ( - xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4 - if fd - else xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION - ) - - self.port_handle = xlclass.XLportHandle(xldefine.XL_INVALID_PORTHANDLE) - self.xldriver.xlOpenPort( - self.port_handle, - self._app_name, - self.mask, - permission_mask, - rx_queue_size, - interface_version, - xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, - ) - self.permission_mask = permission_mask.value - + if fd: + self.xldriver.xlOpenPort( + self.port_handle, + self._app_name, + self.mask, + permission_mask, + rx_queue_size, + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, + ) + else: + self.xldriver.xlOpenPort( + self.port_handle, + self._app_name, + self.mask, + permission_mask, + rx_queue_size, + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, + ) LOG.debug( "Open Port: PortHandle: %d, PermissionMask: 0x%X", self.port_handle.value, permission_mask.value, ) - # set CAN settings - for channel in self.channels: - if self._has_init_access(channel): - if fd: - self._set_bitrate_canfd( - channel=channel, - bitrate=bitrate, - data_bitrate=data_bitrate, - sjw_abr=sjw_abr, - tseg1_abr=tseg1_abr, - tseg2_abr=tseg2_abr, - sjw_dbr=sjw_dbr, - tseg1_dbr=tseg1_dbr, - tseg2_dbr=tseg2_dbr, - ) - elif bitrate: - self._set_bitrate_can(channel=channel, bitrate=bitrate) - - # Check CAN settings - for channel in self.channels: - if kwargs.get("_testing", False): - # avoid check if xldriver is mocked for testing - break - - bus_params = self._read_bus_params(channel) + if permission_mask.value == self.mask: if fd: - _canfd = bus_params.canfd - if not all( - [ - bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, - _canfd.can_op_mode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD, - _canfd.bitrate == bitrate if bitrate else True, - _canfd.sjw_abr == sjw_abr if bitrate else True, - _canfd.tseg1_abr == tseg1_abr if bitrate else True, - _canfd.tseg2_abr == tseg2_abr if bitrate else True, - _canfd.data_bitrate == data_bitrate if data_bitrate else True, - _canfd.sjw_dbr == sjw_dbr if data_bitrate else True, - _canfd.tseg1_dbr == tseg1_dbr if data_bitrate else True, - _canfd.tseg2_dbr == tseg2_dbr if data_bitrate else True, - ] - ): - raise CanInitializationError( - f"The requested CAN FD settings could not be set for channel {channel}. " - f"Another application might have set incompatible settings. " - f"These are the currently active settings: {_canfd._asdict()}" - ) + self.canFdConf = xlclass.XLcanFdConf() + if bitrate: + self.canFdConf.arbitrationBitRate = int(bitrate) + else: + self.canFdConf.arbitrationBitRate = 500000 + self.canFdConf.sjwAbr = int(sjw_abr) + self.canFdConf.tseg1Abr = int(tseg1_abr) + self.canFdConf.tseg2Abr = int(tseg2_abr) + if data_bitrate: + self.canFdConf.dataBitRate = int(data_bitrate) + else: + self.canFdConf.dataBitRate = self.canFdConf.arbitrationBitRate + self.canFdConf.sjwDbr = int(sjw_dbr) + self.canFdConf.tseg1Dbr = int(tseg1_dbr) + self.canFdConf.tseg2Dbr = int(tseg2_dbr) + + self.xldriver.xlCanFdSetConfiguration( + self.port_handle, self.mask, self.canFdConf + ) + LOG.info( + "SetFdConfig.: ABaudr.=%u, DBaudr.=%u", + self.canFdConf.arbitrationBitRate, + self.canFdConf.dataBitRate, + ) + LOG.info( + "SetFdConfig.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u", + self.canFdConf.sjwAbr, + self.canFdConf.tseg1Abr, + self.canFdConf.tseg2Abr, + ) + LOG.info( + "SetFdConfig.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u", + self.canFdConf.sjwDbr, + self.canFdConf.tseg1Dbr, + self.canFdConf.tseg2Dbr, + ) else: - _can = bus_params.can - if not all( - [ - bus_params.bus_type is xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, - _can.can_op_mode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20, - _can.bitrate == bitrate if bitrate else True, - ] - ): - raise CanInitializationError( - f"The requested CAN settings could not be set for channel {channel}. " - f"Another application might have set incompatible settings. " - f"These are the currently active settings: {_can._asdict()}" + if bitrate: + self.xldriver.xlCanSetChannelBitrate( + self.port_handle, permission_mask, bitrate ) + LOG.info("SetChannelBitrate: baudr.=%u", bitrate) + else: + LOG.info("No init access!") # Enable/disable TX receipts tx_receipts = 1 if receive_own_messages else 0 @@ -332,168 +348,6 @@ def __init__( self._is_filtered = False super().__init__(channel=channel, can_filters=can_filters, **kwargs) - def _find_global_channel_idx( - self, - channel: int, - serial: Optional[int], - app_name: Optional[str], - channel_configs: List["VectorChannelConfig"], - ) -> int: - if serial is not None: - hw_type: Optional[xldefine.XL_HardwareType] = None - for channel_config in channel_configs: - if channel_config.serial_number != serial: - continue - - hw_type = xldefine.XL_HardwareType(channel_config.hw_type) - if channel_config.hw_channel == channel: - return channel_config.channel_index - - if hw_type is None: - err_msg = f"No interface with serial {serial} found." - else: - err_msg = f"Channel {channel} not found on interface {hw_type.name} ({serial})." - raise CanInitializationError( - err_msg, error_code=xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT - ) - - if app_name: - hw_type, hw_index, hw_channel = self.get_application_config( - app_name, channel - ) - idx = cast( - int, self.xldriver.xlGetChannelIndex(hw_type, hw_index, hw_channel) - ) - if idx < 0: - # Undocumented behavior! See issue #353. - # If hardware is unavailable, this function returns -1. - # Raise an exception as if the driver - # would have signalled XL_ERR_HW_NOT_PRESENT. - raise VectorInitializationError( - xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT, - xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.name, - "xlGetChannelIndex", - ) - return idx - - # check if channel is a valid global channel index - for channel_config in channel_configs: - if channel == channel_config.channel_index: - return channel - - raise CanInitializationError( - f"Channel {channel} not found. The 'channel' parameter must be " - f"a valid global channel index if neither 'app_name' nor 'serial' were given.", - error_code=xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT, - ) - - def _has_init_access(self, channel: int) -> bool: - return bool(self.permission_mask & self.channel_masks[channel]) - - def _read_bus_params(self, channel: int) -> "VectorBusParams": - channel_mask = self.channel_masks[channel] - - vcc_list = get_channel_configs() - for vcc in vcc_list: - if vcc.channel_mask == channel_mask: - return vcc.bus_params - - raise CanInitializationError( - f"Channel configuration for channel {channel} not found." - ) - - def _set_bitrate_can( - self, - channel: int, - bitrate: int, - sjw: Optional[int] = None, - tseg1: Optional[int] = None, - tseg2: Optional[int] = None, - sam: int = 1, - ) -> None: - kwargs = [sjw, tseg1, tseg2] - if any(kwargs) and not all(kwargs): - raise ValueError( - f"Either all of sjw, tseg1, tseg2 must be set or none of them." - ) - - # set parameters if channel has init access - if any(kwargs): - chip_params = xlclass.XLchipParams() - chip_params.bitRate = bitrate - chip_params.sjw = sjw - chip_params.tseg1 = tseg1 - chip_params.tseg2 = tseg2 - chip_params.sam = sam - self.xldriver.xlCanSetChannelParams( - self.port_handle, - self.channel_masks[channel], - chip_params, - ) - LOG.info( - "xlCanSetChannelParams: baudr.=%u, sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u", - chip_params.bitRate, - chip_params.sjw, - chip_params.tseg1, - chip_params.tseg2, - ) - else: - self.xldriver.xlCanSetChannelBitrate( - self.port_handle, - self.channel_masks[channel], - bitrate, - ) - LOG.info("xlCanSetChannelBitrate: baudr.=%u", bitrate) - - def _set_bitrate_canfd( - self, - channel: int, - bitrate: Optional[int] = None, - data_bitrate: Optional[int] = None, - sjw_abr: int = 2, - tseg1_abr: int = 6, - tseg2_abr: int = 3, - sjw_dbr: int = 2, - tseg1_dbr: int = 6, - tseg2_dbr: int = 3, - ) -> None: - # set parameters if channel has init access - canfd_conf = xlclass.XLcanFdConf() - if bitrate: - canfd_conf.arbitrationBitRate = int(bitrate) - else: - canfd_conf.arbitrationBitRate = 500_000 - canfd_conf.sjwAbr = int(sjw_abr) - canfd_conf.tseg1Abr = int(tseg1_abr) - canfd_conf.tseg2Abr = int(tseg2_abr) - if data_bitrate: - canfd_conf.dataBitRate = int(data_bitrate) - else: - canfd_conf.dataBitRate = int(canfd_conf.arbitrationBitRate) - canfd_conf.sjwDbr = int(sjw_dbr) - canfd_conf.tseg1Dbr = int(tseg1_dbr) - canfd_conf.tseg2Dbr = int(tseg2_dbr) - self.xldriver.xlCanFdSetConfiguration( - self.port_handle, self.channel_masks[channel], canfd_conf - ) - LOG.info( - "xlCanFdSetConfiguration.: ABaudr.=%u, DBaudr.=%u", - canfd_conf.arbitrationBitRate, - canfd_conf.dataBitRate, - ) - LOG.info( - "xlCanFdSetConfiguration.: sjwAbr=%u, tseg1Abr=%u, tseg2Abr=%u", - canfd_conf.sjwAbr, - canfd_conf.tseg1Abr, - canfd_conf.tseg2Abr, - ) - LOG.info( - "xlCanFdSetConfiguration.: sjwDbr=%u, tseg1Dbr=%u, tseg2Dbr=%u", - canfd_conf.sjwDbr, - canfd_conf.tseg1Dbr, - canfd_conf.tseg2Dbr, - ) - def _apply_filters(self, filters: Optional[CanFilters]) -> None: if filters: # Only up to one filter per ID type allowed @@ -690,7 +544,7 @@ def _send_sequence(self, msgs: Sequence[Message]) -> int: def _get_tx_channel_mask(self, msgs: Sequence[Message]) -> int: if len(msgs) == 1: - return self.channel_masks.get(msgs[0].channel, self.mask) # type: ignore[arg-type] + return self.channel_masks.get(msgs[0].channel, self.mask) else: return self.mask @@ -789,28 +643,26 @@ def _detect_available_configs() -> List[AutoDetectedConfig]: LOG.info("Found %d channels", len(channel_configs)) for channel_config in channel_configs: if ( - not channel_config.channel_bus_capabilities + not channel_config.channelBusCapabilities & xldefine.XL_BusCapabilities.XL_BUS_ACTIVE_CAP_CAN ): continue LOG.info( - "Channel index %d: %s", - channel_config.channel_index, - channel_config.name, + "Channel index %d: %s", channel_config.channelIndex, channel_config.name ) configs.append( { # data for use in VectorBus.__init__(): "interface": "vector", - "channel": channel_config.hw_channel, - "serial": channel_config.serial_number, + "channel": channel_config.hwChannel, + "serial": channel_config.serialNumber, # data for use in VectorBus.set_application_config(): - "hw_type": channel_config.hw_type, - "hw_index": channel_config.hw_index, - "hw_channel": channel_config.hw_channel, + "hw_type": channel_config.hwType, + "hw_index": channel_config.hwIndex, + "hw_channel": channel_config.hwChannel, # additional information: "supports_fd": bool( - channel_config.channel_capabilities + channel_config.channelCapabilities & xldefine.XL_ChannelCapabilities.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT ), "vector_channel_config": channel_config, @@ -855,24 +707,14 @@ def get_application_config( hw_channel = ctypes.c_uint() _app_channel = ctypes.c_uint(app_channel) - try: - xldriver.xlGetApplConfig( - app_name.encode(), - _app_channel, - hw_type, - hw_index, - hw_channel, - xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, - ) - except VectorError as e: - raise VectorInitializationError( - error_code=e.error_code, - error_string=( - f"Vector HW Config: Channel '{app_channel}' of " - f"application '{app_name}' is not assigned to any interface" - ), - function="xlGetApplConfig", - ) from None + xldriver.xlGetApplConfig( + app_name.encode(), + _app_channel, + hw_type, + hw_index, + hw_channel, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN, + ) return xldefine.XL_HardwareType(hw_type.value), hw_index.value, hw_channel.value @staticmethod @@ -939,130 +781,54 @@ def set_timer_rate(self, timer_rate_ms: int) -> None: self.xldriver.xlSetTimerRate(self.port_handle, timer_rate_10us) -class VectorCanParams(NamedTuple): - bitrate: int - sjw: int - tseg1: int - tseg2: int - sam: int - output_mode: xldefine.XL_OutputMode - can_op_mode: xldefine.XL_CANFD_BusParams_CanOpMode - - -class VectorCanFdParams(NamedTuple): - bitrate: int - data_bitrate: int - sjw_abr: int - tseg1_abr: int - tseg2_abr: int - sam_abr: int - sjw_dbr: int - tseg1_dbr: int - tseg2_dbr: int - output_mode: xldefine.XL_OutputMode - can_op_mode: xldefine.XL_CANFD_BusParams_CanOpMode - - -class VectorBusParams(NamedTuple): - bus_type: xldefine.XL_BusTypes - can: VectorCanParams - canfd: VectorCanFdParams - - class VectorChannelConfig(NamedTuple): - """NamedTuple which contains the channel properties from Vector XL API.""" - name: str - hw_type: xldefine.XL_HardwareType - hw_index: int - hw_channel: int - channel_index: int - channel_mask: int - channel_capabilities: xldefine.XL_ChannelCapabilities - channel_bus_capabilities: xldefine.XL_BusCapabilities - is_on_bus: bool - connected_bus_type: xldefine.XL_BusTypes - bus_params: VectorBusParams - serial_number: int - article_number: int - transceiver_name: str - - -def _get_xl_driver_config() -> xlclass.XLdriverConfig: - if xldriver is None: - raise VectorError( - error_code=xldefine.XL_Status.XL_ERR_DLL_NOT_FOUND, - error_string="xldriver is unavailable", - function="_get_xl_driver_config", - ) - driver_config = xlclass.XLdriverConfig() - xldriver.xlOpenDriver() - xldriver.xlGetDriverConfig(driver_config) - xldriver.xlCloseDriver() - return driver_config - - -def _read_bus_params_from_c_struct(bus_params: xlclass.XLbusParams) -> VectorBusParams: - return VectorBusParams( - bus_type=xldefine.XL_BusTypes(bus_params.busType), - can=VectorCanParams( - bitrate=bus_params.data.can.bitRate, - sjw=bus_params.data.can.sjw, - tseg1=bus_params.data.can.tseg1, - tseg2=bus_params.data.can.tseg2, - sam=bus_params.data.can.sam, - output_mode=xldefine.XL_OutputMode(bus_params.data.can.outputMode), - can_op_mode=xldefine.XL_CANFD_BusParams_CanOpMode( - bus_params.data.can.canOpMode - ), - ), - canfd=VectorCanFdParams( - bitrate=bus_params.data.canFD.arbitrationBitRate, - data_bitrate=bus_params.data.canFD.dataBitRate, - sjw_abr=bus_params.data.canFD.sjwAbr, - tseg1_abr=bus_params.data.canFD.tseg1Abr, - tseg2_abr=bus_params.data.canFD.tseg2Abr, - sam_abr=bus_params.data.canFD.samAbr, - sjw_dbr=bus_params.data.canFD.sjwDbr, - tseg1_dbr=bus_params.data.canFD.tseg1Dbr, - tseg2_dbr=bus_params.data.canFD.tseg2Dbr, - output_mode=xldefine.XL_OutputMode(bus_params.data.canFD.outputMode), - can_op_mode=xldefine.XL_CANFD_BusParams_CanOpMode( - bus_params.data.canFD.canOpMode - ), - ), - ) + hwType: xldefine.XL_HardwareType + hwIndex: int + hwChannel: int + channelIndex: int + channelMask: int + channelCapabilities: xldefine.XL_ChannelCapabilities + channelBusCapabilities: xldefine.XL_BusCapabilities + isOnBus: bool + connectedBusType: xldefine.XL_BusTypes + serialNumber: int + articleNumber: int + transceiverName: str def get_channel_configs() -> List[VectorChannelConfig]: - """Read channel properties from Vector XL API.""" + if xldriver is None: + return [] + driver_config = xlclass.XLdriverConfig() try: - driver_config = _get_xl_driver_config() + xldriver.xlOpenDriver() + xldriver.xlGetDriverConfig(driver_config) + xldriver.xlCloseDriver() except VectorError: - return [] + pass channel_list: List[VectorChannelConfig] = [] for i in range(driver_config.channelCount): xlcc: xlclass.XLchannelConfig = driver_config.channel[i] vcc = VectorChannelConfig( name=xlcc.name.decode(), - hw_type=xldefine.XL_HardwareType(xlcc.hwType), - hw_index=xlcc.hwIndex, - hw_channel=xlcc.hwChannel, - channel_index=xlcc.channelIndex, - channel_mask=xlcc.channelMask, - channel_capabilities=xldefine.XL_ChannelCapabilities( + hwType=xldefine.XL_HardwareType(xlcc.hwType), + hwIndex=xlcc.hwIndex, + hwChannel=xlcc.hwChannel, + channelIndex=xlcc.channelIndex, + channelMask=xlcc.channelMask, + channelCapabilities=xldefine.XL_ChannelCapabilities( xlcc.channelCapabilities ), - channel_bus_capabilities=xldefine.XL_BusCapabilities( + channelBusCapabilities=xldefine.XL_BusCapabilities( xlcc.channelBusCapabilities ), - is_on_bus=bool(xlcc.isOnBus), - bus_params=_read_bus_params_from_c_struct(xlcc.busParams), - connected_bus_type=xldefine.XL_BusTypes(xlcc.connectedBusType), - serial_number=xlcc.serialNumber, - article_number=xlcc.articleNumber, - transceiver_name=xlcc.transceiverName.decode(), + isOnBus=bool(xlcc.isOnBus), + connectedBusType=xldefine.XL_BusTypes(xlcc.connectedBusType), + serialNumber=xlcc.serialNumber, + articleNumber=xlcc.articleNumber, + transceiverName=xlcc.transceiverName.decode(), ) channel_list.append(vcc) return channel_list diff --git a/can/interfaces/vector/xlclass.py b/can/interfaces/vector/xlclass.py index 6441ad4e5..c0ef9fa48 100644 --- a/can/interfaces/vector/xlclass.py +++ b/can/interfaces/vector/xlclass.py @@ -45,13 +45,6 @@ class s_xl_chip_state(ctypes.Structure): ] -class s_xl_sync_pulse(ctypes.Structure): - _fields_ = [ - ("pulseCode", ctypes.c_ubyte), - ("time", XLuint64), - ] - - class s_xl_can_ev_chip_state(ctypes.Structure): _fields_ = [ ("busStatus", ctypes.c_ubyte), @@ -72,11 +65,7 @@ class s_xl_can_ev_sync_pulse(ctypes.Structure): # BASIC bus message structure class s_xl_tag_data(ctypes.Union): - _fields_ = [ - ("msg", s_xl_can_msg), - ("chipState", s_xl_chip_state), - ("syncPulse", s_xl_sync_pulse), - ] + _fields_ = [("msg", s_xl_can_msg), ("chipState", s_xl_chip_state)] # CAN FD messages diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py index 5a1084f48..032f08318 100644 --- a/can/interfaces/vector/xldefine.py +++ b/can/interfaces/vector/xldefine.py @@ -64,7 +64,7 @@ class XL_BusTypes(IntFlag): XL_BUS_TYPE_A429 = 8192 # =0x00002000 -class XL_CANFD_BusParams_CanOpMode(IntFlag): +class XL_CANFD_BusParams_CanOpMode(IntEnum): XL_BUS_PARAMS_CANOPMODE_CAN20 = 1 XL_BUS_PARAMS_CANOPMODE_CANFD = 2 XL_BUS_PARAMS_CANOPMODE_CANFD_NO_ISO = 8 @@ -318,9 +318,3 @@ class XL_HardwareType(IntEnum): XL_HWTYPE_VX1161A = 114 XL_HWTYPE_VX1161B = 115 XL_MAX_HWTYPE = 120 - - -class XL_SyncPulseSource(IntEnum): - XL_SYNC_PULSE_EXTERNAL = 0 - XL_SYNC_PULSE_OUR = 1 - XL_SYNC_PULSE_OUR_SHARED = 2 diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py index 8df39e9dc..3243fa4a0 100644 --- a/can/interfaces/vector/xldriver.py +++ b/can/interfaces/vector/xldriver.py @@ -272,18 +272,3 @@ def check_status_initialization(result, function, arguments): xlCanGetEventString = _xlapi_dll.xlCanGetEventString xlCanGetEventString.argtypes = [ctypes.POINTER(xlclass.XLcanRxEvent)] xlCanGetEventString.restype = xlclass.XLstringType - -xlGetReceiveQueueLevel = _xlapi_dll.xlGetReceiveQueueLevel -xlGetReceiveQueueLevel.argtypes = [xlclass.XLportHandle, ctypes.POINTER(ctypes.c_int)] -xlGetReceiveQueueLevel.restype = xlclass.XLstatus -xlGetReceiveQueueLevel.errcheck = check_status_operation - -xlGenerateSyncPulse = _xlapi_dll.xlGenerateSyncPulse -xlGenerateSyncPulse.argtypes = [xlclass.XLportHandle, xlclass.XLaccess] -xlGenerateSyncPulse.restype = xlclass.XLstatus -xlGenerateSyncPulse.errcheck = check_status_operation - -xlFlushReceiveQueue = _xlapi_dll.xlFlushReceiveQueue -xlFlushReceiveQueue.argtypes = [xlclass.XLportHandle] -xlFlushReceiveQueue.restype = xlclass.XLstatus -xlFlushReceiveQueue.errcheck = check_status_operation diff --git a/can/interfaces/virtual.py b/can/interfaces/virtual.py index cc71469b5..ffd5b0241 100644 --- a/can/interfaces/virtual.py +++ b/can/interfaces/virtual.py @@ -40,7 +40,7 @@ class VirtualBus(BusABC): an identifier for connected buses. Implements :meth:`can.BusABC._detect_available_configs`; see - :meth:`_detect_available_configs` for how it + :meth:`can.VirtualBus._detect_available_configs` for how it behaves here. .. note:: @@ -84,7 +84,7 @@ def __init__( self.channel.append(self.queue) def _check_if_open(self) -> None: - """Raises :exc:`~can.exceptions.CanOperationError` if the bus is not open. + """Raises :class:`~can.CanOperationError` if the bus is not open. Has to be called in every method that accesses the bus. """ diff --git a/can/io/blf.py b/can/io/blf.py index 93fa54ca2..062526014 100644 --- a/can/io/blf.py +++ b/can/io/blf.py @@ -11,7 +11,7 @@ of uncompressed data each. This data contains the actual CAN messages and other objects types. """ - +import gzip import struct import zlib import datetime @@ -395,6 +395,11 @@ def __init__( speed and compression (currently equivalent to level 6). """ mode = "rb+" if append else "wb" + if type(file) == gzip.GzipFile: + raise ValueError( + f"The BLFWriter is currently incompatible with " + f"{gzip.GzipFile.__name__}s. Try using a different zip format." + ) try: super().__init__(file, mode=mode) except FileNotFoundError: diff --git a/can/io/logger.py b/can/io/logger.py index 09312101b..b50825d3f 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -7,13 +7,14 @@ from abc import ABC, abstractmethod from datetime import datetime import gzip -from typing import Any, Optional, Callable, Type, Tuple, cast, Dict, Set +from typing import Any, Optional, Callable, Type, Tuple, cast, Dict from types import TracebackType from typing_extensions import Literal from pkg_resources import iter_entry_points +import can.io from ..message import Message from ..listener import Listener from .generic import BaseIOHandler, FileIOMessageWriter, MessageWriter @@ -87,7 +88,7 @@ def __new__( # type: ignore file_or_filename: AcceptedIOType = filename if suffix == ".gz": - suffix, file_or_filename = Logger.compress(filename, *args, **kwargs) + suffix, file_or_filename = Logger.compress(filename) try: return Logger.message_writers[suffix](file_or_filename, *args, **kwargs) @@ -97,18 +98,13 @@ def __new__( # type: ignore ) from None @staticmethod - def compress( - filename: StringPathLike, *args: Any, **kwargs: Any - ) -> Tuple[str, FileLike]: + def compress(filename: StringPathLike) -> Tuple[str, FileLike]: """ Return the suffix and io object of the decompressed file. File will automatically recompress upon close. """ real_suffix = pathlib.Path(filename).suffixes[-2].lower() - if kwargs.get("append", False): - mode = "ab" if real_suffix == ".blf" else "at" - else: - mode = "wb" if real_suffix == ".blf" else "wt" + mode = "ab" if real_suffix == ".blf" else "at" return real_suffix, gzip.open(filename, mode) @@ -119,32 +115,32 @@ def on_message_received(self, msg: Message) -> None: class BaseRotatingLogger(Listener, BaseIOHandler, ABC): """ Base class for rotating CAN loggers. This class is not meant to be - instantiated directly. Subclasses must implement the :meth:`should_rollover` - and :meth:`do_rollover` methods according to their rotation strategy. + instantiated directly. Subclasses must implement the :attr:`should_rollover` + and `do_rollover` methods according to their rotation strategy. The rotation behavior can be further customized by the user by setting the :attr:`namer` and :attr:`rotator` attributes after instantiating the subclass. - These attributes as well as the methods :meth:`rotation_filename` and :meth:`rotate` + These attributes as well as the methods `rotation_filename` and `rotate` and the corresponding docstrings are carried over from the python builtin - :class:`~logging.handlers.BaseRotatingHandler`. + `BaseRotatingHandler`. Subclasses must set the `_writer` attribute upon initialization. - """ - _supported_formats: Set[str] = set() + :attr namer: + If this attribute is set to a callable, the :meth:`rotation_filename` method + delegates to this callable. The parameters passed to the callable are + those passed to :meth:`rotation_filename`. + :attr rotator: + If this attribute is set to a callable, the :meth:`rotate` method delegates + to this callable. The parameters passed to the callable are those + passed to :meth:`rotate`. + :attr rollover_count: + An integer counter to track the number of rollovers. + """ - #: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotation_filename` - #: method delegates to this callable. The parameters passed to the callable are - #: those passed to :meth:`~BaseRotatingLogger.rotation_filename`. namer: Optional[Callable[[StringPathLike], StringPathLike]] = None - - #: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotate` method - #: delegates to this callable. The parameters passed to the callable are those - #: passed to :meth:`~BaseRotatingLogger.rotate`. rotator: Optional[Callable[[StringPathLike, StringPathLike], None]] = None - - #: An integer counter to track the number of rollovers. rollover_count: int = 0 def __init__(self, *args: Any, **kwargs: Any) -> None: @@ -168,7 +164,7 @@ def rotation_filename(self, default_name: StringPathLike) -> StringPathLike: This is provided so that a custom filename can be provided. The default implementation calls the :attr:`namer` attribute of the handler, if it's callable, passing the default name to - it. If the attribute isn't callable (the default is :obj:`None`), the name + it. If the attribute isn't callable (the default is `None`), the name is returned unchanged. :param default_name: @@ -183,8 +179,8 @@ def rotate(self, source: StringPathLike, dest: StringPathLike) -> None: """When rotating, rotate the current log. The default implementation calls the :attr:`rotator` attribute of the - handler, if it's callable, passing the `source` and `dest` arguments to - it. If the attribute isn't callable (the default is :obj:`None`), the source + handler, if it's callable, passing the source and dest arguments to + it. If the attribute isn't callable (the default is `None`), the source is simply renamed to the destination. :param source: @@ -224,21 +220,17 @@ def _get_new_writer(self, filename: StringPathLike) -> FileIOMessageWriter: :return: An instance of a writer class. """ - suffix = "".join(pathlib.Path(filename).suffixes[-2:]).lower() - - if suffix in self._supported_formats: - logger = Logger(filename, *self.writer_args, **self.writer_kwargs) - if isinstance(logger, FileIOMessageWriter): - return logger - elif isinstance(logger, Printer) and logger.file is not None: - return cast(FileIOMessageWriter, logger) - - raise Exception( - f'The log format "{suffix}" ' - f"is not supported by {self.__class__.__name__}. " - f"{self.__class__.__name__} supports the following formats: " - f"{', '.join(self._supported_formats)}" - ) + + logger = Logger(filename, *self.writer_args, **self.writer_kwargs) + if isinstance(logger, FileIOMessageWriter): + return logger + elif isinstance(logger, Printer) and logger.file is not None: + return cast(FileIOMessageWriter, logger) + else: + raise Exception( + f"The log format \"{''.join(pathlib.Path(filename).suffixes[-2:])}" + f'" is not supported by {self.__class__.__name__}' + ) def stop(self) -> None: """Stop handling new messages. @@ -276,10 +268,8 @@ class SizedRotatingLogger(BaseRotatingLogger): by adding a timestamp and the rollover count. A new log file is then created and written to. - This behavior can be customized by setting the - :attr:`~can.io.BaseRotatingLogger.namer` and - :attr:`~can.io.BaseRotatingLogger.rotator` - attribute. + This behavior can be customized by setting the :attr:`namer` and + :attr:`rotator` attribute. Example:: @@ -310,8 +300,6 @@ class SizedRotatingLogger(BaseRotatingLogger): :meth:`~can.Listener.stop` is called. """ - _supported_formats = {".asc", ".blf", ".csv", ".log", ".txt"} - def __init__( self, base_filename: StringPathLike, @@ -357,11 +345,11 @@ def _default_name(self) -> StringPathLike: """Generate the default rotation filename.""" path = pathlib.Path(self.base_filename) new_name = ( - path.stem.split(".")[0] + path.stem + "_" + datetime.now().strftime("%Y-%m-%dT%H%M%S") + "_" + f"#{self.rollover_count:03}" - + "".join(path.suffixes[-2:]) + + path.suffix ) return str(path.parent / new_name) diff --git a/can/io/printer.py b/can/io/printer.py index 6a43c63b9..61871e8ad 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -4,7 +4,7 @@ import logging -from typing import Optional, TextIO, Union, Any, cast +from typing import Optional, TextIO, Union, Any from ..message import Message from .generic import MessageWriter @@ -40,18 +40,11 @@ def __init__( :param append: If set to `True` messages, are appended to the file, else the file is truncated """ - self.write_to_file = file is not None mode = "a" if append else "w" super().__init__(file, mode=mode) def on_message_received(self, msg: Message) -> None: - if self.write_to_file: - cast(TextIO, self.file).write(str(msg) + "\n") + if self.file is not None: + self.file.write(str(msg) + "\n") else: print(msg) - - def file_size(self) -> int: - """Return an estimate of the current file size in bytes.""" - if self.file is not None: - return self.file.tell() - return 0 diff --git a/can/io/sqlite.py b/can/io/sqlite.py index 0a4de85f2..b9cbf9f93 100644 --- a/can/io/sqlite.py +++ b/can/io/sqlite.py @@ -25,7 +25,7 @@ class SqliteReader(MessageReader): This class can be iterated over or used to fetch all messages in the database with :meth:`~SqliteReader.read_all`. - Calling :func:`len` on this object might not run in constant time. + Calling :func:`~builtin.len` on this object might not run in constant time. :attr str table_name: the name of the database table used for storing the messages diff --git a/can/listener.py b/can/listener.py index 6c9cdf0be..12836a83c 100644 --- a/can/listener.py +++ b/can/listener.py @@ -105,13 +105,13 @@ def on_message_received(self, msg: Message) -> None: def get_message(self, timeout: float = 0.5) -> Optional[Message]: """ - Attempts to retrieve the message that has been in the queue for the longest amount - of time (FIFO). If no message is available, it blocks for given timeout or until a - message is received (whichever is shorter), or else returns None. This method does - not block after :meth:`can.BufferedReader.stop` has been called. + Attempts to retrieve the latest message received by the instance. If no message is + available it blocks for given timeout or until a message is received, or else + returns None (whichever is shorter). This method does not block after + :meth:`can.BufferedReader.stop` has been called. :param timeout: The number of seconds to wait for a new message. - :return: the received :class:`can.Message` or `None`, if the queue is empty. + :return: the Message if there is one, or None if there is not. """ try: if self.is_stopped: diff --git a/can/logger.py b/can/logger.py index 8cb201987..0b73dd785 100644 --- a/can/logger.py +++ b/can/logger.py @@ -193,10 +193,9 @@ def main() -> None: "--file_size", dest="file_size", type=int, - help="Maximum file size in bytes. Rotate log file when size threshold " - "is reached. (The resulting file sizes will be consistent, but are not " - "guaranteed to be exactly what is specified here due to the rollover " - "conditions being logger implementation specific.)", + help="Maximum file size in bytes (or for the case of blf, maximum " + "buffer size before compression and flush to file). Rotate log " + "file when size threshold is reached.", default=None, ) diff --git a/can/message.py b/can/message.py index 8e0c4deee..87cb6a199 100644 --- a/can/message.py +++ b/can/message.py @@ -29,7 +29,7 @@ class Message: # pylint: disable=too-many-instance-attributes; OK for a datacla :func:`~copy.copy`/:func:`~copy.deepcopy` is supported as well. Messages do not support "dynamic" attributes, meaning any others than the - documented ones, since it uses :obj:`~object.__slots__`. + documented ones, since it uses :attr:`~object.__slots__`. """ __slots__ = ( diff --git a/can/typechecking.py b/can/typechecking.py index b3a513a3a..ed76b6c85 100644 --- a/can/typechecking.py +++ b/can/typechecking.py @@ -8,9 +8,7 @@ import typing_extensions -CanFilter: typing_extensions = typing_extensions.TypedDict( - "CanFilter", {"can_id": int, "can_mask": int} -) +CanFilter = typing_extensions.TypedDict("CanFilter", {"can_id": int, "can_mask": int}) CanFilterExtended = typing_extensions.TypedDict( "CanFilterExtended", {"can_id": int, "can_mask": int, "extended": bool} ) diff --git a/can/util.py b/can/util.py index e64eb13b5..d1ff643de 100644 --- a/can/util.py +++ b/can/util.py @@ -258,10 +258,8 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig: def set_logging_level(level_name: str) -> None: """Set the logging level for the `"can"` logger. - :param level_name: - One of: `'critical'`, `'error'`, `'warning'`, `'info'`, - `'debug'`, `'subdebug'`, or the value :obj:`None` (=default). - Defaults to `'debug'`. + :param level_name: One of: `'critical'`, `'error'`, `'warning'`, `'info'`, + `'debug'`, `'subdebug'`, or the value `None` (=default). Defaults to `'debug'`. """ can_logger = logging.getLogger("can") @@ -318,7 +316,7 @@ def deprecated_args_alias(**aliases): """Allows to rename/deprecate a function kwarg(s) and optionally have the deprecated kwarg(s) set as alias(es) - Example:: + Example: @deprecated_args_alias(oldArg="new_arg", anotherOldArg="another_new_arg") def library_function(new_arg, another_new_arg): @@ -327,7 +325,6 @@ def library_function(new_arg, another_new_arg): @deprecated_args_alias(oldArg="new_arg", obsoleteOldArg=None) def library_function(new_arg): pass - """ def deco(f): diff --git a/doc/api.rst b/doc/api.rst index 23342f992..011553b1b 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -1,7 +1,7 @@ Library API =========== -The main objects are the :class:`~can.Bus` and the :class:`~can.Message`. +The main objects are the :class:`~can.BusABC` and the :class:`~can.Message`. A form of CAN interface is also required. .. hint:: diff --git a/doc/bcm.rst b/doc/bcm.rst index 94cde0e60..549b06edd 100644 --- a/doc/bcm.rst +++ b/doc/bcm.rst @@ -42,7 +42,3 @@ which inherits from :class:`~can.broadcastmanager.CyclicTask`. .. autoclass:: can.RestartableCyclicTaskABC :members: - -.. autoclass:: can.broadcastmanager.ThreadBasedCyclicSendTask - :members: - diff --git a/doc/bus.rst b/doc/bus.rst index 4db49ee29..bbe52cbd6 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -15,16 +15,22 @@ and implements the :class:`~can.BusABC` API. A thread safe bus wrapper is also available, see `Thread safe bus`_. +Autoconfig Bus +'''''''''''''' + .. autoclass:: can.Bus - :class-doc-from: class - :show-inheritance: :members: - :inherited-members: + :undoc-members: + + +API +''' -.. autoclass:: can.bus.BusState +.. autoclass:: can.BusABC :members: :undoc-members: + .. automethod:: __iter__ Transmitting '''''''''''' @@ -68,7 +74,7 @@ Example defining two filters, one to pass 11-bit ID ``0x451``, the other to pass See :meth:`~can.BusABC.set_filters` for the implementation. Thread safe bus -''''''''''''''' +--------------- This thread safe version of the :class:`~can.BusABC` class can be used by multiple threads at once. Sending and receiving is locked separately to avoid unnecessary delays. diff --git a/doc/conf.py b/doc/conf.py index c1409b8c2..14390d5ad 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -8,8 +8,6 @@ import sys import os -import ctypes -from unittest.mock import MagicMock # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -17,7 +15,6 @@ sys.path.insert(0, os.path.abspath("..")) import can # pylint: disable=wrong-import-position -from can import ctypesutil # -- General configuration ----------------------------------------------------- @@ -48,12 +45,11 @@ "sphinx.ext.viewcode", "sphinx.ext.graphviz", "sphinxcontrib.programoutput", - "sphinx_inline_tabs", - "sphinx_rtd_theme", + "sphinx_autodoc_typehints", ] # Now, you can use the alias name as a new role, e.g. :issue:`123`. -extlinks = {"issue": ("https://github.com/hardbyte/python-can/issues/%s/", "issue #%s")} +extlinks = {"issue": ("https://github.com/hardbyte/python-can/issues/%s/", "issue ")} intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} @@ -115,31 +111,11 @@ # Keep cached intersphinx inventories indefinitely intersphinx_cache_limit = -1 -# location of typehints -autodoc_typehints = "description" - -# disable specific warnings -nitpick_ignore = [ - # Ignore warnings for type aliases. Remove once Sphinx supports PEP613 - ("py:class", "can.typechecking.BusConfig"), - ("py:class", "can.typechecking.CanFilter"), - ("py:class", "can.typechecking.CanFilterExtended"), - ("py:class", "can.typechecking.AutoDetectedConfig"), - # intersphinx fails to reference some builtins - ("py:class", "asyncio.events.AbstractEventLoop"), - ("py:class", "_thread.allocate_lock"), -] - -# mock windows specific attributes -autodoc_mock_imports = ["win32com"] -ctypes.windll = MagicMock() -ctypesutil.HRESULT = ctypes.c_long - # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "sphinx_rtd_theme" +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/doc/configuration.rst b/doc/configuration.rst index d92d6164f..9bda3030f 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -1,5 +1,3 @@ -.. _configuration: - Configuration ============= @@ -102,7 +100,6 @@ For example: ``CAN_INTERFACE=socketcan CAN_CONFIG={"receive_own_messages": true, "fd": true}`` -.. _interface names: Interface Names --------------- @@ -112,51 +109,31 @@ Lookup table of interface names: +---------------------+-------------------------------------+ | Name | Documentation | +=====================+=====================================+ -| ``"canalystii"`` | :doc:`interfaces/canalystii` | -+---------------------+-------------------------------------+ -| ``"cantact"`` | :doc:`interfaces/cantact` | +| ``"socketcan"`` | :doc:`interfaces/socketcan` | +---------------------+-------------------------------------+ -| ``"etas"`` | :doc:`interfaces/etas` | +| ``"kvaser"`` | :doc:`interfaces/kvaser` | +---------------------+-------------------------------------+ -| ``"gs_usb"`` | :doc:`interfaces/gs_usb` | +| ``"serial"`` | :doc:`interfaces/serial` | +---------------------+-------------------------------------+ -| ``"iscan"`` | :doc:`interfaces/iscan` | +| ``"slcan"`` | :doc:`interfaces/slcan` | +---------------------+-------------------------------------+ | ``"ixxat"`` | :doc:`interfaces/ixxat` | +---------------------+-------------------------------------+ -| ``"kvaser"`` | :doc:`interfaces/kvaser` | -+---------------------+-------------------------------------+ -| ``"neousys"`` | :doc:`interfaces/neousys` | +| ``"pcan"`` | :doc:`interfaces/pcan` | +---------------------+-------------------------------------+ -| ``"neovi"`` | :doc:`interfaces/neovi` | +| ``"usb2can"`` | :doc:`interfaces/usb2can` | +---------------------+-------------------------------------+ | ``"nican"`` | :doc:`interfaces/nican` | +---------------------+-------------------------------------+ -| ``"nixnet"`` | :doc:`interfaces/nixnet` | -+---------------------+-------------------------------------+ -| ``"pcan"`` | :doc:`interfaces/pcan` | -+---------------------+-------------------------------------+ -| ``"robotell"`` | :doc:`interfaces/robotell` | -+---------------------+-------------------------------------+ -| ``"seeedstudio"`` | :doc:`interfaces/seeedstudio` | +| ``"iscan"`` | :doc:`interfaces/iscan` | +---------------------+-------------------------------------+ -| ``"serial"`` | :doc:`interfaces/serial` | +| ``"neovi"`` | :doc:`interfaces/neovi` | +---------------------+-------------------------------------+ -| ``"slcan"`` | :doc:`interfaces/slcan` | +| ``"vector"`` | :doc:`interfaces/vector` | +---------------------+-------------------------------------+ -| ``"socketcan"`` | :doc:`interfaces/socketcan` | +| ``"virtual"`` | :doc:`interfaces/virtual` | +---------------------+-------------------------------------+ -| ``"socketcand"`` | :doc:`interfaces/socketcand` | +| ``"canalystii"`` | :doc:`interfaces/canalystii` | +---------------------+-------------------------------------+ | ``"systec"`` | :doc:`interfaces/systec` | +---------------------+-------------------------------------+ -| ``"udp_multicast"`` | :doc:`interfaces/udp_multicast` | -+---------------------+-------------------------------------+ -| ``"usb2can"`` | :doc:`interfaces/usb2can` | -+---------------------+-------------------------------------+ -| ``"vector"`` | :doc:`interfaces/vector` | -+---------------------+-------------------------------------+ -| ``"virtual"`` | :doc:`interfaces/virtual` | -+---------------------+-------------------------------------+ - -Additional interface types can be added via the :ref:`plugin interface`. \ No newline at end of file diff --git a/doc/development.rst b/doc/development.rst index 055401bdc..03bf9a374 100644 --- a/doc/development.rst +++ b/doc/development.rst @@ -35,8 +35,8 @@ The following assumes that the commands are executed from the root of the reposi The project can be built with:: - pipx run build - pipx run twine check dist/* + pip install wheel + python setup.py sdist bdist_wheel The project can be installed in editable mode with:: @@ -44,7 +44,8 @@ The project can be installed in editable mode with:: The unit tests can be run with:: - pipx run tox -e py + pip install tox + tox -e py The documentation can be built with:: @@ -78,11 +79,6 @@ These steps are a guideline on how to add a new backend to python-can. To get started, have a look at ``back2back_test.py``: Simply add a test case like ``BasicTestSocketCan`` and some basic tests will be executed for the new interface. -.. attention:: - We strongly recommend using the :ref:`plugin interface` to extend python-can. - Publish a python package that contains your :class:`can.BusABC` subclass and use - it within the python-can API. We will mention your package inside this documentation - and add it as an optional dependency. Code Structure -------------- diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt index b1c2da632..dead5e2e5 100644 --- a/doc/doc-requirements.txt +++ b/doc/doc-requirements.txt @@ -1,4 +1,3 @@ -sphinx>=5.2.3 +sphinx>=1.8.1 sphinxcontrib-programoutput -sphinx_rtd_theme -sphinx-inline-tabs +sphinx-autodoc-typehints diff --git a/doc/installation.rst b/doc/installation.rst index bfce72180..fe7204ba6 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -103,16 +103,6 @@ If ``python-can`` is already installed, the CANtact backend can be installed sep Additional CANtact documentation is available at `cantact.io `__. -CanViewer -~~~~~~~~~ - -``python-can`` has support for showing a simple CAN viewer terminal application -by running ``python -m can.viewer``. On Windows, this depends on the -`windows-curses library `__ which can -be installed with: - -``python -m pip install "python-can[viewer]"`` - Installing python-can in development mode ----------------------------------------- diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 54d70ca86..757cf67b4 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -1,5 +1,3 @@ -.. _can interface modules: - CAN Interface Modules --------------------- @@ -14,13 +12,11 @@ The available interfaces are: :maxdepth: 1 interfaces/canalystii - interfaces/cantact interfaces/etas interfaces/gs_usb interfaces/iscan interfaces/ixxat interfaces/kvaser - interfaces/neousys interfaces/neovi interfaces/nican interfaces/nixnet @@ -30,65 +26,25 @@ The available interfaces are: interfaces/serial interfaces/slcan interfaces/socketcan - interfaces/socketcand interfaces/systec interfaces/udp_multicast interfaces/usb2can interfaces/vector interfaces/virtual -The *Interface Names* are listed in :doc:`configuration`. - - -.. _plugin interface: - -Plugin Interface -^^^^^^^^^^^^^^^^ - -External packages can register new interfaces by using the ``can.interface`` entry point -in its project configuration. The format of the entry point depends on your project -configuration format (*pyproject.toml*, *setup.cfg* or *setup.py*). - -In the following example ``module`` defines the location of your bus class inside your -package e.g. ``my_package.subpackage.bus_module`` and ``classname`` is the name of -your :class:`can.BusABC` subclass. - -.. tab:: pyproject.toml (PEP 621) - - .. code-block:: toml +Additional interfaces can be added via a plugin interface. An external package +can register a new interface by using the ``can.interface`` entry point in its setup.py. - # Note the quotes around can.interface in order to escape the dot . - [project.entry-points."can.interface"] - interface_name = "module:classname" +The format of the entry point is ``interface_name=module:classname`` where +``classname`` is a concrete :class:`can.BusABC` implementation. -.. tab:: setup.cfg +:: - .. code-block:: ini + entry_points={ + 'can.interface': [ + "interface_name=module:classname", + ] + }, - [options.entry_points] - can.interface = - interface_name = module:classname -.. tab:: setup.py - - .. code-block:: python - - from setuptools import setup - - setup( - # ..., - entry_points = { - 'can.interface': [ - 'interface_name = module:classname' - ] - } - ) - -The ``interface_name`` can be used to -create an instance of the bus in the **python-can** API: - -.. code-block:: python - - import can - - bus = can.Bus(interface="interface_name", channel=0) +The *Interface Names* are listed in :doc:`configuration`. diff --git a/doc/interfaces/cantact.rst b/doc/interfaces/cantact.rst deleted file mode 100644 index dc9667218..000000000 --- a/doc/interfaces/cantact.rst +++ /dev/null @@ -1,8 +0,0 @@ -CANtact CAN Interface -===================== - -Interface for CANtact devices from Linklayer Labs - -.. autoclass:: can.interfaces.cantact.CantactBus - :show-inheritance: - :members: diff --git a/doc/interfaces/etas.rst b/doc/interfaces/etas.rst index 2b59a4eee..cc3cbdea4 100644 --- a/doc/interfaces/etas.rst +++ b/doc/interfaces/etas.rst @@ -6,7 +6,7 @@ The ETAS BOA_ (Basic Open API) is used. Install the "ETAS ECU and Bus Interfaces – Distribution Package". Only Windows is supported by this interface. The Linux kernel v5.13 (and greater) natively supports ETAS ES581.4, ES582.1 and ES584.1 USB modules. -To use these under Linux, please refer to :ref:`SocketCAN`. +To use these under Linux, please refer to :ref:`socketcan`. Bus --- @@ -25,7 +25,7 @@ The simplest configuration file would be:: Channels are the URIs used by the underlying API. -To find available URIs, use :meth:`~can.detect_available_configs`:: +To find available URIs, use :meth:`~can.interface.detect_available_configs`:: configs = can.interface.detect_available_configs(interfaces="etas") for c in configs: diff --git a/doc/interfaces/ixxat.rst b/doc/interfaces/ixxat.rst index 02e707c1c..28fb6f314 100644 --- a/doc/interfaces/ixxat.rst +++ b/doc/interfaces/ixxat.rst @@ -8,7 +8,7 @@ Interface to `IXXAT `__ Virtual CAN Interface V3 SDK. Wor The Linux ECI SDK is currently unsupported, however on Linux some devices are supported with :doc:`socketcan`. -The :meth:`~can.BusABC.send_periodic` method is supported +The :meth:`~can.interfaces.ixxat.canlib.IXXATBus.send_periodic` method is supported natively through the on-board cyclic transmit list. Modifying cyclic messages is not possible. You will need to stop it, and then start a new periodic message. @@ -20,22 +20,7 @@ Bus .. autoclass:: can.interfaces.ixxat.IXXATBus :members: -Implementation based on vcinpl.dll -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.IXXATBus - :members: - -.. autoclass:: can.interfaces.ixxat.canlib_vcinpl.CyclicSendTask - :members: - -Implementation based on vcinpl2.dll -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.IXXATBus - :members: - -.. autoclass:: can.interfaces.ixxat.canlib_vcinpl2.CyclicSendTask +.. autoclass:: can.interfaces.ixxat.canlib.CyclicSendTask :members: diff --git a/doc/interfaces/kvaser.rst b/doc/interfaces/kvaser.rst index 4e0062cfa..a4a51ad09 100644 --- a/doc/interfaces/kvaser.rst +++ b/doc/interfaces/kvaser.rst @@ -46,5 +46,3 @@ This section contains Kvaser driver specific methods. .. automethod:: can.interfaces.kvaser.canlib.KvaserBus.get_stats -.. autoclass:: can.interfaces.kvaser.structures.BusStatistics - :members: diff --git a/doc/interfaces/neousys.rst b/doc/interfaces/neousys.rst deleted file mode 100644 index 97a37868c..000000000 --- a/doc/interfaces/neousys.rst +++ /dev/null @@ -1,13 +0,0 @@ -Neousys CAN Interface -===================== - -This kind of interface can be found for example on Neousys POC-551VTC -One needs to have correct drivers and DLL (Share object for Linux) from -`Neousys `_. - -Beware this is only tested on Linux kernel higher than v5.3. This should be drop in -with Windows but you have to replace with correct named DLL - -.. autoclass:: can.interfaces.neousys.NeousysBus - :show-inheritance: - :members: diff --git a/doc/interfaces/neovi.rst b/doc/interfaces/neovi.rst index 588c5e914..05423ac8e 100644 --- a/doc/interfaces/neovi.rst +++ b/doc/interfaces/neovi.rst @@ -42,6 +42,5 @@ Bus --- .. autoclass:: can.interfaces.ics_neovi.NeoViBus -.. autoexception:: can.interfaces.ics_neovi.ICSApiError -.. autoexception:: can.interfaces.ics_neovi.ICSInitializationError -.. autoexception:: can.interfaces.ics_neovi.ICSOperationError + + diff --git a/doc/interfaces/nican.rst b/doc/interfaces/nican.rst index 4d2a40717..b2214371f 100644 --- a/doc/interfaces/nican.rst +++ b/doc/interfaces/nican.rst @@ -21,7 +21,6 @@ Bus .. autoclass:: can.interfaces.nican.NicanBus .. autoexception:: can.interfaces.nican.NicanError -.. autoexception:: can.interfaces.nican.NicanInitializationError .. _National Instruments: http://www.ni.com/can/ diff --git a/doc/interfaces/pcan.rst b/doc/interfaces/pcan.rst index feb40b195..ff82ba9f4 100644 --- a/doc/interfaces/pcan.rst +++ b/doc/interfaces/pcan.rst @@ -48,4 +48,3 @@ Bus --- .. autoclass:: can.interfaces.pcan.PcanBus - :members: diff --git a/doc/interfaces/seeedstudio.rst b/doc/interfaces/seeedstudio.rst index da4d86995..5c86fa688 100644 --- a/doc/interfaces/seeedstudio.rst +++ b/doc/interfaces/seeedstudio.rst @@ -1,8 +1,9 @@ .. _seeeddoc: -Seeed Studio USB-CAN Analyzer -============================= +USB-CAN Analyzer +================ +...by Seeed Studio SKU: 114991193 diff --git a/doc/interfaces/serial.rst b/doc/interfaces/serial.rst index 99ee54df6..59ffef21a 100644 --- a/doc/interfaces/serial.rst +++ b/doc/interfaces/serial.rst @@ -21,8 +21,6 @@ Bus .. autoclass:: can.interfaces.serial.serial_can.SerialBus - .. automethod:: _recv_internal - Internals --------- The frames that will be sent and received over the serial interface consist of diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index f6cc1ba5f..d3a583d75 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -1,9 +1,7 @@ -.. _SocketCAN: - SocketCAN ========= -The SocketCAN documentation can be found in the `Linux kernel docs`_ at +The `SocketCAN`_ documentation can be found in the Linux kernel docs at ``networking`` directory. Quoting from the SocketCAN Linux documentation:: > The socketcan package is an implementation of CAN protocols @@ -59,28 +57,6 @@ existing ``can0`` interface with a bitrate of 1MB: sudo ip link set can0 up type can bitrate 1000000 -CAN over Serial / SLCAN -~~~~~~~~~~~~~~~~~~~~~~~ - -SLCAN adapters can be used directly via :doc:`/interfaces/slcan`, or -via :doc:`/interfaces/socketcan` with some help from the ``slcand`` utility -which can be found in the `can-utils `_ package. - -To create a socketcan interface for an SLCAN adapter run the following: - -.. code-block:: bash - - slcand -f -o -c -s5 /dev/ttyAMA0 - ip link set up slcan0 - -Names of the interfaces created by ``slcand`` match the ``slcan\d+`` regex. -If a custom name is required, it can be specified as the last argument. E.g.: - -.. code-block:: bash - - slcand -f -o -c -s5 /dev/ttyAMA0 can0 - ip link set up can0 - .. _socketcan-pcan: PCAN @@ -286,7 +262,7 @@ to ensure usage of SocketCAN Linux API. The most important differences are: .. External references -.. _Linux kernel docs: https://www.kernel.org/doc/Documentation/networking/can.txt +.. _SocketCAN: https://www.kernel.org/doc/Documentation/networking/can.txt .. _Intrepid kernel module: https://github.com/intrepidcs/intrepid-socketcan-kernel-module .. _Intrepid user-space daemon: https://github.com/intrepidcs/icsscand .. _can-utils: https://github.com/linux-can/can-utils diff --git a/doc/interfaces/socketcand.rst b/doc/interfaces/socketcand.rst index 3c05bcc85..e50f134e1 100644 --- a/doc/interfaces/socketcand.rst +++ b/doc/interfaces/socketcand.rst @@ -28,8 +28,8 @@ daemon running on a remote Raspberry Pi: except KeyboardInterrupt: pass -The output may look like this:: - +The output may look like this: +:: Timestamp: 1637791111.209224 ID: 000006fd X Rx DLC: 8 c4 10 e3 2d 96 ff 25 6b Timestamp: 1637791111.233951 ID: 000001ad X Rx DLC: 4 4d 47 c7 64 Timestamp: 1637791111.409415 ID: 000005f7 X Rx DLC: 8 86 de e6 0f 42 55 5d 39 @@ -47,9 +47,8 @@ However, it will also work with any other socketcan device. Install CAN Interface for a MCP2515 based interface on a Raspberry Pi ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Add the following lines to ``/boot/config.txt``. -Please take care on the frequency of the crystal on your MCP2515 board:: - +Add the following lines to ``/boot/config.txt``. Please take care on the frequency of the crystal on your MCP2515 board: +:: dtparam=spi=on dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25,spimaxfrequency=1000000 @@ -107,8 +106,8 @@ Run socketcand ./socketcand -v -i can0 -During start, socketcand will prompt its IP address and port it listens to:: - +During start, socketcand will prompt its IP address and port it listens to: +:: Verbose output activated Using network interface 'eth0' diff --git a/doc/interfaces/usb2can.rst b/doc/interfaces/usb2can.rst index 56243d41d..e2e8d7517 100644 --- a/doc/interfaces/usb2can.rst +++ b/doc/interfaces/usb2can.rst @@ -86,5 +86,3 @@ Internals .. autoclass:: can.interfaces.usb2can.Usb2CanAbstractionLayer :members: :undoc-members: - -.. autoexception:: can.interfaces.usb2can.usb2canabstractionlayer.CanalError diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst index 7f9aa1f3f..dcd45f1bf 100644 --- a/doc/interfaces/vector.rst +++ b/doc/interfaces/vector.rst @@ -4,7 +4,7 @@ Vector This interface adds support for CAN controllers by `Vector`_. Only Windows is supported. By default this library uses the channel configuration for CANalyzer. -To use a different application, open **Vector Hardware Configuration** program and create +To use a different application, open Vector Hardware Config program and create a new application and assign the channels you may want to use. Specify the application name as ``app_name='Your app name'`` when constructing the bus or in a config file. @@ -19,98 +19,17 @@ application named "python-can":: channel = 0, 1 app_name = python-can +If you are using Python 2.7 it is recommended to install pywin32_, otherwise a +slow and CPU intensive polling will be used when waiting for new messages. -VectorBus ---------- +Bus +--- .. autoclass:: can.interfaces.vector.VectorBus - :show-inheritance: - :member-order: bysource - :members: - set_filters, - recv, - send, - send_periodic, - stop_all_periodic_tasks, - flush_tx_buffer, - reset, - shutdown, - popup_vector_hw_configuration, - get_application_config, - set_application_config - -Exceptions ----------- .. autoexception:: can.interfaces.vector.VectorError - :show-inheritance: -.. autoexception:: can.interfaces.vector.VectorInitializationError - :show-inheritance: -.. autoexception:: can.interfaces.vector.VectorOperationError - :show-inheritance: - -Miscellaneous -------------- - -.. autofunction:: can.interfaces.vector.get_channel_configs - -.. autoclass:: can.interfaces.vector.VectorChannelConfig - :show-inheritance: - :class-doc-from: class - -.. autoclass:: can.interfaces.vector.canlib.VectorBusParams - :show-inheritance: - :class-doc-from: class - -.. autoclass:: can.interfaces.vector.canlib.VectorCanParams - :show-inheritance: - :class-doc-from: class - -.. autoclass:: can.interfaces.vector.canlib.VectorCanFdParams - :show-inheritance: - :class-doc-from: class - -.. autoclass:: can.interfaces.vector.xldefine.XL_HardwareType - :show-inheritance: - :member-order: bysource - :members: - :undoc-members: - -.. autoclass:: can.interfaces.vector.xldefine.XL_ChannelCapabilities - :show-inheritance: - :member-order: bysource - :members: - :undoc-members: - -.. autoclass:: can.interfaces.vector.xldefine.XL_BusCapabilities - :show-inheritance: - :member-order: bysource - :members: - :undoc-members: - -.. autoclass:: can.interfaces.vector.xldefine.XL_BusTypes - :show-inheritance: - :member-order: bysource - :members: - :undoc-members: - -.. autoclass:: can.interfaces.vector.xldefine.XL_OutputMode - :show-inheritance: - :member-order: bysource - :members: - :undoc-members: - -.. autoclass:: can.interfaces.vector.xldefine.XL_CANFD_BusParams_CanOpMode - :show-inheritance: - :member-order: bysource - :members: - :undoc-members: -.. autoclass:: can.interfaces.vector.xldefine.XL_Status - :show-inheritance: - :member-order: bysource - :members: - :undoc-members: .. _Vector: https://vector.com/ +.. _pywin32: https://sourceforge.net/projects/pywin32/ diff --git a/doc/interfaces/virtual.rst b/doc/interfaces/virtual.rst index 29976ed47..9258c9bbd 100644 --- a/doc/interfaces/virtual.rst +++ b/doc/interfaces/virtual.rst @@ -70,8 +70,8 @@ arrive at the recipients exactly once. Both is not guaranteed to hold for the be these guarantees of message delivery and message ordering. The central servers receive and distribute the CAN messages to all other bus participants, unlike in a real physical CAN network. The first intra-process ``virtual`` interface only runs within one Python process, effectively the -Python instance of :class:`~can.interfaces.virtual.VirtualBus` acts as a central server. -Notably the ``udp_multicast`` bus does not require a central server. +Python instance of :class:`VirtualBus` acts as a central server. Notably the ``udp_multicast`` bus +does not require a central server. **Arbitration and throughput** are two interrelated functions/properties of CAN networks which are typically abstracted in virtual interfaces. In all four interfaces, an unlimited amount @@ -133,5 +133,3 @@ Bus Class Documentation .. autoclass:: can.interfaces.virtual.VirtualBus :members: - - .. automethod:: _detect_available_configs diff --git a/doc/internal-api.rst b/doc/internal-api.rst index 3ef599598..c43db3394 100644 --- a/doc/internal-api.rst +++ b/doc/internal-api.rst @@ -55,22 +55,22 @@ configuration into account. Bus Internals ~~~~~~~~~~~~~ -Several methods are not documented in the main :class:`can.Bus` +Several methods are not documented in the main :class:`can.BusABC` as they are primarily useful for library developers as opposed to library users. This is the entire ABC bus class with all internal methods: .. autoclass:: can.BusABC - :members: :private-members: :special-members: + :noindex: About the IO module ------------------- -Handling of the different file formats is implemented in ``can.io``. +Handling of the different file formats is implemented in :mod:`can.io`. Each file/IO type is within a separate module and ideally implements both a *Reader* and a *Writer*. The reader usually extends :class:`can.io.generic.BaseIOHandler`, while the writer often additionally extends :class:`can.Listener`, diff --git a/doc/message.rst b/doc/message.rst index 78ccc0b50..e5745f6b5 100644 --- a/doc/message.rst +++ b/doc/message.rst @@ -200,5 +200,3 @@ Message Each of the bytes in the data field (when present) are represented as two-digit hexadecimal numbers. - - .. automethod:: equals diff --git a/doc/scripts.rst b/doc/scripts.rst index 6b9bdf504..ace8c1e39 100644 --- a/doc/scripts.rst +++ b/doc/scripts.rst @@ -55,6 +55,6 @@ The full usage page can be seen below: can.logconvert --------------- +---------- .. command-output:: python -m can.logconvert -h diff --git a/examples/receive_all.py b/examples/receive_all.py index 7b94d526f..7ff532079 100755 --- a/examples/receive_all.py +++ b/examples/receive_all.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Shows how to receive messages via polling. +Shows how the receive messages via polling. """ import can @@ -11,9 +11,11 @@ def receive_all(): """Receives all messages and prints them to the console until Ctrl+C is pressed.""" - with can.Bus(interface="pcan", channel="PCAN_USBBUS1", bitrate=250000) as bus: - # bus = can.Bus(interface='ixxat', channel=0, bitrate=250000) - # bus = can.Bus(interface='vector', app_name='CANalyzer', channel=0, bitrate=250000) + with can.interface.Bus( + bustype="pcan", channel="PCAN_USBBUS1", bitrate=250000 + ) as bus: + # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) + # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) # set to read-only, only supported on some interfaces bus.state = BusState.PASSIVE diff --git a/examples/send_one.py b/examples/send_one.py index 7e3fb8a4c..49a4f1ee1 100755 --- a/examples/send_one.py +++ b/examples/send_one.py @@ -12,13 +12,13 @@ def send_one(): # this uses the default configuration (for example from the config file) # see https://python-can.readthedocs.io/en/stable/configuration.html - with can.Bus() as bus: + with can.interface.Bus() as bus: # Using specific buses works similar: - # bus = can.Bus(interface='socketcan', channel='vcan0', bitrate=250000) - # bus = can.Bus(interface='pcan', channel='PCAN_USBBUS1', bitrate=250000) - # bus = can.Bus(interface='ixxat', channel=0, bitrate=250000) - # bus = can.Bus(interface='vector', app_name='CANalyzer', channel=0, bitrate=250000) + # bus = can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000) + # bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) + # bus = can.interface.Bus(bustype='ixxat', channel=0, bitrate=250000) + # bus = can.interface.Bus(bustype='vector', app_name='CANalyzer', channel=0, bitrate=250000) # ... msg = can.Message( diff --git a/examples/serial_com.py b/examples/serial_com.py index 76b95c3e7..c57207a77 100755 --- a/examples/serial_com.py +++ b/examples/serial_com.py @@ -48,8 +48,8 @@ def receive(bus, stop_event): def main(): """Controls the sender and receiver.""" - with can.Bus(interface="serial", channel="/dev/ttyS10") as server: - with can.Bus(interface="serial", channel="/dev/ttyS11") as client: + with can.interface.Bus(interface="serial", channel="/dev/ttyS10") as server: + with can.interface.Bus(interface="serial", channel="/dev/ttyS11") as client: tx_msg = can.Message( arbitration_id=0x01, diff --git a/examples/vcan_filtered.py b/examples/vcan_filtered.py index a43fbe821..fa6c71547 100755 --- a/examples/vcan_filtered.py +++ b/examples/vcan_filtered.py @@ -11,7 +11,7 @@ def main(): """Send some messages to itself and apply filtering.""" - with can.Bus(interface="virtual", receive_own_messages=True) as bus: + with can.Bus(bustype="virtual", receive_own_messages=True) as bus: can_filters = [{"can_id": 1, "can_mask": 0xF, "extended": True}] bus.set_filters(can_filters) diff --git a/examples/virtual_can_demo.py b/examples/virtual_can_demo.py index af50a87a7..d0d6a4a6a 100755 --- a/examples/virtual_can_demo.py +++ b/examples/virtual_can_demo.py @@ -14,9 +14,9 @@ def producer(thread_id: int, message_count: int = 16) -> None: """Spam the bus with messages including the data id. :param thread_id: the id of the thread/process - :param message_count: the number of messages that shall be sent + :param message_count: the number of messages that shall be send """ - with can.Bus(interface="socketcan", channel="vcan0") as bus: # type: ignore + with can.Bus(bustype="socketcan", channel="vcan0") as bus: # type: ignore for i in range(message_count): msg = can.Message( arbitration_id=0x0CF02200 + thread_id, diff --git a/setup.cfg b/setup.cfg index 2f3ee032f..b402ee645 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -license_files = LICENSE.txt +license_file = LICENSE.txt [mypy] warn_return_any = True @@ -12,7 +12,6 @@ warn_unused_ignores = True exclude = (?x)( venv - |^doc/conf.py$ |^test |^setup.py$ |^can/interfaces/__init__.py diff --git a/setup.py b/setup.py index a32471844..c9defecab 100644 --- a/setup.py +++ b/setup.py @@ -33,9 +33,6 @@ "gs_usb": ["gs_usb>=0.2.1"], "nixnet": ["nixnet>=0.3.1"], "pcan": ["uptime~=3.0.1"], - "viewer": [ - 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"' - ], } setup( @@ -44,7 +41,6 @@ url="https://github.com/hardbyte/python-can", description="Controller Area Network interface module for Python", long_description=long_description, - long_description_content_type="text/x-rst", classifiers=[ # a list of all available ones: https://pypi.org/classifiers/ "Programming Language :: Python", @@ -52,7 +48,6 @@ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", "Natural Language :: English", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", @@ -91,6 +86,7 @@ install_requires=[ "setuptools", "wrapt~=1.10", + 'windows-curses;platform_system=="Windows" and platform_python_implementation=="CPython"', "typing_extensions>=3.10.0.0", 'pywin32;platform_system=="Windows" and platform_python_implementation=="CPython"', 'msgpack~=1.0.0;platform_system!="Windows"', diff --git a/test/listener_test.py b/test/listener_test.py index 0e64a266a..e5abd94a2 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -15,6 +15,9 @@ from .data.example_data import generate_message +channel = "virtual_channel_0" +can.rc["interface"] = "virtual" + logging.basicConfig(level=logging.DEBUG) # makes the random number generator deterministic @@ -52,15 +55,10 @@ def testClassesImportable(self): class BusTest(unittest.TestCase): def setUp(self): - # Save all can.rc defaults - self._can_rc = can.rc - can.rc = {"interface": "virtual"} self.bus = can.interface.Bus() def tearDown(self): self.bus.shutdown() - # Restore the defaults - can.rc = self._can_rc class ListenerTest(BusTest): diff --git a/test/network_test.py b/test/network_test.py index 58c305a38..5900cd10f 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -14,8 +14,10 @@ import can channel = "vcan0" +can.rc["interface"] = "virtual" +@unittest.skipIf("interface" not in can.rc, "Need a CAN interface") class ControllerAreaNetworkTestCase(unittest.TestCase): """ This test ensures that what messages go in to the bus is what comes out. @@ -40,15 +42,6 @@ class ControllerAreaNetworkTestCase(unittest.TestCase): for b in range(num_messages) ) - def setUp(self): - # Save all can.rc defaults - self._can_rc = can.rc - can.rc = {"interface": "virtual"} - - def tearDown(self): - # Restore the defaults - can.rc = self._can_rc - def producer(self, ready_event, msg_read): self.client_bus = can.interface.Bus(channel=channel) ready_event.wait() diff --git a/test/open_vcan.sh b/test/open_vcan.sh index b6f4676b7..bd02ad752 100755 --- a/test/open_vcan.sh +++ b/test/open_vcan.sh @@ -5,7 +5,3 @@ modprobe vcan ip link add dev vcan0 type vcan ip link set up vcan0 mtu 72 -ip link add dev vxcan0 type vcan -ip link set up vxcan0 mtu 72 -ip link add dev slcan0 type vcan -ip link set up slcan0 mtu 72 diff --git a/test/test_load_config.py b/test/test_load_config.py deleted file mode 100644 index a2969b0a5..000000000 --- a/test/test_load_config.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python - -import os -import shutil -import tempfile -import unittest -from tempfile import NamedTemporaryFile - -import can - - -class LoadConfigTest(unittest.TestCase): - configuration = { - "default": {"interface": "serial", "channel": "0"}, - "one": {"interface": "kvaser", "channel": "1", "bitrate": 100000}, - "two": {"channel": "2"}, - } - - def setUp(self): - # Create a temporary directory - self.test_dir = tempfile.mkdtemp() - - def tearDown(self): - # Remove the directory after the test - shutil.rmtree(self.test_dir) - - def _gen_configration_file(self, sections): - with NamedTemporaryFile( - mode="w", dir=self.test_dir, delete=False - ) as tmp_config_file: - content = [] - for section in sections: - content.append("[{}]".format(section)) - for k, v in self.configuration[section].items(): - content.append("{} = {}".format(k, v)) - tmp_config_file.write("\n".join(content)) - return tmp_config_file.name - - def _dict_to_env(self, d): - return {f"CAN_{k.upper()}": str(v) for k, v in d.items()} - - def test_config_default(self): - tmp_config = self._gen_configration_file(["default"]) - config = can.util.load_config(path=tmp_config) - self.assertEqual(config, self.configuration["default"]) - - def test_config_whole_default(self): - tmp_config = self._gen_configration_file(self.configuration) - config = can.util.load_config(path=tmp_config) - self.assertEqual(config, self.configuration["default"]) - - def test_config_whole_context(self): - tmp_config = self._gen_configration_file(self.configuration) - config = can.util.load_config(path=tmp_config, context="one") - self.assertEqual(config, self.configuration["one"]) - - def test_config_merge_context(self): - tmp_config = self._gen_configration_file(self.configuration) - config = can.util.load_config(path=tmp_config, context="two") - expected = self.configuration["default"] - expected.update(self.configuration["two"]) - self.assertEqual(config, expected) - - def test_config_merge_environment_to_context(self): - tmp_config = self._gen_configration_file(self.configuration) - env_data = {"interface": "serial", "bitrate": 125000} - env_dict = self._dict_to_env(env_data) - with unittest.mock.patch.dict("os.environ", env_dict): - config = can.util.load_config(path=tmp_config, context="one") - expected = self.configuration["one"] - expected.update(env_data) - self.assertEqual(config, expected) - - def test_config_whole_environment(self): - tmp_config = self._gen_configration_file(self.configuration) - env_data = {"interface": "socketcan", "channel": "3", "bitrate": 250000} - env_dict = self._dict_to_env(env_data) - with unittest.mock.patch.dict("os.environ", env_dict): - config = can.util.load_config(path=tmp_config, context="one") - expected = self.configuration["one"] - expected.update(env_data) - self.assertEqual(config, expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/test_load_file_config.py b/test/test_load_file_config.py index 6b1d5a382..c71e6ccd6 100644 --- a/test/test_load_file_config.py +++ b/test/test_load_file_config.py @@ -11,7 +11,7 @@ class LoadFileConfigTest(unittest.TestCase): configuration = { "default": {"interface": "virtual", "channel": "0"}, - "one": {"interface": "kvaser", "channel": "1"}, + "one": {"interface": "virtual", "channel": "1"}, "two": {"channel": "2"}, "three": {"extra": "extra value"}, } diff --git a/test/test_rotating_loggers.py b/test/test_rotating_loggers.py index ad4388bf7..d900f4f23 100644 --- a/test/test_rotating_loggers.py +++ b/test/test_rotating_loggers.py @@ -18,8 +18,6 @@ def _get_instance(path, *args, **kwargs) -> can.io.BaseRotatingLogger: class SubClass(can.io.BaseRotatingLogger): """Subclass that implements abstract methods for testing.""" - _supported_formats = {".asc", ".blf", ".csv", ".log", ".txt"} - def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._writer = can.Printer(file=path / "__unused.txt") diff --git a/test/test_socketcan.py b/test/test_socketcan.py index 1c38e1583..a2c4faed3 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -3,10 +3,13 @@ """ Test functions in `can.interfaces.socketcan.socketcan`. """ -import ctypes -import struct import unittest + +from unittest.mock import Mock from unittest.mock import patch +from unittest.mock import call + +import ctypes from can.interfaces.socketcan.socketcan import ( bcm_header_factory, @@ -237,25 +240,51 @@ def side_effect_ctypes_alignment(value): ] self.assertEqual(expected_fields, BcmMsgHead._fields_) - def test_build_bcm_header(self): - def _find_u32_fmt_char() -> str: - for _fmt in ("H", "I", "L", "Q"): - if struct.calcsize(_fmt) == 4: - return _fmt + @unittest.skipIf( + not ( + ctypes.sizeof(ctypes.c_long) == 4 and ctypes.alignment(ctypes.c_long) == 4 + ), + "Should only run on platforms where sizeof(long) == 4 and alignof(long) == 4", + ) + def test_build_bcm_header_sizeof_long_4_alignof_long_4(self): + expected_result = ( + b"\x02\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x01\x04\x00\x00" + b"\x01\x00\x00\x00\x00\x00\x00\x00" + ) - def _standard_size_little_endian_to_native(data: bytes) -> bytes: - std_le_fmt = " None: - # basic mock for XLDriver - xldriver_mock = Mock() - - # bus creation functions - xldriver_mock.xlOpenDriver = Mock() - xldriver_mock.xlGetApplConfig = Mock(side_effect=xlGetApplConfig) - xldriver_mock.xlGetChannelIndex = Mock(side_effect=xlGetChannelIndex) - xldriver_mock.xlOpenPort = Mock(side_effect=xlOpenPort) - xldriver_mock.xlCanFdSetConfiguration = Mock(return_value=0) - xldriver_mock.xlCanSetChannelMode = Mock(return_value=0) - xldriver_mock.xlActivateChannel = Mock(return_value=0) - xldriver_mock.xlGetSyncTime = Mock(side_effect=xlGetSyncTime) - xldriver_mock.xlCanSetChannelAcceptance = Mock(return_value=0) - xldriver_mock.xlCanSetChannelBitrate = Mock(return_value=0) - xldriver_mock.xlSetNotification = Mock(side_effect=xlSetNotification) - - # bus deactivation functions - xldriver_mock.xlDeactivateChannel = Mock(return_value=0) - xldriver_mock.xlClosePort = Mock(return_value=0) - xldriver_mock.xlCloseDriver = Mock() - - # sender functions - xldriver_mock.xlCanTransmit = Mock(return_value=0) - xldriver_mock.xlCanTransmitEx = Mock(return_value=0) - - # various functions - xldriver_mock.xlCanFlushTransmitQueue = Mock() - - # backup unmodified values - real_xldriver = canlib.xldriver - real_waitforsingleobject = canlib.WaitForSingleObject - real_has_events = canlib.HAS_EVENTS - - # set mock - canlib.xldriver = xldriver_mock - canlib.HAS_EVENTS = False - - yield - - # cleanup - canlib.xldriver = real_xldriver - canlib.WaitForSingleObject = real_waitforsingleobject - canlib.HAS_EVENTS = real_has_events - - -def test_bus_creation_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", _testing=True) - assert isinstance(bus, canlib.VectorBus) - can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() - can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() - - can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() - xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] - assert xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value - assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value - - can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called() - can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_bus_creation() -> None: - bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") - assert isinstance(bus, canlib.VectorBus) - bus.shutdown() - - xl_channel_config = _find_xl_channel_config( - serial=_find_virtual_can_serial(), channel=0 - ) - assert bus.channel_masks[0] == xl_channel_config.channelMask - assert ( - xl_channel_config.busParams.data.can.canOpMode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CAN20 - ) - - bus = canlib.VectorBus(channel=0, serial=_find_virtual_can_serial()) - assert isinstance(bus, canlib.VectorBus) - bus.shutdown() - - -def test_bus_creation_bitrate_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", bitrate=200_000, _testing=True) - assert isinstance(bus, canlib.VectorBus) - can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() - can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() - - can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() - xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] - assert xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value - assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value - - can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called() - can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_called() - xlCanSetChannelBitrate_args = ( - can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.call_args[0] - ) - assert xlCanSetChannelBitrate_args[2] == 200_000 - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_bus_creation_bitrate() -> None: - bus = can.Bus( - channel=0, serial=_find_virtual_can_serial(), bustype="vector", bitrate=200_000 - ) - assert isinstance(bus, canlib.VectorBus) - - xl_channel_config = _find_xl_channel_config( - serial=_find_virtual_can_serial(), channel=0 - ) - assert xl_channel_config.busParams.data.can.bitRate == 200_000 - - bus.shutdown() - - -def test_bus_creation_fd_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) - assert isinstance(bus, canlib.VectorBus) - can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() - can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() - - can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() - xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] - assert ( - xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value - ) - assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value - - can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called() - can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_bus_creation_fd() -> None: - bus = can.Bus( - channel=0, serial=_find_virtual_can_serial(), bustype="vector", fd=True - ) - assert isinstance(bus, canlib.VectorBus) - - xl_channel_config = _find_xl_channel_config( - serial=_find_virtual_can_serial(), channel=0 - ) - assert ( - xl_channel_config.interfaceVersion - == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4 - ) - assert ( - xl_channel_config.busParams.data.canFD.canOpMode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD - ) - bus.shutdown() - - -def test_bus_creation_fd_bitrate_timings_mocked(mock_xldriver) -> None: - bus = can.Bus( - channel=0, - bustype="vector", - fd=True, - bitrate=500_000, - data_bitrate=2_000_000, - sjw_abr=10, - tseg1_abr=11, - tseg2_abr=12, - sjw_dbr=13, - tseg1_dbr=14, - tseg2_dbr=15, - _testing=True, - ) - assert isinstance(bus, canlib.VectorBus) - can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() - can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() - - can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() - xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] - assert ( - xlOpenPort_args[5] == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value - ) - - assert xlOpenPort_args[6] == xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value - - can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called() - can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() - - xlCanFdSetConfiguration_args = ( - can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.call_args[0] - ) - canFdConf = xlCanFdSetConfiguration_args[2] - assert canFdConf.arbitrationBitRate == 500000 - assert canFdConf.dataBitRate == 2000000 - assert canFdConf.sjwAbr == 10 - assert canFdConf.tseg1Abr == 11 - assert canFdConf.tseg2Abr == 12 - assert canFdConf.sjwDbr == 13 - assert canFdConf.tseg1Dbr == 14 - assert canFdConf.tseg2Dbr == 15 - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_bus_creation_fd_bitrate_timings() -> None: - bus = can.Bus( - channel=0, - serial=_find_virtual_can_serial(), - bustype="vector", - fd=True, - bitrate=500_000, - data_bitrate=2_000_000, - sjw_abr=10, - tseg1_abr=11, - tseg2_abr=12, - sjw_dbr=13, - tseg1_dbr=14, - tseg2_dbr=15, - ) - - xl_channel_config = _find_xl_channel_config( - serial=_find_virtual_can_serial(), channel=0 - ) - assert ( - xl_channel_config.interfaceVersion - == xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4 - ) - assert ( - xl_channel_config.busParams.data.canFD.canOpMode - & xldefine.XL_CANFD_BusParams_CanOpMode.XL_BUS_PARAMS_CANOPMODE_CANFD - ) - assert xl_channel_config.busParams.data.canFD.arbitrationBitRate == 500_000 - assert xl_channel_config.busParams.data.canFD.sjwAbr == 10 - assert xl_channel_config.busParams.data.canFD.tseg1Abr == 11 - assert xl_channel_config.busParams.data.canFD.tseg2Abr == 12 - assert xl_channel_config.busParams.data.canFD.sjwDbr == 13 - assert xl_channel_config.busParams.data.canFD.tseg1Dbr == 14 - assert xl_channel_config.busParams.data.canFD.tseg2Dbr == 15 - assert xl_channel_config.busParams.data.canFD.dataBitRate == 2_000_000 - - bus.shutdown() - - -def test_send_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", _testing=True) - msg = can.Message( - arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True - ) - bus.send(msg) - can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_called() - can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_not_called() - - -def test_send_fd_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) - msg = can.Message( - arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True - ) - bus.send(msg) - can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_not_called() - can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_called() - - -def test_receive_mocked(mock_xldriver) -> None: - can.interfaces.vector.canlib.xldriver.xlReceive = Mock(side_effect=xlReceive) - bus = can.Bus(channel=0, bustype="vector", _testing=True) - bus.recv(timeout=0.05) - can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() - can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() - - -def test_receive_fd_mocked(mock_xldriver) -> None: - can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock(side_effect=xlCanReceive) - bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) - bus.recv(timeout=0.05) - can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() - can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_send_and_receive() -> None: - bus1 = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") - bus2 = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") - - msg_std = can.Message( - channel=0, arbitration_id=0xFF, data=list(range(8)), is_extended_id=False - ) - msg_ext = can.Message( - channel=0, arbitration_id=0xFFFFFF, data=list(range(8)), is_extended_id=True - ) - - bus1.send(msg_std) - msg_std_recv = bus2.recv(None) - assert msg_std.equals(msg_std_recv, timestamp_delta=None) - - bus1.send(msg_ext) - msg_ext_recv = bus2.recv(None) - assert msg_ext.equals(msg_ext_recv, timestamp_delta=None) - - bus1.shutdown() - bus2.shutdown() - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_send_and_receive_fd() -> None: - bus1 = can.Bus( - channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector" - ) - bus2 = can.Bus( - channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector" - ) - - msg_std = can.Message( - channel=0, - arbitration_id=0xFF, - data=list(range(64)), - is_extended_id=False, - is_fd=True, - ) - msg_ext = can.Message( - channel=0, - arbitration_id=0xFFFFFF, - data=list(range(64)), - is_extended_id=True, - is_fd=True, - ) - - bus1.send(msg_std) - msg_std_recv = bus2.recv(None) - assert msg_std.equals(msg_std_recv, timestamp_delta=None) - - bus1.send(msg_ext) - msg_ext_recv = bus2.recv(None) - assert msg_ext.equals(msg_ext_recv, timestamp_delta=None) - - bus1.shutdown() - bus2.shutdown() - - -def test_receive_non_msg_event_mocked(mock_xldriver) -> None: - can.interfaces.vector.canlib.xldriver.xlReceive = Mock( - side_effect=xlReceive_chipstate - ) - bus = can.Bus(channel=0, bustype="vector", _testing=True) - bus.handle_can_event = Mock() - bus.recv(timeout=0.05) - can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() - can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() - bus.handle_can_event.assert_called() - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_receive_non_msg_event() -> None: - bus = canlib.VectorBus( - channel=0, serial=_find_virtual_can_serial(), bustype="vector" - ) - bus.handle_can_event = Mock() - bus.xldriver.xlCanRequestChipState(bus.port_handle, bus.channel_masks[0]) - bus.recv(timeout=0.5) - bus.handle_can_event.assert_called() - bus.shutdown() - - -def test_receive_fd_non_msg_event_mocked(mock_xldriver) -> None: - can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock( - side_effect=xlCanReceive_chipstate - ) - bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) - bus.handle_canfd_event = Mock() - bus.recv(timeout=0.05) - can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() - can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() - bus.handle_canfd_event.assert_called() - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_receive_fd_non_msg_event() -> None: - bus = canlib.VectorBus( - channel=0, serial=_find_virtual_can_serial(), fd=True, bustype="vector" - ) - bus.handle_canfd_event = Mock() - bus.xldriver.xlCanRequestChipState(bus.port_handle, bus.channel_masks[0]) - bus.recv(timeout=0.5) - bus.handle_canfd_event.assert_called() - bus.shutdown() - - -def test_flush_tx_buffer_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", _testing=True) - bus.flush_tx_buffer() - can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue.assert_called() - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_flush_tx_buffer() -> None: - bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") - bus.flush_tx_buffer() - bus.shutdown() - - -def test_shutdown_mocked(mock_xldriver) -> None: - bus = can.Bus(channel=0, bustype="vector", _testing=True) - bus.shutdown() - can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() - can.interfaces.vector.canlib.xldriver.xlClosePort.assert_called() - can.interfaces.vector.canlib.xldriver.xlCloseDriver.assert_called() - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_shutdown() -> None: - bus = can.Bus(channel=0, serial=_find_virtual_can_serial(), bustype="vector") - - xl_channel_config = _find_xl_channel_config( - serial=_find_virtual_can_serial(), channel=0 - ) - assert xl_channel_config.isOnBus != 0 - bus.shutdown() - - xl_channel_config = _find_xl_channel_config( - serial=_find_virtual_can_serial(), channel=0 - ) - assert xl_channel_config.isOnBus == 0 - - -def test_reset_mocked(mock_xldriver) -> None: - bus = canlib.VectorBus(channel=0, bustype="vector", _testing=True) - bus.reset() - can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() - can.interfaces.vector.canlib.xldriver.xlActivateChannel.assert_called() - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_reset_mocked() -> None: - bus = canlib.VectorBus( - channel=0, serial=_find_virtual_can_serial(), bustype="vector" - ) - bus.reset() - bus.shutdown() - - -def test_popup_hw_cfg_mocked(mock_xldriver) -> None: - canlib.xldriver.xlPopupHwConfig = Mock() - canlib.VectorBus.popup_vector_hw_configuration(10) - assert canlib.xldriver.xlPopupHwConfig.called - args, kwargs = canlib.xldriver.xlPopupHwConfig.call_args - assert isinstance(args[0], ctypes.c_char_p) - assert isinstance(args[1], ctypes.c_uint) - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_popup_hw_cfg() -> None: - with pytest.raises(VectorOperationError): - canlib.VectorBus.popup_vector_hw_configuration(1) - - -def test_get_application_config_mocked(mock_xldriver) -> None: - canlib.xldriver.xlGetApplConfig = Mock() - canlib.VectorBus.get_application_config(app_name="CANalyzer", app_channel=0) - assert canlib.xldriver.xlGetApplConfig.called - - -def test_set_application_config_mocked(mock_xldriver) -> None: - canlib.xldriver.xlSetApplConfig = Mock() - canlib.VectorBus.set_application_config( - app_name="CANalyzer", - app_channel=0, - hw_type=xldefine.XL_HardwareType.XL_HWTYPE_VN1610, - hw_index=0, - hw_channel=0, - ) - assert canlib.xldriver.xlSetApplConfig.called - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_set_and_get_application_config() -> None: - xl_channel_config = _find_xl_channel_config( - serial=_find_virtual_can_serial(), channel=1 - ) - canlib.VectorBus.set_application_config( - app_name="python-can::test_vector", - app_channel=5, - hw_channel=xl_channel_config.hwChannel, - hw_index=xl_channel_config.hwIndex, - hw_type=xldefine.XL_HardwareType(xl_channel_config.hwType), - ) - hw_type, hw_index, hw_channel = canlib.VectorBus.get_application_config( - app_name="python-can::test_vector", - app_channel=5, - ) - assert hw_type == xldefine.XL_HardwareType(xl_channel_config.hwType) - assert hw_index == xl_channel_config.hwIndex - assert hw_channel == xl_channel_config.hwChannel - - -def test_set_timer_mocked(mock_xldriver) -> None: - canlib.xldriver.xlSetTimerRate = Mock() - bus = canlib.VectorBus(channel=0, bustype="vector", fd=True, _testing=True) - bus.set_timer_rate(timer_rate_ms=1) - assert canlib.xldriver.xlSetTimerRate.called - - -@pytest.mark.skipif(not XLDRIVER_FOUND, reason="Vector XL API is unavailable") -def test_set_timer() -> None: - bus = canlib.VectorBus( - channel=0, serial=_find_virtual_can_serial(), bustype="vector" - ) - bus.handle_can_event = Mock() - bus.set_timer_rate(timer_rate_ms=1) - t0 = time.perf_counter() - while time.perf_counter() - t0 < 0.5: - bus.recv(timeout=-1) - - # call_count is incorrect when using virtual bus - # assert bus.handle_can_event.call_count > 498 - # assert bus.handle_can_event.call_count < 502 - - -@pytest.mark.skipif(IS_WINDOWS, reason="Not relevant for Windows.") -def test_called_without_testing_argument() -> None: - """This tests if an exception is thrown when we are not running on Windows.""" - with pytest.raises(can.CanInterfaceNotImplementedError): - # do not set the _testing argument, since it would suppress the exception - can.Bus(channel=0, bustype="vector") - - -def test_vector_error_pickle() -> None: - for error_type in [ - VectorError, - VectorInitializationError, - VectorOperationError, - ]: - error_code = 118 - error_string = "XL_ERROR" - function = "function_name" - - exc = error_type(error_code, error_string, function) - - # pickle and unpickle - p = pickle.dumps(exc) - exc_unpickled: VectorError = pickle.loads(p) - - assert str(exc) == str(exc_unpickled) - assert error_code == exc_unpickled.error_code - - with pytest.raises(error_type): - raise exc_unpickled - - -def test_vector_subtype_error_from_generic() -> None: - for error_type in [VectorInitializationError, VectorOperationError]: - error_code = 118 - error_string = "XL_ERROR" - function = "function_name" - - generic = VectorError(error_code, error_string, function) - - # pickle and unpickle - specific: VectorError = error_type.from_generic(generic) - - assert str(generic) == str(specific) - assert error_code == specific.error_code - - with pytest.raises(error_type): - raise specific - - -@pytest.mark.skipif( - sys.byteorder != "little", reason="Test relies on little endian data." -) -def test_get_channel_configs() -> None: - _original_func = canlib._get_xl_driver_config - canlib._get_xl_driver_config = _get_predefined_xl_driver_config - - channel_configs = canlib.get_channel_configs() - assert len(channel_configs) == 12 - - canlib._get_xl_driver_config = _original_func - - -@pytest.mark.skipif(not IS_WINDOWS, reason="Windows specific test") -def test_winapi_availability() -> None: - assert canlib.WaitForSingleObject is not None - assert canlib.INFINITE is not None - - -def test_vector_channel_config_attributes(): - assert hasattr(VectorChannelConfig, "name") - assert hasattr(VectorChannelConfig, "hw_type") - assert hasattr(VectorChannelConfig, "hw_index") - assert hasattr(VectorChannelConfig, "hw_channel") - assert hasattr(VectorChannelConfig, "channel_index") - assert hasattr(VectorChannelConfig, "channel_mask") - assert hasattr(VectorChannelConfig, "channel_capabilities") - assert hasattr(VectorChannelConfig, "channel_bus_capabilities") - assert hasattr(VectorChannelConfig, "is_on_bus") - assert hasattr(VectorChannelConfig, "bus_params") - assert hasattr(VectorChannelConfig, "connected_bus_type") - assert hasattr(VectorChannelConfig, "serial_number") - assert hasattr(VectorChannelConfig, "article_number") - assert hasattr(VectorChannelConfig, "transceiver_name") - - -def test_vector_bus_params_attributes(): - assert hasattr(VectorBusParams, "bus_type") - assert hasattr(VectorBusParams, "can") - assert hasattr(VectorBusParams, "canfd") - - -def test_vector_can_params_attributes(): - assert hasattr(VectorCanParams, "bitrate") - assert hasattr(VectorCanParams, "sjw") - assert hasattr(VectorCanParams, "tseg1") - assert hasattr(VectorCanParams, "tseg2") - assert hasattr(VectorCanParams, "sam") - assert hasattr(VectorCanParams, "output_mode") - assert hasattr(VectorCanParams, "can_op_mode") - - -def test_vector_canfd_params_attributes(): - assert hasattr(VectorCanFdParams, "bitrate") - assert hasattr(VectorCanFdParams, "data_bitrate") - assert hasattr(VectorCanFdParams, "sjw_abr") - assert hasattr(VectorCanFdParams, "tseg1_abr") - assert hasattr(VectorCanFdParams, "tseg2_abr") - assert hasattr(VectorCanFdParams, "sam_abr") - assert hasattr(VectorCanFdParams, "sjw_dbr") - assert hasattr(VectorCanFdParams, "tseg1_dbr") - assert hasattr(VectorCanFdParams, "tseg2_dbr") - assert hasattr(VectorCanFdParams, "output_mode") - assert hasattr(VectorCanFdParams, "can_op_mode") - - -# ***************************************************************************** -# Utility functions -# ***************************************************************************** - - -def _find_xl_channel_config(serial: int, channel: int) -> xlclass.XLchannelConfig: - """Helper function""" - xl_driver_config = xlclass.XLdriverConfig() - canlib.xldriver.xlOpenDriver() - canlib.xldriver.xlGetDriverConfig(xl_driver_config) - canlib.xldriver.xlCloseDriver() - - for i in range(xl_driver_config.channelCount): - xl_channel_config: xlclass.XLchannelConfig = xl_driver_config.channel[i] - - if xl_channel_config.serialNumber != serial: - continue - - if xl_channel_config.hwChannel != channel: - continue - - return xl_channel_config - - raise LookupError("XLchannelConfig not found.") - - -@functools.lru_cache() -def _find_virtual_can_serial() -> int: - """Serial number might be 0 or 100 depending on driver version.""" - xl_driver_config = xlclass.XLdriverConfig() - canlib.xldriver.xlOpenDriver() - canlib.xldriver.xlGetDriverConfig(xl_driver_config) - canlib.xldriver.xlCloseDriver() - - for i in range(xl_driver_config.channelCount): - xl_channel_config: xlclass.XLchannelConfig = xl_driver_config.channel[i] - - if xl_channel_config.transceiverName.decode() == "Virtual CAN": - return xl_channel_config.serialNumber - - raise LookupError("Vector virtual CAN not found") - - -XL_DRIVER_CONFIG_EXAMPLE = ( - b"\x0E\x00\x1E\x14\x0C\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E" - b"\x65\x6C\x20\x53\x74\x72\x65\x61\x6D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x2D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04" - b"\x0A\x40\x00\x02\x00\x02\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00\x8E" - b"\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08" - b"\x1C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31" - b"\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x01\x03\x02\x00\x00\x00\x00\x01\x02\x00\x00" - b"\x00\x00\x00\x00\x00\x02\x10\x00\x08\x07\x01\x04\x00\x00\x00\x00\x00\x00\x04\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x04\x00" - b"\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x46\x52\x70\x69\x67\x67\x79\x20\x31\x30" - b"\x38\x30\x41\x6D\x61\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x05\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x32\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x02\x3C\x01\x00" - b"\x00\x00\x00\x02\x04\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\xA2\x03\x05\x01\x00" - b"\x00\x00\x04\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01\x01\x00\x00" - b"\x00\x00\x00\x00\x00\x01\x80\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00" - b"\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x4F\x6E\x20" - b"\x62\x6F\x61\x72\x64\x20\x43\x41\x4E\x20\x31\x30\x35\x31\x63\x61\x70\x28\x48\x69" - b"\x67\x68\x73\x70\x65\x65\x64\x29\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03" - b"\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E" - b"\x6E\x65\x6C\x20\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x2D\x00\x03\x3C\x01\x00\x00\x00\x00\x03\x08\x00\x00\x00\x00\x00\x00\x00\x12" - b"\x00\x00\xA2\x03\x09\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00" - b"\x01\x04\x03\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x9B\x00\x00\x00\x68\x89\x09" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00" - b"\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00" - b"\x08\x1C\x00\x00\x4F\x6E\x20\x62\x6F\x61\x72\x64\x20\x43\x41\x4E\x20\x31\x30\x35" - b"\x31\x63\x61\x70\x28\x48\x69\x67\x68\x73\x70\x65\x65\x64\x29\x00\x04\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39" - b"\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x34\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x04\x33\x01\x00\x00\x00\x00\x04\x10\x00" - b"\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x09\x02\x08\x00\x00\x00\x00\x00\x02" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x03" - b"\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x4C\x49\x4E\x70\x69\x67\x67\x79\x20" - b"\x37\x32\x36\x39\x6D\x61\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x07\x00\x00\x00\x70\x17\x00\x00\x0C\x09\x03\x04\x58\x02\x10\x0E\x30" - b"\x57\x05\x00\x00\x00\x00\x00\x88\x13\x88\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x35\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x05\x00\x00" - b"\x00\x00\x02\x00\x05\x20\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x0C\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00" - b"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61" - b"\x6E\x6E\x65\x6C\x20\x36\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x2D\x00\x06\x00\x00\x00\x00\x02\x00\x06\x40\x00\x00\x00\x00\x00\x00\x00" - b"\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00" - b"\x00\x08\x1C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38" - b"\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x37\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x07\x00\x00\x00\x00\x02\x00\x07\x80" - b"\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x38" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2D\x00\x08\x3C" - b"\x01\x00\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00\x12\x00\x00\xA2\x01\x00" - b"\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x0C\x00\x02\x0A\x04\x00\x00\x00\x00\x00\x00\x00\x8E\x00\x02\x0A\x00" - b"\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03\x00\x00\x08\x1C\x00\x00\x4F" - b"\x6E\x20\x62\x6F\x61\x72\x64\x20\x43\x41\x4E\x20\x31\x30\x35\x31\x63\x61\x70\x28" - b"\x48\x69\x67\x68\x73\x70\x65\x65\x64\x29\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x4E\x38\x39\x31\x34\x20\x43\x68" - b"\x61\x6E\x6E\x65\x6C\x20\x39\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x2D\x00\x09\x80\x02\x00\x00\x00\x00\x09\x00\x02\x00\x00\x00\x00\x00" - b"\x00\x02\x00\x00\x00\x40\x00\x40\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x02\x0A\x03\x00\x00\x00\x00\x00" - b"\x00\x00\x8E\x00\x02\x0A\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE9\x03" - b"\x00\x00\x08\x1C\x00\x00\x44\x2F\x41\x20\x49\x4F\x70\x69\x67\x67\x79\x20\x38\x36" - b"\x34\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x69" - b"\x72\x74\x75\x61\x6C\x20\x43\x68\x61\x6E\x6E\x65\x6C\x20\x31\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x16\x00\x00\x00\x00\x00\x0A" - b"\x00\x04\x00\x00\x00\x00\x00\x00\x07\x00\x00\xA0\x01\x00\x01\x00\x00\x00\x00\x00" - b"\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01\x01\x00\x00\x00\x00\x00\x00" - b"\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x1E" - b"\x14\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x69\x72\x74\x75\x61\x6C" - b"\x20\x43\x41\x4E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x56\x69\x72\x74\x75\x61\x6C\x20\x43\x68\x61\x6E\x6E\x65\x6C" - b"\x20\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01" - b"\x16\x00\x00\x00\x00\x00\x0B\x00\x08\x00\x00\x00\x00\x00\x00\x07\x00\x00\xA0\x01" - b"\x00\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20\xA1\x07\x00\x01\x04\x03\x01" - b"\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x68\x89\x09\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x10\x00\x1E\x14\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x56\x69\x72\x74\x75\x61\x6C\x20\x43\x41\x4E\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x02" + 11832 * b"\x00" -) - - -def _get_predefined_xl_driver_config() -> xlclass.XLdriverConfig: - return xlclass.XLdriverConfig.from_buffer_copy(XL_DRIVER_CONFIG_EXAMPLE) - -# ***************************************************************************** -# Mock functions/side effects -# ***************************************************************************** +class TestVectorBus(unittest.TestCase): + def setUp(self) -> None: + # basic mock for XLDriver + can.interfaces.vector.canlib.xldriver = Mock() + + # bus creation functions + can.interfaces.vector.canlib.xldriver.xlOpenDriver = Mock() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig = Mock( + side_effect=xlGetApplConfig + ) + can.interfaces.vector.canlib.xldriver.xlGetChannelIndex = Mock( + side_effect=xlGetChannelIndex + ) + can.interfaces.vector.canlib.xldriver.xlOpenPort = Mock(side_effect=xlOpenPort) + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration = Mock( + return_value=0 + ) + can.interfaces.vector.canlib.xldriver.xlCanSetChannelMode = Mock(return_value=0) + can.interfaces.vector.canlib.xldriver.xlActivateChannel = Mock(return_value=0) + can.interfaces.vector.canlib.xldriver.xlGetSyncTime = Mock( + side_effect=xlGetSyncTime + ) + can.interfaces.vector.canlib.xldriver.xlCanSetChannelAcceptance = Mock( + return_value=0 + ) + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate = Mock( + return_value=0 + ) + can.interfaces.vector.canlib.xldriver.xlSetNotification = Mock( + side_effect=xlSetNotification + ) + + # bus deactivation functions + can.interfaces.vector.canlib.xldriver.xlDeactivateChannel = Mock(return_value=0) + can.interfaces.vector.canlib.xldriver.xlClosePort = Mock(return_value=0) + can.interfaces.vector.canlib.xldriver.xlCloseDriver = Mock() + + # sender functions + can.interfaces.vector.canlib.xldriver.xlCanTransmit = Mock(return_value=0) + can.interfaces.vector.canlib.xldriver.xlCanTransmitEx = Mock(return_value=0) + + # various functions + can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue = Mock() + can.interfaces.vector.canlib.WaitForSingleObject = Mock() + + self.bus = None + + def tearDown(self) -> None: + if self.bus: + self.bus.shutdown() + self.bus = None + + def test_bus_creation(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.assertIsInstance(self.bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + self.assertEqual( + xlOpenPort_args[5], xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value + ) + self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value) + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() + + def test_bus_creation_bitrate(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", bitrate=200000, _testing=True) + self.assertIsInstance(self.bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + self.assertEqual( + xlOpenPort_args[5], xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value + ) + self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value) + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_called() + xlCanSetChannelBitrate_args = ( + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.call_args[0] + ) + self.assertEqual(xlCanSetChannelBitrate_args[2], 200000) + + def test_bus_creation_fd(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + self.assertIsInstance(self.bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + self.assertEqual( + xlOpenPort_args[5], + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value, + ) + self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value) + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() + + def test_bus_creation_fd_bitrate_timings(self) -> None: + self.bus = can.Bus( + channel=0, + bustype="vector", + fd=True, + bitrate=500000, + data_bitrate=2000000, + sjw_abr=10, + tseg1_abr=11, + tseg2_abr=12, + sjw_dbr=13, + tseg1_dbr=14, + tseg2_dbr=15, + _testing=True, + ) + self.assertIsInstance(self.bus, canlib.VectorBus) + can.interfaces.vector.canlib.xldriver.xlOpenDriver.assert_called() + can.interfaces.vector.canlib.xldriver.xlGetApplConfig.assert_called() + + can.interfaces.vector.canlib.xldriver.xlOpenPort.assert_called() + xlOpenPort_args = can.interfaces.vector.canlib.xldriver.xlOpenPort.call_args[0] + self.assertEqual( + xlOpenPort_args[5], + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value, + ) + self.assertEqual(xlOpenPort_args[6], xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value) + + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanSetChannelBitrate.assert_not_called() + + xlCanFdSetConfiguration_args = ( + can.interfaces.vector.canlib.xldriver.xlCanFdSetConfiguration.call_args[0] + ) + canFdConf = xlCanFdSetConfiguration_args[2] + self.assertEqual(canFdConf.arbitrationBitRate, 500000) + self.assertEqual(canFdConf.dataBitRate, 2000000) + self.assertEqual(canFdConf.sjwAbr, 10) + self.assertEqual(canFdConf.tseg1Abr, 11) + self.assertEqual(canFdConf.tseg2Abr, 12) + self.assertEqual(canFdConf.sjwDbr, 13) + self.assertEqual(canFdConf.tseg1Dbr, 14) + self.assertEqual(canFdConf.tseg2Dbr, 15) + + def test_receive(self) -> None: + can.interfaces.vector.canlib.xldriver.xlReceive = Mock(side_effect=xlReceive) + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() + + def test_receive_fd(self) -> None: + can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock( + side_effect=xlCanReceive + ) + self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + self.bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() + + def test_receive_non_msg_event(self) -> None: + can.interfaces.vector.canlib.xldriver.xlReceive = Mock( + side_effect=xlReceive_chipstate + ) + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.bus.handle_can_event = Mock() + self.bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_not_called() + self.bus.handle_can_event.assert_called() + + def test_receive_fd_non_msg_event(self) -> None: + can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock( + side_effect=xlCanReceive_chipstate + ) + self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + self.bus.handle_canfd_event = Mock() + self.bus.recv(timeout=0.05) + can.interfaces.vector.canlib.xldriver.xlReceive.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanReceive.assert_called() + self.bus.handle_canfd_event.assert_called() + + def test_send(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + msg = can.Message( + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) + self.bus.send(msg) + can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_called() + can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_not_called() + + def test_send_fd(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", fd=True, _testing=True) + msg = can.Message( + arbitration_id=0xC0FFEF, data=[1, 2, 3, 4, 5, 6, 7, 8], is_extended_id=True + ) + self.bus.send(msg) + can.interfaces.vector.canlib.xldriver.xlCanTransmit.assert_not_called() + can.interfaces.vector.canlib.xldriver.xlCanTransmitEx.assert_called() + + def test_flush_tx_buffer(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.bus.flush_tx_buffer() + can.interfaces.vector.canlib.xldriver.xlCanFlushTransmitQueue.assert_called() + + def test_shutdown(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.bus.shutdown() + can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() + can.interfaces.vector.canlib.xldriver.xlClosePort.assert_called() + can.interfaces.vector.canlib.xldriver.xlCloseDriver.assert_called() + + def test_reset(self) -> None: + self.bus = can.Bus(channel=0, bustype="vector", _testing=True) + self.bus.reset() + can.interfaces.vector.canlib.xldriver.xlDeactivateChannel.assert_called() + can.interfaces.vector.canlib.xldriver.xlActivateChannel.assert_called() + + def test_popup_hw_cfg(self) -> None: + canlib.xldriver.xlPopupHwConfig = Mock() + canlib.VectorBus.popup_vector_hw_configuration(10) + assert canlib.xldriver.xlPopupHwConfig.called + args, kwargs = canlib.xldriver.xlPopupHwConfig.call_args + assert isinstance(args[0], ctypes.c_char_p) + assert isinstance(args[1], ctypes.c_uint) + + def test_get_application_config(self) -> None: + canlib.xldriver.xlGetApplConfig = Mock() + canlib.VectorBus.get_application_config(app_name="CANalyzer", app_channel=0) + assert canlib.xldriver.xlGetApplConfig.called + + def test_set_application_config(self) -> None: + canlib.xldriver.xlSetApplConfig = Mock() + canlib.VectorBus.set_application_config( + app_name="CANalyzer", + app_channel=0, + hw_type=xldefine.XL_HardwareType.XL_HWTYPE_VN1610, + hw_index=0, + hw_channel=0, + ) + assert canlib.xldriver.xlSetApplConfig.called + + def test_set_timer_rate(self) -> None: + canlib.xldriver.xlSetTimerRate = Mock() + bus: canlib.VectorBus = can.Bus( + channel=0, bustype="vector", fd=True, _testing=True + ) + bus.set_timer_rate(timer_rate_ms=1) + assert canlib.xldriver.xlSetTimerRate.called + + def test_called_without_testing_argument(self) -> None: + """This tests if an exception is thrown when we are not running on Windows.""" + if os.name != "nt": + with self.assertRaises(can.CanInterfaceNotImplementedError): + # do not set the _testing argument, since it would suppress the exception + can.Bus(channel=0, bustype="vector") + + def test_vector_error_pickle(self) -> None: + for error_type in [ + VectorError, + VectorInitializationError, + VectorOperationError, + ]: + with self.subTest(f"error_type = {error_type.__name__}"): + + error_code = 118 + error_string = "XL_ERROR" + function = "function_name" + + exc = error_type(error_code, error_string, function) + + # pickle and unpickle + p = pickle.dumps(exc) + exc_unpickled: VectorError = pickle.loads(p) + + self.assertEqual(str(exc), str(exc_unpickled)) + self.assertEqual(error_code, exc_unpickled.error_code) + + with pytest.raises(error_type): + raise exc_unpickled + + def test_vector_subtype_error_from_generic(self) -> None: + for error_type in [VectorInitializationError, VectorOperationError]: + with self.subTest(f"error_type = {error_type.__name__}"): + + error_code = 118 + error_string = "XL_ERROR" + function = "function_name" + + generic = VectorError(error_code, error_string, function) + + # pickle and unpickle + specific: VectorError = error_type.from_generic(generic) + + self.assertEqual(str(generic), str(specific)) + self.assertEqual(error_code, specific.error_code) + + with pytest.raises(error_type): + raise specific + + @unittest.skipUnless(IS_WINDOWS, "Windows specific test") + def test_winapi_availability(self) -> None: + self.assertIsNotNone(canlib.WaitForSingleObject) + self.assertIsNotNone(canlib.INFINITE) + + +class TestVectorChannelConfig: + def test_attributes(self): + assert hasattr(VectorChannelConfig, "name") + assert hasattr(VectorChannelConfig, "hwType") + assert hasattr(VectorChannelConfig, "hwIndex") + assert hasattr(VectorChannelConfig, "hwChannel") + assert hasattr(VectorChannelConfig, "channelIndex") + assert hasattr(VectorChannelConfig, "channelMask") + assert hasattr(VectorChannelConfig, "channelCapabilities") + assert hasattr(VectorChannelConfig, "channelBusCapabilities") + assert hasattr(VectorChannelConfig, "isOnBus") + assert hasattr(VectorChannelConfig, "connectedBusType") + assert hasattr(VectorChannelConfig, "serialNumber") + assert hasattr(VectorChannelConfig, "articleNumber") + assert hasattr(VectorChannelConfig, "transceiverName") def xlGetApplConfig( @@ -872,14 +374,13 @@ def xlGetChannelIndex( def xlOpenPort( port_handle_p: ctypes.POINTER(xlclass.XLportHandle), app_name_p: ctypes.c_char_p, - access_mask: int, - permission_mask: xlclass.XLaccess, + access_mask: xlclass.XLaccess, + permission_mask_p: ctypes.POINTER(xlclass.XLaccess), rx_queue_size: ctypes.c_uint, xl_interface_version: ctypes.c_uint, bus_type: ctypes.c_uint, ) -> int: port_handle_p.value = 0 - permission_mask.value = access_mask return 0 @@ -953,3 +454,7 @@ def xlCanReceive_chipstate( event.timeStamp = 0 event.chanIndex = 2 return 0 + + +if __name__ == "__main__": + unittest.main()