diff --git a/.gitignore b/.gitignore index 6b813427e..258ca73ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ test/__tempdir__/ .pytest_cache/ +.mypy_cache/ +.dmypy.json +dmypy.json # ------------------------- # below: https://github.com/github/gitignore/blob/da00310ccba9de9a988cc973ef5238ad2c1460e9/Python.gitignore diff --git a/.travis.yml b/.travis.yml index 2eca95d6d..8ecb4abd5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,6 +84,24 @@ jobs: # warnings to the .pylintrc-wip file to prevent them from being # re-introduced - pylint --rcfile=.pylintrc-wip can/ + # mypy checking + - mypy + --python-version=3.7 + --ignore-missing-imports + --no-implicit-optional + can/bit_timing.py + can/broadcastmanager.py + can/bus.py + can/interface.py + can/listener.py + can/logger.py + can/message.py + can/notifier.py + can/player.py + can/thread_safe_bus.py + can/typechecking.py + can/util.py + can/io/**.py - stage: linter name: "Formatting Checks" python: "3.7" diff --git a/README.rst b/README.rst index ef92a6222..0214f2d4b 100644 --- a/README.rst +++ b/README.rst @@ -1,12 +1,26 @@ python-can ========== -|release| |docs| |build_travis| |build_appveyor| |coverage| |downloads| |formatter| +|release| |downloads| |downloads_monthly| |formatter| + +|docs| |build_travis| |build_appveyor| |coverage| .. |release| image:: https://img.shields.io/pypi/v/python-can.svg :target: https://pypi.python.org/pypi/python-can/ :alt: Latest Version on PyPi +.. |downloads| image:: https://pepy.tech/badge/python-can + :target: https://pepy.tech/project/python-can + :alt: Downloads on PePy + +.. |downloads_monthly| image:: https://pepy.tech/badge/python-can/month + :target: https://pepy.tech/project/python-can/month + :alt: Monthly downloads on PePy + +.. |formatter| image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/python/black + :alt: This project uses the black formatter. + .. |docs| image:: https://readthedocs.org/projects/python-can/badge/?version=stable :target: https://python-can.readthedocs.io/en/stable/ :alt: Documentation @@ -23,14 +37,6 @@ python-can :target: https://codecov.io/gh/hardbyte/python-can/branch/develop :alt: Test coverage reports on Codecov.io -.. |downloads| image:: https://pepy.tech/badge/python-can - :target: https://pepy.tech/project/python-can - :alt: Downloads on PePy - -.. |formatter| image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/python/black - :alt: This project uses the black formatter. - The **C**\ ontroller **A**\ rea **N**\ etwork is a bus standard designed to allow microcontrollers and devices to communicate with each other. It has priority based bus arbitration and reliable deterministic @@ -44,13 +50,13 @@ messages on a can bus. The library currently supports Python 3.6+ as well as PyPy 3 and runs on Mac, Linux and Windows. -============================= =========== -Library Version Python ------------------------------ ----------- - 2.x 2.6+, 3.4+ - 3.x 2.7+, 3.5+ - 4.x *(currently on devlop)* 3.6+ -============================= =========== +============================== =========== +Library Version Python +------------------------------ ----------- + 2.x 2.6+, 3.4+ + 3.x 2.7+, 3.5+ + 4.x *(currently on develop)* 3.6+ +============================== =========== Features diff --git a/can/__init__.py b/can/__init__.py index f134ea8af..3c1ac8d75 100644 --- a/can/__init__.py +++ b/can/__init__.py @@ -6,11 +6,13 @@ import logging +from typing import Dict, Any + __version__ = "3.2.0" log = logging.getLogger("can") -rc = dict() +rc: Dict[str, Any] = dict() class CanError(IOError): diff --git a/can/bit_timing.py b/can/bit_timing.py index 8d9cf5727..b0ad762fb 100644 --- a/can/bit_timing.py +++ b/can/bit_timing.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Optional, Union class BitTiming: @@ -27,15 +27,15 @@ class BitTiming: def __init__( self, - bitrate: int = None, - f_clock: int = None, - brp: int = None, - tseg1: int = None, - tseg2: int = None, - sjw: int = None, + bitrate: Optional[int] = None, + f_clock: Optional[int] = None, + brp: Optional[int] = None, + tseg1: Optional[int] = None, + tseg2: Optional[int] = None, + sjw: Optional[int] = None, nof_samples: int = 1, - btr0: int = None, - btr1: int = None, + btr0: Optional[int] = None, + btr1: Optional[int] = None, ): """ :param int bitrate: diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index edb5da2a3..733693802 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -7,12 +7,27 @@ :meth:`can.BusABC.send_periodic`. """ +from typing import Optional, Sequence, Tuple, Union, TYPE_CHECKING + +from can import typechecking + +if TYPE_CHECKING: + from can.bus import BusABC + +from can.message import Message + import abc import logging import threading import time -import can +# try to import win32event for event-based cyclic send task(needs pywin32 package) +try: + import win32event + + HAS_EVENTS = True +except ImportError: + HAS_EVENTS = False log = logging.getLogger("can.bcm") @@ -36,11 +51,11 @@ class CyclicSendTaskABC(CyclicTask): Message send task with defined period """ - def __init__(self, messages, period): + def __init__(self, messages: Union[Sequence[Message], Message], period: float): """ - :param Union[Sequence[can.Message], can.Message] messages: + :param messages: The messages to be sent periodically. - :param float period: The rate in seconds at which to send the messages. + :param period: The rate in seconds at which to send the messages. """ messages = self._check_and_convert_messages(messages) @@ -50,7 +65,9 @@ def __init__(self, messages, period): self.messages = messages @staticmethod - def _check_and_convert_messages(messages): + def _check_and_convert_messages( + messages: Union[Sequence[Message], Message] + ) -> Tuple[Message, ...]: """Helper function to convert a Message or Sequence of messages into a tuple, and raises an error when the given value is invalid. @@ -60,7 +77,7 @@ def _check_and_convert_messages(messages): Should be called when the cyclic task is initialized """ if not isinstance(messages, (list, tuple)): - if isinstance(messages, can.Message): + if isinstance(messages, Message): messages = [messages] else: raise ValueError("Must be either a list, tuple, or a Message") @@ -84,13 +101,18 @@ def _check_and_convert_messages(messages): class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC): - def __init__(self, messages, period, duration): + def __init__( + self, + messages: Union[Sequence[Message], Message], + period: float, + duration: Optional[float], + ): """Message send task with a defined duration and period. - :param Union[Sequence[can.Message], can.Message] messages: + :param messages: The messages to be sent periodically. - :param float period: The rate in seconds at which to send the messages. - :param float duration: + :param period: The rate in seconds at which to send the messages. + :param duration: Approximate duration in seconds to continue sending messages. If no duration is provided, the task will continue indefinitely. """ @@ -110,7 +132,7 @@ def start(self): class ModifiableCyclicTaskABC(CyclicSendTaskABC): """Adds support for modifying a periodic message""" - def _check_modified_messages(self, messages): + def _check_modified_messages(self, messages: Tuple[Message, ...]): """Helper function to perform error checking when modifying the data in the cyclic task. @@ -130,12 +152,12 @@ def _check_modified_messages(self, messages): "from when the task was created" ) - def modify_data(self, messages): + def modify_data(self, messages: Union[Sequence[Message], Message]): """Update the contents of the periodically sent messages, without altering the timing. - :param Union[Sequence[can.Message], can.Message] messages: - The messages with the new :attr:`can.Message.data`. + :param messages: + The messages with the new :attr:`Message.data`. Note: The arbitration ID cannot be changed. @@ -153,18 +175,26 @@ class MultiRateCyclicSendTaskABC(CyclicSendTaskABC): """A Cyclic send task that supports switches send frequency after a set time. """ - def __init__(self, channel, messages, count, initial_period, subsequent_period): + def __init__( + self, + channel: typechecking.Channel, + messages: Union[Sequence[Message], Message], + count: int, + initial_period: float, + subsequent_period: float, + ): """ Transmits a message `count` times at `initial_period` then continues to transmit messages at `subsequent_period`. :param channel: See interface specific documentation. - :param Union[Sequence[can.Message], can.Message] messages: - :param int count: - :param float initial_period: - :param float subsequent_period: + :param messages: + :param count: + :param initial_period: + :param subsequent_period: """ - super().__init__(channel, messages, subsequent_period) + super().__init__(messages, subsequent_period) + self._channel = channel class ThreadBasedCyclicSendTask( @@ -172,16 +202,30 @@ class ThreadBasedCyclicSendTask( ): """Fallback cyclic send task using thread.""" - def __init__(self, bus, lock, messages, period, duration=None): + def __init__( + self, + bus: "BusABC", + lock: threading.Lock, + messages: Union[Sequence[Message], Message], + period: float, + duration: Optional[float] = None, + ): super().__init__(messages, period, duration) self.bus = bus self.send_lock = lock self.stopped = True self.thread = None - self.end_time = time.time() + duration if duration else None + self.end_time = time.perf_counter() + duration if duration else None + + if HAS_EVENTS: + self.period_ms: int = int(round(period * 1000, 0)) + self.event = win32event.CreateWaitableTimer(None, False, None) + self.start() def stop(self): + if HAS_EVENTS: + win32event.CancelWaitableTimer(self.event.handle) self.stopped = True def start(self): @@ -190,6 +234,12 @@ def start(self): name = "Cyclic send task for 0x%X" % (self.messages[0].arbitration_id) self.thread = threading.Thread(target=self._run, name=name) self.thread.daemon = True + + if HAS_EVENTS: + win32event.SetWaitableTimer( + self.event.handle, 0, self.period_ms, None, None, False + ) + self.thread.start() def _run(self): @@ -197,15 +247,19 @@ def _run(self): while not self.stopped: # Prevent calling bus.send from multiple threads with self.send_lock: - started = time.time() + started = time.perf_counter() try: self.bus.send(self.messages[msg_index]) except Exception as exc: log.exception(exc) break - if self.end_time is not None and time.time() >= self.end_time: + if self.end_time is not None and time.perf_counter() >= self.end_time: break msg_index = (msg_index + 1) % len(self.messages) - # Compensate for the time it takes to send the message - delay = self.period - (time.time() - started) - time.sleep(max(0.0, delay)) + + if HAS_EVENTS: + win32event.WaitForSingleObject(self.event.handle, self.period_ms) + else: + # Compensate for the time it takes to send the message + delay = self.period - (time.perf_counter() - started) + time.sleep(max(0.0, delay)) diff --git a/can/bus.py b/can/bus.py index 59ddbd1d2..e22bf6157 100644 --- a/can/bus.py +++ b/can/bus.py @@ -4,6 +4,10 @@ Contains the ABC bus implementation and its documentation. """ +from typing import Iterator, List, Optional, Sequence, Tuple, Union + +import can.typechecking + from abc import ABCMeta, abstractmethod import can import logging @@ -11,7 +15,8 @@ from time import time from aenum import Enum, auto -from .broadcastmanager import ThreadBasedCyclicSendTask +from can.broadcastmanager import ThreadBasedCyclicSendTask +from can.message import Message LOG = logging.getLogger(__name__) @@ -38,7 +43,12 @@ class BusABC(metaclass=ABCMeta): RECV_LOGGING_LEVEL = 9 @abstractmethod - def __init__(self, channel, can_filters=None, **kwargs): + def __init__( + self, + channel: can.typechecking.Channel, + can_filters: Optional[can.typechecking.CanFilters] = None, + **kwargs: object + ): """Construct and open a CAN bus instance of the specified type. Subclasses should call though this method with all given parameters @@ -47,28 +57,26 @@ def __init__(self, channel, can_filters=None, **kwargs): :param channel: The can interface identifier. Expected type is backend dependent. - :param list can_filters: + :param can_filters: See :meth:`~can.BusABC.set_filters` for details. :param dict kwargs: Any backend dependent configurations are passed in this dictionary """ - self._periodic_tasks = [] + self._periodic_tasks: List[can.broadcastmanager.CyclicSendTaskABC] = [] self.set_filters(can_filters) - def __str__(self): + def __str__(self) -> str: return self.channel_info - def recv(self, timeout=None): + def recv(self, timeout: Optional[float] = None) -> Optional[Message]: """Block waiting for a message from the Bus. - :type timeout: float or None :param timeout: seconds to wait for a message or None to wait indefinitely - :rtype: can.Message or None :return: - None on timeout or a :class:`can.Message` object. + None on timeout or a :class:`Message` object. :raises can.CanError: if an error occurred while reading """ @@ -100,7 +108,9 @@ def recv(self, timeout=None): else: return None - def _recv_internal(self, timeout): + def _recv_internal( + self, timeout: Optional[float] + ) -> Tuple[Optional[Message], bool]: """ Read a message from the bus and tell whether it was filtered. This methods may be called by :meth:`~can.BusABC.recv` @@ -128,7 +138,6 @@ def _recv_internal(self, timeout): :param float timeout: seconds to wait for a message, see :meth:`~can.BusABC.send` - :rtype: tuple[can.Message, bool] or tuple[None, bool] :return: 1. a message that was read or None on timeout 2. a bool that is True if message filtering has already @@ -144,14 +153,13 @@ def _recv_internal(self, timeout): raise NotImplementedError("Trying to read from a write only bus?") @abstractmethod - def send(self, msg, timeout=None): + def send(self, msg: Message, timeout: Optional[float] = None): """Transmit a message to the CAN bus. Override this method to enable the transmit path. - :param can.Message msg: A message object. + :param Message msg: A message object. - :type timeout: float or None :param timeout: If > 0, wait up to this many seconds for message to be ACK'ed or for transmit queue to be ready depending on driver implementation. @@ -164,7 +172,13 @@ def send(self, msg, timeout=None): """ raise NotImplementedError("Trying to write to a readonly bus?") - def send_periodic(self, msgs, period, duration=None, store_task=True): + def send_periodic( + self, + msgs: Union[Sequence[Message], Message], + period: float, + duration: Optional[float] = None, + store_task: bool = True, + ) -> can.broadcastmanager.CyclicSendTaskABC: """Start sending messages at a given period on this bus. The task will be active until one of the following conditions are met: @@ -175,20 +189,19 @@ def send_periodic(self, msgs, period, duration=None, store_task=True): - :meth:`BusABC.stop_all_periodic_tasks()` is called - the task's :meth:`CyclicTask.stop()` method is called. - :param Union[Sequence[can.Message], can.Message] msgs: + :param msgs: Messages to transmit - :param float period: + :param period: Period in seconds between each message - :param float duration: + :param duration: Approximate duration in seconds to continue sending messages. If no duration is provided, the task will continue indefinitely. - :param bool store_task: + :param store_task: If True (the default) the task will be attached to this Bus instance. 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 :meth:`stop` method. - :rtype: can.broadcastmanager.CyclicSendTaskABC .. note:: @@ -205,7 +218,7 @@ def send_periodic(self, msgs, period, duration=None, store_task=True): are associated with the Bus instance. """ if not isinstance(msgs, (list, tuple)): - if isinstance(msgs, can.Message): + if isinstance(msgs, Message): msgs = [msgs] else: raise ValueError("Must be either a list, tuple, or a Message") @@ -223,30 +236,34 @@ def wrapped_stop_method(remove_task=True): pass original_stop_method() - task.stop = wrapped_stop_method + setattr(task, "stop", wrapped_stop_method) if store_task: self._periodic_tasks.append(task) return task - def _send_periodic_internal(self, msgs, period, duration=None): + def _send_periodic_internal( + self, + msgs: Union[Sequence[Message], Message], + period: float, + duration: Optional[float] = None, + ) -> can.broadcastmanager.CyclicSendTaskABC: """Default implementation of periodic message sending using threading. Override this method to enable a more efficient backend specific approach. - :param Union[Sequence[can.Message], can.Message] msgs: + :param msgs: Messages to transmit - :param float period: + :param period: Period in seconds between each message - :param float duration: + :param duration: The duration between sending each message at the given rate. If 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:`stop` method. - :rtype: can.broadcastmanager.CyclicSendTaskABC """ if not hasattr(self, "_lock_send_periodic"): # Create a send lock for this bus, but not for buses which override this method @@ -275,7 +292,7 @@ def stop_all_periodic_tasks(self, remove_tasks=True): if remove_tasks: self._periodic_tasks = [] - def __iter__(self): + def __iter__(self) -> Iterator[Message]: """Allow iteration on messages as they are received. >>> for msg in bus: @@ -283,7 +300,7 @@ def __iter__(self): :yields: - :class:`can.Message` msg objects. + :class:`Message` msg objects. """ while True: msg = self.recv(timeout=1.0) @@ -291,7 +308,7 @@ def __iter__(self): yield msg @property - def filters(self): + def filters(self) -> Optional[can.typechecking.CanFilters]: """ Modify the filters of this bus. See :meth:`~can.BusABC.set_filters` for details. @@ -299,10 +316,10 @@ def filters(self): return self._filters @filters.setter - def filters(self, filters): + def filters(self, filters: Optional[can.typechecking.CanFilters]): self.set_filters(filters) - def set_filters(self, filters=None): + def set_filters(self, filters: Optional[can.typechecking.CanFilters] = None): """Apply filtering to all messages received by this Bus. All messages that match at least one filter are returned. @@ -327,25 +344,24 @@ def set_filters(self, filters=None): self._filters = filters or None self._apply_filters(self._filters) - def _apply_filters(self, filters): + def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]): """ Hook for applying the filters to the underlying kernel or hardware if supported/implemented by the interface. - :param Iterator[dict] filters: + :param filters: See :meth:`~can.BusABC.set_filters` for details. """ - def _matches_filters(self, msg): + def _matches_filters(self, msg: Message) -> bool: """Checks whether the given message matches at least one of the current filters. See :meth:`~can.BusABC.set_filters` for details on how the filters work. This method should not be overridden. - :param can.Message msg: + :param msg: the message to check if matching - :rtype: bool :return: whether the given message matches at least one filter """ @@ -388,25 +404,21 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.shutdown() @property - def state(self): + def state(self) -> BusState: """ Return the current state of the hardware - - :type: can.BusState """ return BusState.ACTIVE @state.setter - def state(self, new_state): + def state(self, new_state: BusState): """ Set the new state of the hardware - - :type: can.BusState """ raise NotImplementedError("Property is not implemented.") @staticmethod - def _detect_available_configs(): + def _detect_available_configs() -> Iterator[dict]: """Detect all configurations/channels that this interface could currently connect with. @@ -414,7 +426,6 @@ def _detect_available_configs(): May not to be implemented by every interface on every platform. - :rtype: Iterator[dict] :return: an iterable of dicts, each being a configuration suitable for usage in the interface's bus constructor. """ diff --git a/can/interfaces/__init__.py b/can/interfaces/__init__.py index 66e55153d..174b7f7ab 100644 --- a/can/interfaces/__init__.py +++ b/can/interfaces/__init__.py @@ -34,6 +34,4 @@ } ) -VALID_INTERFACES = frozenset( - list(BACKENDS.keys()) + ["socketcan_native", "socketcan_ctypes"] -) +VALID_INTERFACES = frozenset(list(BACKENDS.keys())) diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index 7557f036c..eb29f0e16 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -780,7 +780,7 @@ def GetValue(self, Channel, Parameter): A touple with 2 values """ try: - if Parameter in { + if Parameter in ( PCAN_API_VERSION, PCAN_HARDWARE_NAME, PCAN_CHANNEL_VERSION, @@ -788,7 +788,7 @@ def GetValue(self, Channel, Parameter): PCAN_TRACE_LOCATION, PCAN_BITRATE_INFO_FD, PCAN_IP_ADDRESS, - }: + ): mybuffer = create_string_buffer(256) else: mybuffer = c_int(0) @@ -822,7 +822,7 @@ def SetValue(self, Channel, Parameter, Buffer): A TPCANStatus error code """ try: - if Parameter in {PCAN_LOG_LOCATION, PCAN_LOG_TEXT, PCAN_TRACE_LOCATION}: + if Parameter in (PCAN_LOG_LOCATION, PCAN_LOG_TEXT, PCAN_TRACE_LOCATION): mybuffer = create_string_buffer(256) else: mybuffer = c_int(0) diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 4bddfc3b1..26da9ded9 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -426,9 +426,7 @@ def send(self, msg, timeout=None): CANMsg.MSGTYPE = msgType # if a remote frame will be sent, data bytes are not important. - if msg.is_remote_frame: - CANMsg.MSGTYPE = msgType.value | PCAN_MESSAGE_RTR.value - else: + if not msg.is_remote_frame: # copy data for i in range(CANMsg.LEN): CANMsg.DATA[i] = msg.data[i] diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index be5d672d3..710c23185 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -97,9 +97,9 @@ def __init__( if bitrate is not None and btr is not None: raise ValueError("Bitrate and btr mutually exclusive.") if bitrate is not None: - self.set_bitrate(self, bitrate) + self.set_bitrate(bitrate) if btr is not None: - self.set_bitrate_reg(self, btr) + self.set_bitrate_reg(btr) self.open() super().__init__( diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 970994c56..7c8d9c7c3 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -11,6 +11,7 @@ import ctypes import logging import time +import os try: # Try builtin Python 3 Windows API @@ -37,10 +38,14 @@ # ==================== LOG = logging.getLogger(__name__) +# Import Vector API module +# ======================== +from . import xldefine, xlclass + # Import safely Vector API module for Travis tests -vxlapi = None +xldriver = None try: - from . import vxlapi + from . import xldriver except Exception as exc: LOG.warning("Could not import vxlapi: %s", exc) @@ -66,7 +71,7 @@ def __init__( sjwDbr=2, tseg1Dbr=6, tseg2Dbr=3, - **kwargs + **kwargs, ): """ :param list channel: @@ -93,8 +98,14 @@ def __init__( Which bitrate to use for data phase in CAN FD. Defaults to arbitration bitrate. """ - if vxlapi is None: + if os.name != "nt" and not kwargs.get("_testing", False): + raise OSError( + f'The Vector interface is only supported on Windows, but you are running "{os.name}"' + ) + + if xldriver is None: raise ImportError("The Vector API has not been loaded") + self.poll_interval = poll_interval if isinstance(channel, (list, tuple)): self.channels = channel @@ -129,8 +140,8 @@ def __init__( "None of the configured channels could be found on the specified hardware." ) - vxlapi.xlOpenDriver() - self.port_handle = vxlapi.XLportHandle(vxlapi.XL_INVALID_PORTHANDLE) + xldriver.xlOpenDriver() + self.port_handle = xlclass.XLportHandle(xldefine.XL_INVALID_PORTHANDLE) self.mask = 0 self.fd = fd # Get channels masks @@ -143,16 +154,16 @@ def __init__( hw_type = ctypes.c_uint(0) hw_index = ctypes.c_uint(0) hw_channel = ctypes.c_uint(0) - vxlapi.xlGetApplConfig( + xldriver.xlGetApplConfig( self._app_name, channel, hw_type, hw_index, hw_channel, - vxlapi.XL_BUS_TYPE_CAN, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, ) LOG.debug("Channel index %d found", channel) - idx = vxlapi.xlGetChannelIndex( + idx = xldriver.xlGetChannelIndex( hw_type.value, hw_index.value, hw_channel.value ) if idx < 0: @@ -161,7 +172,7 @@ def __init__( # Raise an exception as if the driver # would have signalled XL_ERR_HW_NOT_PRESENT. raise VectorError( - vxlapi.XL_ERR_HW_NOT_PRESENT, + xldefine.XL_Status.XL_ERR_HW_NOT_PRESENT.value, "XL_ERR_HW_NOT_PRESENT", "xlGetChannelIndex", ) @@ -173,29 +184,29 @@ def __init__( self.index_to_channel[idx] = channel self.mask |= mask - permission_mask = vxlapi.XLaccess() + permission_mask = xlclass.XLaccess() # Set mask to request channel init permission if needed if bitrate or fd: permission_mask.value = self.mask if fd: - vxlapi.xlOpenPort( + xldriver.xlOpenPort( self.port_handle, self._app_name, self.mask, permission_mask, rx_queue_size, - vxlapi.XL_INTERFACE_VERSION_V4, - vxlapi.XL_BUS_TYPE_CAN, + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION_V4.value, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, ) else: - vxlapi.xlOpenPort( + xldriver.xlOpenPort( self.port_handle, self._app_name, self.mask, permission_mask, rx_queue_size, - vxlapi.XL_INTERFACE_VERSION, - vxlapi.XL_BUS_TYPE_CAN, + xldefine.XL_InterfaceVersion.XL_INTERFACE_VERSION.value, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, ) LOG.debug( "Open Port: PortHandle: %d, PermissionMask: 0x%X", @@ -205,7 +216,7 @@ def __init__( if permission_mask.value == self.mask: if fd: - self.canFdConf = vxlapi.XLcanFdConf() + self.canFdConf = xlclass.XLcanFdConf() if bitrate: self.canFdConf.arbitrationBitRate = ctypes.c_uint(bitrate) else: @@ -221,7 +232,7 @@ def __init__( self.canFdConf.tseg1Dbr = ctypes.c_uint(tseg1Dbr) self.canFdConf.tseg2Dbr = ctypes.c_uint(tseg2Dbr) - vxlapi.xlCanFdSetConfiguration( + xldriver.xlCanFdSetConfiguration( self.port_handle, self.mask, self.canFdConf ) LOG.info( @@ -243,7 +254,7 @@ def __init__( ) else: if bitrate: - vxlapi.xlCanSetChannelBitrate( + xldriver.xlCanSetChannelBitrate( self.port_handle, permission_mask, bitrate ) LOG.info("SetChannelBitrate: baudr.=%u", bitrate) @@ -252,25 +263,28 @@ def __init__( # Enable/disable TX receipts tx_receipts = 1 if receive_own_messages else 0 - vxlapi.xlCanSetChannelMode(self.port_handle, self.mask, tx_receipts, 0) + xldriver.xlCanSetChannelMode(self.port_handle, self.mask, tx_receipts, 0) if HAS_EVENTS: - self.event_handle = vxlapi.XLhandle() - vxlapi.xlSetNotification(self.port_handle, self.event_handle, 1) + self.event_handle = xlclass.XLhandle() + xldriver.xlSetNotification(self.port_handle, self.event_handle, 1) else: LOG.info("Install pywin32 to avoid polling") try: - vxlapi.xlActivateChannel( - self.port_handle, self.mask, vxlapi.XL_BUS_TYPE_CAN, 0 + xldriver.xlActivateChannel( + self.port_handle, + self.mask, + xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, + 0, ) except VectorError: self.shutdown() raise # Calculate time offset for absolute timestamps - offset = vxlapi.XLuint64() - vxlapi.xlGetSyncTime(self.port_handle, offset) + offset = xlclass.XLuint64() + xldriver.xlGetSyncTime(self.port_handle, offset) self._time_offset = time.time() - offset.value * 1e-9 self._is_filtered = False @@ -285,14 +299,14 @@ def _apply_filters(self, filters): ): try: for can_filter in filters: - vxlapi.xlCanSetChannelAcceptance( + xldriver.xlCanSetChannelAcceptance( self.port_handle, self.mask, can_filter["can_id"], can_filter["can_mask"], - vxlapi.XL_CAN_EXT + xldefine.XL_AcceptanceFilter.XL_CAN_EXT.value if can_filter.get("extended") - else vxlapi.XL_CAN_STD, + else xldefine.XL_AcceptanceFilter.XL_CAN_STD.value, ) except VectorError as exc: LOG.warning("Could not set filters: %s", exc) @@ -307,11 +321,19 @@ def _apply_filters(self, filters): # fallback: reset filters self._is_filtered = False try: - vxlapi.xlCanSetChannelAcceptance( - self.port_handle, self.mask, 0x0, 0x0, vxlapi.XL_CAN_EXT + xldriver.xlCanSetChannelAcceptance( + self.port_handle, + self.mask, + 0x0, + 0x0, + xldefine.XL_AcceptanceFilter.XL_CAN_EXT.value, ) - vxlapi.xlCanSetChannelAcceptance( - self.port_handle, self.mask, 0x0, 0x0, vxlapi.XL_CAN_STD + xldriver.xlCanSetChannelAcceptance( + self.port_handle, + self.mask, + 0x0, + 0x0, + xldefine.XL_AcceptanceFilter.XL_CAN_STD.value, ) except VectorError as exc: LOG.warning("Could not reset filters: %s", exc) @@ -320,22 +342,24 @@ def _recv_internal(self, timeout): end_time = time.time() + timeout if timeout is not None else None if self.fd: - event = vxlapi.XLcanRxEvent() + event = xlclass.XLcanRxEvent() else: - event = vxlapi.XLevent() + event = xlclass.XLevent() event_count = ctypes.c_uint() while True: if self.fd: try: - vxlapi.xlCanReceive(self.port_handle, event) + xldriver.xlCanReceive(self.port_handle, event) except VectorError as exc: - if exc.error_code != vxlapi.XL_ERR_QUEUE_IS_EMPTY: + if exc.error_code != xldefine.XL_Status.XL_ERR_QUEUE_IS_EMPTY.value: raise else: if ( - event.tag == vxlapi.XL_CAN_EV_TAG_RX_OK - or event.tag == vxlapi.XL_CAN_EV_TAG_TX_OK + event.tag + == xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_RX_OK.value + or event.tag + == xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_TX_OK.value ): msg_id = event.tagData.canRxOkMsg.canId dlc = dlc2len(event.tagData.canRxOkMsg.dlc) @@ -345,14 +369,30 @@ def _recv_internal(self, timeout): msg = Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, - is_extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), - is_remote_frame=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_RTR), - is_error_frame=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_EF), - is_fd=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_EDL), + is_extended_id=bool( + msg_id + & xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value + ), + is_remote_frame=bool( + flags + & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_RTR.value + ), + is_error_frame=bool( + flags + & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_EF.value + ), + is_fd=bool( + flags + & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_EDL.value + ), error_state_indicator=bool( - flags & vxlapi.XL_CAN_RXMSG_FLAG_ESI + flags + & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_ESI.value + ), + bitrate_switch=bool( + flags + & xldefine.XL_CANFD_RX_MessageFlags.XL_CAN_RXMSG_FLAG_BRS.value ), - bitrate_switch=bool(flags & vxlapi.XL_CAN_RXMSG_FLAG_BRS), dlc=dlc, data=event.tagData.canRxOkMsg.data[:dlc], channel=channel, @@ -361,12 +401,12 @@ def _recv_internal(self, timeout): else: event_count.value = 1 try: - vxlapi.xlReceive(self.port_handle, event_count, event) + xldriver.xlReceive(self.port_handle, event_count, event) except VectorError as exc: - if exc.error_code != vxlapi.XL_ERR_QUEUE_IS_EMPTY: + if exc.error_code != xldefine.XL_Status.XL_ERR_QUEUE_IS_EMPTY.value: raise else: - if event.tag == vxlapi.XL_RECEIVE_MSG: + if event.tag == xldefine.XL_EventTags.XL_RECEIVE_MSG.value: msg_id = event.tagData.msg.id dlc = event.tagData.msg.dlc flags = event.tagData.msg.flags @@ -375,12 +415,17 @@ def _recv_internal(self, timeout): msg = Message( timestamp=timestamp + self._time_offset, arbitration_id=msg_id & 0x1FFFFFFF, - is_extended_id=bool(msg_id & vxlapi.XL_CAN_EXT_MSG_ID), + is_extended_id=bool( + msg_id + & xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value + ), is_remote_frame=bool( - flags & vxlapi.XL_CAN_MSG_FLAG_REMOTE_FRAME + flags + & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_REMOTE_FRAME.value ), is_error_frame=bool( - flags & vxlapi.XL_CAN_MSG_FLAG_ERROR_FRAME + flags + & xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_ERROR_FRAME.value ), is_fd=False, dlc=dlc, @@ -408,7 +453,7 @@ def send(self, msg, timeout=None): msg_id = msg.arbitration_id if msg.is_extended_id: - msg_id |= vxlapi.XL_CAN_EXT_MSG_ID + msg_id |= xldefine.XL_MessageFlagsExtended.XL_CAN_EXT_MSG_ID.value flags = 0 @@ -418,17 +463,17 @@ def send(self, msg, timeout=None): if self.fd: if msg.is_fd: - flags |= vxlapi.XL_CAN_TXMSG_FLAG_EDL + flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_EDL.value if msg.bitrate_switch: - flags |= vxlapi.XL_CAN_TXMSG_FLAG_BRS + flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_BRS.value if msg.is_remote_frame: - flags |= vxlapi.XL_CAN_TXMSG_FLAG_RTR + flags |= xldefine.XL_CANFD_TX_MessageFlags.XL_CAN_TXMSG_FLAG_RTR.value message_count = 1 MsgCntSent = ctypes.c_uint(1) - XLcanTxEvent = vxlapi.XLcanTxEvent() - XLcanTxEvent.tag = vxlapi.XL_CAN_EV_TAG_TX_MSG + XLcanTxEvent = xlclass.XLcanTxEvent() + XLcanTxEvent.tag = xldefine.XL_CANFD_TX_EventTags.XL_CAN_EV_TAG_TX_MSG.value XLcanTxEvent.transId = 0xFFFF XLcanTxEvent.tagData.canMsg.canId = msg_id @@ -436,37 +481,39 @@ def send(self, msg, timeout=None): XLcanTxEvent.tagData.canMsg.dlc = len2dlc(msg.dlc) for idx, value in enumerate(msg.data): XLcanTxEvent.tagData.canMsg.data[idx] = value - vxlapi.xlCanTransmitEx( + xldriver.xlCanTransmitEx( self.port_handle, mask, message_count, MsgCntSent, XLcanTxEvent ) else: if msg.is_remote_frame: - flags |= vxlapi.XL_CAN_MSG_FLAG_REMOTE_FRAME + flags |= xldefine.XL_MessageFlags.XL_CAN_MSG_FLAG_REMOTE_FRAME.value message_count = ctypes.c_uint(1) - xl_event = vxlapi.XLevent() - xl_event.tag = vxlapi.XL_TRANSMIT_MSG + xl_event = xlclass.XLevent() + xl_event.tag = xldefine.XL_EventTags.XL_TRANSMIT_MSG.value xl_event.tagData.msg.id = msg_id xl_event.tagData.msg.dlc = msg.dlc xl_event.tagData.msg.flags = flags for idx, value in enumerate(msg.data): xl_event.tagData.msg.data[idx] = value - vxlapi.xlCanTransmit(self.port_handle, mask, message_count, xl_event) + xldriver.xlCanTransmit(self.port_handle, mask, message_count, xl_event) def flush_tx_buffer(self): - vxlapi.xlCanFlushTransmitQueue(self.port_handle, self.mask) + xldriver.xlCanFlushTransmitQueue(self.port_handle, self.mask) def shutdown(self): - vxlapi.xlDeactivateChannel(self.port_handle, self.mask) - vxlapi.xlClosePort(self.port_handle) - vxlapi.xlCloseDriver() + xldriver.xlDeactivateChannel(self.port_handle, self.mask) + xldriver.xlClosePort(self.port_handle) + xldriver.xlCloseDriver() def reset(self): - vxlapi.xlDeactivateChannel(self.port_handle, self.mask) - vxlapi.xlActivateChannel(self.port_handle, self.mask, vxlapi.XL_BUS_TYPE_CAN, 0) + xldriver.xlDeactivateChannel(self.port_handle, self.mask) + xldriver.xlActivateChannel( + self.port_handle, self.mask, xldefine.XL_BusTypes.XL_BUS_TYPE_CAN.value, 0 + ) @staticmethod def _detect_available_configs(): @@ -474,7 +521,10 @@ def _detect_available_configs(): channel_configs = get_channel_configs() LOG.info("Found %d channels", len(channel_configs)) for channel_config in channel_configs: - if not channel_config.channelBusCapabilities & vxlapi.XL_BUS_ACTIVE_CAP_CAN: + if ( + not channel_config.channelBusCapabilities + & xldefine.XL_BusCapabilities.XL_BUS_ACTIVE_CAP_CAN.value + ): continue LOG.info( "Channel index %d: %s", @@ -488,7 +538,7 @@ def _detect_available_configs(): "channel": channel_config.channelIndex, "supports_fd": bool( channel_config.channelBusCapabilities - & vxlapi.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT + & xldefine.XL_ChannelCapabilities.XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT.value ), } ) @@ -496,13 +546,13 @@ def _detect_available_configs(): def get_channel_configs(): - if vxlapi is None: + if xldriver is None: return [] - driver_config = vxlapi.XLdriverConfig() + driver_config = xlclass.XLdriverConfig() try: - vxlapi.xlOpenDriver() - vxlapi.xlGetDriverConfig(driver_config) - vxlapi.xlCloseDriver() + xldriver.xlOpenDriver() + xldriver.xlGetDriverConfig(driver_config) + xldriver.xlCloseDriver() except Exception: pass return [driver_config.channel[i] for i in range(driver_config.channelCount)] diff --git a/can/interfaces/vector/vxlapi.py b/can/interfaces/vector/vxlapi.py deleted file mode 100644 index b631923f2..000000000 --- a/can/interfaces/vector/vxlapi.py +++ /dev/null @@ -1,486 +0,0 @@ -# coding: utf-8 - -""" -Ctypes wrapper module for Vector CAN Interface on win32/win64 systems. - -Authors: Julien Grave , Christian Sandberg -""" - -# Import Standard Python Modules -# ============================== -import ctypes -import logging -import platform -from .exceptions import VectorError - -# Define Module Logger -# ==================== -LOG = logging.getLogger(__name__) - -# Vector XL API Definitions -# ========================= -# Load Windows DLL -DLL_NAME = "vxlapi64" if platform.architecture()[0] == "64bit" else "vxlapi" -_xlapi_dll = ctypes.windll.LoadLibrary(DLL_NAME) - -XL_BUS_TYPE_CAN = 0x00000001 - -XL_ERR_QUEUE_IS_EMPTY = 10 -XL_ERR_HW_NOT_PRESENT = 129 - -# XLeventTag -# Common and CAN events -XL_NO_COMMAND = 0 -XL_RECEIVE_MSG = 1 -XL_CHIP_STATE = 4 -XL_TRANSCEIVER = 6 -XL_TIMER = 8 -XL_TRANSMIT_MSG = 10 -XL_SYNC_PULSE = 11 -XL_APPLICATION_NOTIFICATION = 15 - -# CAN/CAN-FD event tags -# Rx -XL_CAN_EV_TAG_RX_OK = 0x0400 -XL_CAN_EV_TAG_RX_ERROR = 0x0401 -XL_CAN_EV_TAG_TX_ERROR = 0x0402 -XL_CAN_EV_TAG_TX_REQUEST = 0x0403 -XL_CAN_EV_TAG_TX_OK = 0x0404 -XL_CAN_EV_TAG_CHIP_STATE = 0x0409 -# Tx -XL_CAN_EV_TAG_TX_MSG = 0x0440 - -# s_xl_can_msg : id -XL_CAN_EXT_MSG_ID = 0x80000000 - -# s_xl_can_msg : flags -XL_CAN_MSG_FLAG_ERROR_FRAME = 0x01 -XL_CAN_MSG_FLAG_REMOTE_FRAME = 0x10 -XL_CAN_MSG_FLAG_TX_COMPLETED = 0x40 - -# to be used with -# XLcanTxEvent::XL_CAN_TX_MSG::msgFlags -XL_CAN_TXMSG_FLAG_EDL = 0x0001 -XL_CAN_TXMSG_FLAG_BRS = 0x0002 -XL_CAN_TXMSG_FLAG_RTR = 0x0010 -XL_CAN_TXMSG_FLAG_HIGHPRIO = 0x0080 -XL_CAN_TXMSG_FLAG_WAKEUP = 0x0200 - -# to be used with -# XLcanRxEvent::XL_CAN_EV_RX_MSG::msgFlags -# XLcanRxEvent::XL_CAN_EV_TX_REQUEST::msgFlags -# XLcanRxEvent::XL_CAN_EV_RX_MSG::msgFlags -# XLcanRxEvent::XL_CAN_EV_TX_REMOVED::msgFlags -# XLcanRxEvent::XL_CAN_EV_ERROR::msgFlags -XL_CAN_RXMSG_FLAG_EDL = 0x0001 -XL_CAN_RXMSG_FLAG_BRS = 0x0002 -XL_CAN_RXMSG_FLAG_ESI = 0x0004 -XL_CAN_RXMSG_FLAG_RTR = 0x0010 -XL_CAN_RXMSG_FLAG_EF = 0x0200 -XL_CAN_RXMSG_FLAG_ARB_LOST = 0x0400 -XL_CAN_RXMSG_FLAG_WAKEUP = 0x2000 -XL_CAN_RXMSG_FLAG_TE = 0x4000 - -# acceptance filter -XL_CAN_STD = 1 -XL_CAN_EXT = 2 - -# s_xl_chip_state : busStatus -XL_CHIPSTAT_BUSOFF = 0x01 -XL_CHIPSTAT_ERROR_PASSIVE = 0x02 -XL_CHIPSTAT_ERROR_WARNING = 0x04 -XL_CHIPSTAT_ERROR_ACTIVE = 0x08 - -# s_xl_can_ev_error : errorCode -XL_CAN_ERRC_BIT_ERROR = 1 -XL_CAN_ERRC_FORM_ERROR = 2 -XL_CAN_ERRC_STUFF_ERROR = 3 -XL_CAN_ERRC_OTHER_ERROR = 4 -XL_CAN_ERRC_CRC_ERROR = 5 -XL_CAN_ERRC_ACK_ERROR = 6 -XL_CAN_ERRC_NACK_ERROR = 7 -XL_CAN_ERRC_OVLD_ERROR = 8 -XL_CAN_ERRC_EXCPT_ERROR = 9 - -XLuint64 = ctypes.c_int64 -XLaccess = XLuint64 -XLhandle = ctypes.c_void_p - -MAX_MSG_LEN = 8 - -# CAN / CAN-FD types and definitions -XL_CAN_MAX_DATA_LEN = 64 -XL_CANFD_RX_EVENT_HEADER_SIZE = 32 -XL_CANFD_MAX_EVENT_SIZE = 128 - -# current version -XL_INTERFACE_VERSION = 3 -XL_INTERFACE_VERSION_V4 = 4 - -XL_BUS_ACTIVE_CAP_CAN = XL_BUS_TYPE_CAN << 16 -XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT = 0x80000000 - -# structure for XL_RECEIVE_MSG, XL_TRANSMIT_MSG -class s_xl_can_msg(ctypes.Structure): - _fields_ = [ - ("id", ctypes.c_ulong), - ("flags", ctypes.c_ushort), - ("dlc", ctypes.c_ushort), - ("res1", XLuint64), - ("data", ctypes.c_ubyte * MAX_MSG_LEN), - ("res2", XLuint64), - ] - - -class s_xl_can_ev_error(ctypes.Structure): - _fields_ = [("errorCode", ctypes.c_ubyte), ("reserved", ctypes.c_ubyte * 95)] - - -class s_xl_can_ev_chip_state(ctypes.Structure): - _fields_ = [ - ("busStatus", ctypes.c_ubyte), - ("txErrorCounter", ctypes.c_ubyte), - ("rxErrorCounter", ctypes.c_ubyte), - ("reserved", ctypes.c_ubyte), - ("reserved0", ctypes.c_uint), - ] - - -class s_xl_can_ev_sync_pulse(ctypes.Structure): - _fields_ = [ - ("triggerSource", ctypes.c_uint), - ("reserved", ctypes.c_uint), - ("time", XLuint64), - ] - - -# BASIC bus message structure -class s_xl_tag_data(ctypes.Union): - _fields_ = [("msg", s_xl_can_msg)] - - -# CAN FD messages -class s_xl_can_ev_rx_msg(ctypes.Structure): - _fields_ = [ - ("canId", ctypes.c_uint), - ("msgFlags", ctypes.c_uint), - ("crc", ctypes.c_uint), - ("reserved1", ctypes.c_ubyte * 12), - ("totalBitCnt", ctypes.c_ushort), - ("dlc", ctypes.c_ubyte), - ("reserved", ctypes.c_ubyte * 5), - ("data", ctypes.c_ubyte * XL_CAN_MAX_DATA_LEN), - ] - - -class s_xl_can_ev_tx_request(ctypes.Structure): - _fields_ = [ - ("canId", ctypes.c_uint), - ("msgFlags", ctypes.c_uint), - ("dlc", ctypes.c_ubyte), - ("txAttemptConf", ctypes.c_ubyte), - ("reserved", ctypes.c_ushort), - ("data", ctypes.c_ubyte * XL_CAN_MAX_DATA_LEN), - ] - - -class s_xl_can_tx_msg(ctypes.Structure): - _fields_ = [ - ("canId", ctypes.c_uint), - ("msgFlags", ctypes.c_uint), - ("dlc", ctypes.c_ubyte), - ("reserved", ctypes.c_ubyte * 7), - ("data", ctypes.c_ubyte * XL_CAN_MAX_DATA_LEN), - ] - - -class s_rxTagData(ctypes.Union): - _fields_ = [ - ("canRxOkMsg", s_xl_can_ev_rx_msg), - ("canTxOkMsg", s_xl_can_ev_rx_msg), - ("canTxRequest", s_xl_can_ev_tx_request), - ("canError", s_xl_can_ev_error), - ("canChipState", s_xl_can_ev_chip_state), - ("canSyncPulse", s_xl_can_ev_sync_pulse), - ] - - -class s_txTagData(ctypes.Union): - _fields_ = [("canMsg", s_xl_can_tx_msg)] - - -# BASIC events -XLeventTag = ctypes.c_ubyte - - -class XLevent(ctypes.Structure): - _fields_ = [ - ("tag", XLeventTag), - ("chanIndex", ctypes.c_ubyte), - ("transId", ctypes.c_ushort), - ("portHandle", ctypes.c_ushort), - ("flags", ctypes.c_ubyte), - ("reserved", ctypes.c_ubyte), - ("timeStamp", XLuint64), - ("tagData", s_xl_tag_data), - ] - - -# CAN FD events -class XLcanRxEvent(ctypes.Structure): - _fields_ = [ - ("size", ctypes.c_int), - ("tag", ctypes.c_ushort), - ("chanIndex", ctypes.c_ubyte), - ("reserved", ctypes.c_ubyte), - ("userHandle", ctypes.c_int), - ("flagsChip", ctypes.c_ushort), - ("reserved0", ctypes.c_ushort), - ("reserved1", XLuint64), - ("timeStamp", XLuint64), - ("tagData", s_rxTagData), - ] - - -class XLcanTxEvent(ctypes.Structure): - _fields_ = [ - ("tag", ctypes.c_ushort), - ("transId", ctypes.c_ushort), - ("chanIndex", ctypes.c_ubyte), - ("reserved", ctypes.c_ubyte * 3), - ("tagData", s_txTagData), - ] - - -# CAN FD configuration structure -class XLcanFdConf(ctypes.Structure): - _fields_ = [ - ("arbitrationBitRate", ctypes.c_uint), - ("sjwAbr", ctypes.c_uint), - ("tseg1Abr", ctypes.c_uint), - ("tseg2Abr", ctypes.c_uint), - ("dataBitRate", ctypes.c_uint), - ("sjwDbr", ctypes.c_uint), - ("tseg1Dbr", ctypes.c_uint), - ("tseg2Dbr", ctypes.c_uint), - ("reserved", ctypes.c_uint * 2), - ] - - -class XLchannelConfig(ctypes.Structure): - _pack_ = 1 - _fields_ = [ - ("name", ctypes.c_char * 32), - ("hwType", ctypes.c_ubyte), - ("hwIndex", ctypes.c_ubyte), - ("hwChannel", ctypes.c_ubyte), - ("transceiverType", ctypes.c_ushort), - ("transceiverState", ctypes.c_ushort), - ("configError", ctypes.c_ushort), - ("channelIndex", ctypes.c_ubyte), - ("channelMask", XLuint64), - ("channelCapabilities", ctypes.c_uint), - ("channelBusCapabilities", ctypes.c_uint), - ("isOnBus", ctypes.c_ubyte), - ("connectedBusType", ctypes.c_uint), - ("busParams", ctypes.c_ubyte * 32), - ("_doNotUse", ctypes.c_uint), - ("driverVersion", ctypes.c_uint), - ("interfaceVersion", ctypes.c_uint), - ("raw_data", ctypes.c_uint * 10), - ("serialNumber", ctypes.c_uint), - ("articleNumber", ctypes.c_uint), - ("transceiverName", ctypes.c_char * 32), - ("specialCabFlags", ctypes.c_uint), - ("dominantTimeout", ctypes.c_uint), - ("dominantRecessiveDelay", ctypes.c_ubyte), - ("recessiveDominantDelay", ctypes.c_ubyte), - ("connectionInfo", ctypes.c_ubyte), - ("currentlyAvailableTimestamps", ctypes.c_ubyte), - ("minimalSupplyVoltage", ctypes.c_ushort), - ("maximalSupplyVoltage", ctypes.c_ushort), - ("maximalBaudrate", ctypes.c_uint), - ("fpgaCoreCapabilities", ctypes.c_ubyte), - ("specialDeviceStatus", ctypes.c_ubyte), - ("channelBusActiveCapabilities", ctypes.c_ushort), - ("breakOffset", ctypes.c_ushort), - ("delimiterOffset", ctypes.c_ushort), - ("reserved", ctypes.c_uint * 3), - ] - - -class XLdriverConfig(ctypes.Structure): - _fields_ = [ - ("dllVersion", ctypes.c_uint), - ("channelCount", ctypes.c_uint), - ("reserved", ctypes.c_uint * 10), - ("channel", XLchannelConfig * 64), - ] - - -# driver status -XLstatus = ctypes.c_short - -# porthandle -XL_INVALID_PORTHANDLE = -1 -XLportHandle = ctypes.c_long - - -def check_status(result, function, arguments): - if result > 0: - raise VectorError(result, xlGetErrorString(result).decode(), function.__name__) - return result - - -xlGetDriverConfig = _xlapi_dll.xlGetDriverConfig -xlGetDriverConfig.argtypes = [ctypes.POINTER(XLdriverConfig)] -xlGetDriverConfig.restype = XLstatus -xlGetDriverConfig.errcheck = check_status - -xlOpenDriver = _xlapi_dll.xlOpenDriver -xlOpenDriver.argtypes = [] -xlOpenDriver.restype = XLstatus -xlOpenDriver.errcheck = check_status - -xlCloseDriver = _xlapi_dll.xlCloseDriver -xlCloseDriver.argtypes = [] -xlCloseDriver.restype = XLstatus -xlCloseDriver.errcheck = check_status - -xlGetApplConfig = _xlapi_dll.xlGetApplConfig -xlGetApplConfig.argtypes = [ - ctypes.c_char_p, - ctypes.c_uint, - ctypes.POINTER(ctypes.c_uint), - ctypes.POINTER(ctypes.c_uint), - ctypes.POINTER(ctypes.c_uint), - ctypes.c_uint, -] -xlGetApplConfig.restype = XLstatus -xlGetApplConfig.errcheck = check_status - -xlGetChannelIndex = _xlapi_dll.xlGetChannelIndex -xlGetChannelIndex.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] -xlGetChannelIndex.restype = ctypes.c_int - -xlGetChannelMask = _xlapi_dll.xlGetChannelMask -xlGetChannelMask.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] -xlGetChannelMask.restype = XLaccess - -xlOpenPort = _xlapi_dll.xlOpenPort -xlOpenPort.argtypes = [ - ctypes.POINTER(XLportHandle), - ctypes.c_char_p, - XLaccess, - ctypes.POINTER(XLaccess), - ctypes.c_uint, - ctypes.c_uint, - ctypes.c_uint, -] -xlOpenPort.restype = XLstatus -xlOpenPort.errcheck = check_status - -xlGetSyncTime = _xlapi_dll.xlGetSyncTime -xlGetSyncTime.argtypes = [XLportHandle, ctypes.POINTER(XLuint64)] -xlGetSyncTime.restype = XLstatus -xlGetSyncTime.errcheck = check_status - -xlClosePort = _xlapi_dll.xlClosePort -xlClosePort.argtypes = [XLportHandle] -xlClosePort.restype = XLstatus -xlClosePort.errcheck = check_status - -xlSetNotification = _xlapi_dll.xlSetNotification -xlSetNotification.argtypes = [XLportHandle, ctypes.POINTER(XLhandle), ctypes.c_int] -xlSetNotification.restype = XLstatus -xlSetNotification.errcheck = check_status - -xlCanSetChannelMode = _xlapi_dll.xlCanSetChannelMode -xlCanSetChannelMode.argtypes = [XLportHandle, XLaccess, ctypes.c_int, ctypes.c_int] -xlCanSetChannelMode.restype = XLstatus -xlCanSetChannelMode.errcheck = check_status - -xlActivateChannel = _xlapi_dll.xlActivateChannel -xlActivateChannel.argtypes = [XLportHandle, XLaccess, ctypes.c_uint, ctypes.c_uint] -xlActivateChannel.restype = XLstatus -xlActivateChannel.errcheck = check_status - -xlDeactivateChannel = _xlapi_dll.xlDeactivateChannel -xlDeactivateChannel.argtypes = [XLportHandle, XLaccess] -xlDeactivateChannel.restype = XLstatus -xlDeactivateChannel.errcheck = check_status - -xlCanFdSetConfiguration = _xlapi_dll.xlCanFdSetConfiguration -xlCanFdSetConfiguration.argtypes = [XLportHandle, XLaccess, ctypes.POINTER(XLcanFdConf)] -xlCanFdSetConfiguration.restype = XLstatus -xlCanFdSetConfiguration.errcheck = check_status - -xlReceive = _xlapi_dll.xlReceive -xlReceive.argtypes = [ - XLportHandle, - ctypes.POINTER(ctypes.c_uint), - ctypes.POINTER(XLevent), -] -xlReceive.restype = XLstatus -xlReceive.errcheck = check_status - -xlCanReceive = _xlapi_dll.xlCanReceive -xlCanReceive.argtypes = [XLportHandle, ctypes.POINTER(XLcanRxEvent)] -xlCanReceive.restype = XLstatus -xlCanReceive.errcheck = check_status - -xlGetErrorString = _xlapi_dll.xlGetErrorString -xlGetErrorString.argtypes = [XLstatus] -xlGetErrorString.restype = ctypes.c_char_p - -xlCanSetChannelBitrate = _xlapi_dll.xlCanSetChannelBitrate -xlCanSetChannelBitrate.argtypes = [XLportHandle, XLaccess, ctypes.c_ulong] -xlCanSetChannelBitrate.restype = XLstatus -xlCanSetChannelBitrate.errcheck = check_status - -xlCanTransmit = _xlapi_dll.xlCanTransmit -xlCanTransmit.argtypes = [ - XLportHandle, - XLaccess, - ctypes.POINTER(ctypes.c_uint), - ctypes.POINTER(XLevent), -] -xlCanTransmit.restype = XLstatus -xlCanTransmit.errcheck = check_status - -xlCanTransmitEx = _xlapi_dll.xlCanTransmitEx -xlCanTransmitEx.argtypes = [ - XLportHandle, - XLaccess, - ctypes.c_uint, - ctypes.POINTER(ctypes.c_uint), - ctypes.POINTER(XLcanTxEvent), -] -xlCanTransmitEx.restype = XLstatus -xlCanTransmitEx.errcheck = check_status - -xlCanFlushTransmitQueue = _xlapi_dll.xlCanFlushTransmitQueue -xlCanFlushTransmitQueue.argtypes = [XLportHandle, XLaccess] -xlCanFlushTransmitQueue.restype = XLstatus -xlCanFlushTransmitQueue.errcheck = check_status - -xlCanSetChannelAcceptance = _xlapi_dll.xlCanSetChannelAcceptance -xlCanSetChannelAcceptance.argtypes = [ - XLportHandle, - XLaccess, - ctypes.c_ulong, - ctypes.c_ulong, - ctypes.c_uint, -] -xlCanSetChannelAcceptance.restype = XLstatus -xlCanSetChannelAcceptance.errcheck = check_status - -xlCanResetAcceptance = _xlapi_dll.xlCanResetAcceptance -xlCanResetAcceptance.argtypes = [XLportHandle, XLaccess, ctypes.c_uint] -xlCanResetAcceptance.restype = XLstatus -xlCanResetAcceptance.errcheck = check_status - -xlCanRequestChipState = _xlapi_dll.xlCanRequestChipState -xlCanRequestChipState.argtypes = [XLportHandle, XLaccess] -xlCanRequestChipState.restype = XLstatus -xlCanRequestChipState.errcheck = check_status diff --git a/can/interfaces/vector/xlclass.py b/can/interfaces/vector/xlclass.py new file mode 100644 index 000000000..90fc611bd --- /dev/null +++ b/can/interfaces/vector/xlclass.py @@ -0,0 +1,226 @@ +# coding: utf-8 + +""" +Definition of data types and structures for vxlapi. + +Authors: Julien Grave , Christian Sandberg +""" + +# Import Standard Python Modules +# ============================== +import ctypes + +# Vector XL API Definitions +# ========================= +from . import xldefine + +XLuint64 = ctypes.c_int64 +XLaccess = XLuint64 +XLhandle = ctypes.c_void_p +XLstatus = ctypes.c_short +XLportHandle = ctypes.c_long +XLeventTag = ctypes.c_ubyte + +# structure for XL_RECEIVE_MSG, XL_TRANSMIT_MSG +class s_xl_can_msg(ctypes.Structure): + _fields_ = [ + ("id", ctypes.c_ulong), + ("flags", ctypes.c_ushort), + ("dlc", ctypes.c_ushort), + ("res1", XLuint64), + ("data", ctypes.c_ubyte * xldefine.MAX_MSG_LEN), + ("res2", XLuint64), + ] + + +class s_xl_can_ev_error(ctypes.Structure): + _fields_ = [("errorCode", ctypes.c_ubyte), ("reserved", ctypes.c_ubyte * 95)] + + +class s_xl_can_ev_chip_state(ctypes.Structure): + _fields_ = [ + ("busStatus", ctypes.c_ubyte), + ("txErrorCounter", ctypes.c_ubyte), + ("rxErrorCounter", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte), + ("reserved0", ctypes.c_uint), + ] + + +class s_xl_can_ev_sync_pulse(ctypes.Structure): + _fields_ = [ + ("triggerSource", ctypes.c_uint), + ("reserved", ctypes.c_uint), + ("time", XLuint64), + ] + + +# BASIC bus message structure +class s_xl_tag_data(ctypes.Union): + _fields_ = [("msg", s_xl_can_msg)] + + +# CAN FD messages +class s_xl_can_ev_rx_msg(ctypes.Structure): + _fields_ = [ + ("canId", ctypes.c_uint), + ("msgFlags", ctypes.c_uint), + ("crc", ctypes.c_uint), + ("reserved1", ctypes.c_ubyte * 12), + ("totalBitCnt", ctypes.c_ushort), + ("dlc", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte * 5), + ("data", ctypes.c_ubyte * xldefine.XL_CAN_MAX_DATA_LEN), + ] + + +class s_xl_can_ev_tx_request(ctypes.Structure): + _fields_ = [ + ("canId", ctypes.c_uint), + ("msgFlags", ctypes.c_uint), + ("dlc", ctypes.c_ubyte), + ("txAttemptConf", ctypes.c_ubyte), + ("reserved", ctypes.c_ushort), + ("data", ctypes.c_ubyte * xldefine.XL_CAN_MAX_DATA_LEN), + ] + + +class s_xl_can_tx_msg(ctypes.Structure): + _fields_ = [ + ("canId", ctypes.c_uint), + ("msgFlags", ctypes.c_uint), + ("dlc", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte * 7), + ("data", ctypes.c_ubyte * xldefine.XL_CAN_MAX_DATA_LEN), + ] + + +class s_rxTagData(ctypes.Union): + _fields_ = [ + ("canRxOkMsg", s_xl_can_ev_rx_msg), + ("canTxOkMsg", s_xl_can_ev_rx_msg), + ("canTxRequest", s_xl_can_ev_tx_request), + ("canError", s_xl_can_ev_error), + ("canChipState", s_xl_can_ev_chip_state), + ("canSyncPulse", s_xl_can_ev_sync_pulse), + ] + + +class s_txTagData(ctypes.Union): + _fields_ = [("canMsg", s_xl_can_tx_msg)] + + +class XLevent(ctypes.Structure): + _fields_ = [ + ("tag", XLeventTag), + ("chanIndex", ctypes.c_ubyte), + ("transId", ctypes.c_ushort), + ("portHandle", ctypes.c_ushort), + ("flags", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte), + ("timeStamp", XLuint64), + ("tagData", s_xl_tag_data), + ] + + +# CAN FD events +class XLcanRxEvent(ctypes.Structure): + _fields_ = [ + ("size", ctypes.c_int), + ("tag", ctypes.c_ushort), + ("chanIndex", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte), + ("userHandle", ctypes.c_int), + ("flagsChip", ctypes.c_ushort), + ("reserved0", ctypes.c_ushort), + ("reserved1", XLuint64), + ("timeStamp", XLuint64), + ("tagData", s_rxTagData), + ] + + +class XLcanTxEvent(ctypes.Structure): + _fields_ = [ + ("tag", ctypes.c_ushort), + ("transId", ctypes.c_ushort), + ("chanIndex", ctypes.c_ubyte), + ("reserved", ctypes.c_ubyte * 3), + ("tagData", s_txTagData), + ] + + +# CAN configuration structure +class XLchipParams(ctypes.Structure): + _fields_ = [ + ("bitRate", ctypes.c_ulong), + ("sjw", ctypes.c_ubyte), + ("tseg1", ctypes.c_ubyte), + ("tseg2", ctypes.c_ubyte), + ("sam", ctypes.c_ubyte), + ] + + +# CAN FD configuration structure +class XLcanFdConf(ctypes.Structure): + _fields_ = [ + ("arbitrationBitRate", ctypes.c_uint), + ("sjwAbr", ctypes.c_uint), + ("tseg1Abr", ctypes.c_uint), + ("tseg2Abr", ctypes.c_uint), + ("dataBitRate", ctypes.c_uint), + ("sjwDbr", ctypes.c_uint), + ("tseg1Dbr", ctypes.c_uint), + ("tseg2Dbr", ctypes.c_uint), + ("reserved", ctypes.c_uint * 2), + ] + + +class XLchannelConfig(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("name", ctypes.c_char * 32), + ("hwType", ctypes.c_ubyte), + ("hwIndex", ctypes.c_ubyte), + ("hwChannel", ctypes.c_ubyte), + ("transceiverType", ctypes.c_ushort), + ("transceiverState", ctypes.c_ushort), + ("configError", ctypes.c_ushort), + ("channelIndex", ctypes.c_ubyte), + ("channelMask", XLuint64), + ("channelCapabilities", ctypes.c_uint), + ("channelBusCapabilities", ctypes.c_uint), + ("isOnBus", ctypes.c_ubyte), + ("connectedBusType", ctypes.c_uint), + ("busParams", ctypes.c_ubyte * 32), + ("_doNotUse", ctypes.c_uint), + ("driverVersion", ctypes.c_uint), + ("interfaceVersion", ctypes.c_uint), + ("raw_data", ctypes.c_uint * 10), + ("serialNumber", ctypes.c_uint), + ("articleNumber", ctypes.c_uint), + ("transceiverName", ctypes.c_char * 32), + ("specialCabFlags", ctypes.c_uint), + ("dominantTimeout", ctypes.c_uint), + ("dominantRecessiveDelay", ctypes.c_ubyte), + ("recessiveDominantDelay", ctypes.c_ubyte), + ("connectionInfo", ctypes.c_ubyte), + ("currentlyAvailableTimestamps", ctypes.c_ubyte), + ("minimalSupplyVoltage", ctypes.c_ushort), + ("maximalSupplyVoltage", ctypes.c_ushort), + ("maximalBaudrate", ctypes.c_uint), + ("fpgaCoreCapabilities", ctypes.c_ubyte), + ("specialDeviceStatus", ctypes.c_ubyte), + ("channelBusActiveCapabilities", ctypes.c_ushort), + ("breakOffset", ctypes.c_ushort), + ("delimiterOffset", ctypes.c_ushort), + ("reserved", ctypes.c_uint * 3), + ] + + +class XLdriverConfig(ctypes.Structure): + _fields_ = [ + ("dllVersion", ctypes.c_uint), + ("channelCount", ctypes.c_uint), + ("reserved", ctypes.c_uint * 10), + ("channel", XLchannelConfig * 64), + ] diff --git a/can/interfaces/vector/xldefine.py b/can/interfaces/vector/xldefine.py new file mode 100644 index 000000000..fcf041683 --- /dev/null +++ b/can/interfaces/vector/xldefine.py @@ -0,0 +1,172 @@ +# coding: utf-8 + +""" +Definition of constants for vxlapi. +""" + +# Import Python Modules +# ============================== +from enum import Enum + + +MAX_MSG_LEN = 8 +XL_CAN_MAX_DATA_LEN = 64 +XL_INVALID_PORTHANDLE = -1 + + +class XL_AC_Flags(Enum): + XL_ACTIVATE_NONE = 0 + XL_ACTIVATE_RESET_CLOCK = 8 + + +class XL_AcceptanceFilter(Enum): + XL_CAN_STD = 1 + XL_CAN_EXT = 2 + + +class XL_BusCapabilities(Enum): + XL_BUS_COMPATIBLE_CAN = 1 + XL_BUS_ACTIVE_CAP_CAN = 65536 + + +class XL_BusStatus(Enum): + XL_CHIPSTAT_BUSOFF = 1 + XL_CHIPSTAT_ERROR_PASSIVE = 2 + XL_CHIPSTAT_ERROR_WARNING = 4 + XL_CHIPSTAT_ERROR_ACTIVE = 8 + + +class XL_BusTypes(Enum): + XL_BUS_TYPE_NONE = 0 + XL_BUS_TYPE_CAN = 1 + + +class XL_CANFD_BusParams_CanOpMode(Enum): + XL_BUS_PARAMS_CANOPMODE_CAN20 = 1 + XL_BUS_PARAMS_CANOPMODE_CANFD = 2 + XL_BUS_PARAMS_CANOPMODE_CANFD_NO_ISO = 8 + + +class XL_CANFD_ConfigOptions(Enum): + CANFD_CONFOPT_NO_ISO = 8 + + +class XL_CANFD_RX_EV_ERROR_errorCode(Enum): + XL_CAN_ERRC_BIT_ERROR = 1 + XL_CAN_ERRC_FORM_ERROR = 2 + XL_CAN_ERRC_STUFF_ERROR = 3 + XL_CAN_ERRC_OTHER_ERROR = 4 + XL_CAN_ERRC_CRC_ERROR = 5 + XL_CAN_ERRC_ACK_ERROR = 6 + XL_CAN_ERRC_NACK_ERROR = 7 + XL_CAN_ERRC_OVLD_ERROR = 8 + XL_CAN_ERRC_EXCPT_ERROR = 9 + + +class XL_CANFD_RX_EventTags(Enum): + XL_SYNC_PULSE = 11 + XL_CAN_EV_TAG_RX_OK = 1024 + XL_CAN_EV_TAG_RX_ERROR = 1025 + XL_CAN_EV_TAG_TX_ERROR = 1026 + XL_CAN_EV_TAG_TX_REQUEST = 1027 + XL_CAN_EV_TAG_TX_OK = 1028 + XL_CAN_EV_TAG_CHIP_STATE = 1033 + + +class XL_CANFD_RX_MessageFlags(Enum): + XL_CAN_RXMSG_FLAG_NONE = 0 + XL_CAN_RXMSG_FLAG_EDL = 1 + XL_CAN_RXMSG_FLAG_BRS = 2 + XL_CAN_RXMSG_FLAG_ESI = 4 + XL_CAN_RXMSG_FLAG_RTR = 16 + XL_CAN_RXMSG_FLAG_EF = 512 + XL_CAN_RXMSG_FLAG_ARB_LOST = 1024 + XL_CAN_RXMSG_FLAG_WAKEUP = 8192 + XL_CAN_RXMSG_FLAG_TE = 16384 + + +class XL_CANFD_TX_EventTags(Enum): + XL_CAN_EV_TAG_TX_MSG = 1088 + + +class XL_CANFD_TX_MessageFlags(Enum): + XL_CAN_TXMSG_FLAG_NONE = 0 + XL_CAN_TXMSG_FLAG_EDL = 1 + XL_CAN_TXMSG_FLAG_BRS = 2 + XL_CAN_TXMSG_FLAG_RTR = 16 + XL_CAN_TXMSG_FLAG_HIGHPRIO = 128 + XL_CAN_TXMSG_FLAG_WAKEUP = 512 + + +class XL_ChannelCapabilities(Enum): + XL_CHANNEL_FLAG_TIME_SYNC_RUNNING = 1 + XL_CHANNEL_FLAG_NO_HWSYNC_SUPPORT = 1024 + XL_CHANNEL_FLAG_SPDIF_CAPABLE = 16384 + XL_CHANNEL_FLAG_CANFD_BOSCH_SUPPORT = 536870912 + XL_CHANNEL_FLAG_CMACTLICENSE_SUPPORT = 1073741824 + XL_CHANNEL_FLAG_CANFD_ISO_SUPPORT = 2147483648 + + +class XL_EventTags(Enum): + XL_NO_COMMAND = 0 + XL_RECEIVE_MSG = 1 + XL_CHIP_STATE = 4 + XL_TRANSCEIVER = 6 + XL_TIMER = 8 + XL_TRANSMIT_MSG = 10 + XL_SYNC_PULSE = 11 + XL_APPLICATION_NOTIFICATION = 15 + + +class XL_InterfaceVersion(Enum): + XL_INTERFACE_VERSION_V2 = 2 + XL_INTERFACE_VERSION_V3 = 3 + XL_INTERFACE_VERSION = XL_INTERFACE_VERSION_V3 + XL_INTERFACE_VERSION_V4 = 4 + + +class XL_MessageFlags(Enum): + XL_CAN_MSG_FLAG_NONE = 0 + XL_CAN_MSG_FLAG_ERROR_FRAME = 1 + XL_CAN_MSG_FLAG_OVERRUN = 2 + XL_CAN_MSG_FLAG_NERR = 4 + XL_CAN_MSG_FLAG_WAKEUP = 8 + XL_CAN_MSG_FLAG_REMOTE_FRAME = 16 + XL_CAN_MSG_FLAG_RESERVED_1 = 32 + XL_CAN_MSG_FLAG_TX_COMPLETED = 64 + XL_CAN_MSG_FLAG_TX_REQUEST = 128 + XL_CAN_MSG_FLAG_SRR_BIT_DOM = 512 + XL_EVENT_FLAG_OVERRUN = 1 + + +class XL_MessageFlagsExtended(Enum): + XL_CAN_EXT_MSG_ID = 2147483648 + + +class XL_OutputMode(Enum): + XL_OUTPUT_MODE_SILENT = 0 + XL_OUTPUT_MODE_NORMAL = 1 + XL_OUTPUT_MODE_TX_OFF = 2 + XL_OUTPUT_MODE_SJA_1000_SILENT = 3 + + +class XL_Sizes(Enum): + XL_MAX_LENGTH = 31 + XL_MAX_APPNAME = 32 + XL_MAX_NAME_LENGTH = 48 + XLEVENT_SIZE = 48 + XL_CONFIG_MAX_CHANNELS = 64 + XL_APPLCONFIG_MAX_CHANNELS = 256 + + +class XL_Status(Enum): + XL_SUCCESS = 0 + XL_PENDING = 1 + XL_ERR_QUEUE_IS_EMPTY = 10 + XL_ERR_HW_NOT_PRESENT = 129 + + +class XL_TimeSyncNewValue(Enum): + XL_SET_TIMESYNC_NO_CHANGE = 0 + XL_SET_TIMESYNC_ON = 1 + XL_SET_TIMESYNC_OFF = 2 diff --git a/can/interfaces/vector/xldriver.py b/can/interfaces/vector/xldriver.py new file mode 100644 index 000000000..b413e47fb --- /dev/null +++ b/can/interfaces/vector/xldriver.py @@ -0,0 +1,224 @@ +# coding: utf-8 + +""" +Ctypes wrapper module for Vector CAN Interface on win32/win64 systems. + +Authors: Julien Grave , Christian Sandberg +""" + +# Import Standard Python Modules +# ============================== +import ctypes +import logging +import platform +from .exceptions import VectorError + +# Define Module Logger +# ==================== +LOG = logging.getLogger(__name__) + +# Vector XL API Definitions +# ========================= +from . import xlclass + +# Load Windows DLL +DLL_NAME = "vxlapi64" if platform.architecture()[0] == "64bit" else "vxlapi" +_xlapi_dll = ctypes.windll.LoadLibrary(DLL_NAME) + + +# ctypes wrapping for API functions +xlGetErrorString = _xlapi_dll.xlGetErrorString +xlGetErrorString.argtypes = [xlclass.XLstatus] +xlGetErrorString.restype = ctypes.c_char_p + + +def check_status(result, function, arguments): + if result > 0: + raise VectorError(result, xlGetErrorString(result).decode(), function.__name__) + return result + + +xlGetDriverConfig = _xlapi_dll.xlGetDriverConfig +xlGetDriverConfig.argtypes = [ctypes.POINTER(xlclass.XLdriverConfig)] +xlGetDriverConfig.restype = xlclass.XLstatus +xlGetDriverConfig.errcheck = check_status + +xlOpenDriver = _xlapi_dll.xlOpenDriver +xlOpenDriver.argtypes = [] +xlOpenDriver.restype = xlclass.XLstatus +xlOpenDriver.errcheck = check_status + +xlCloseDriver = _xlapi_dll.xlCloseDriver +xlCloseDriver.argtypes = [] +xlCloseDriver.restype = xlclass.XLstatus +xlCloseDriver.errcheck = check_status + +xlGetApplConfig = _xlapi_dll.xlGetApplConfig +xlGetApplConfig.argtypes = [ + ctypes.c_char_p, + ctypes.c_uint, + ctypes.POINTER(ctypes.c_uint), + ctypes.POINTER(ctypes.c_uint), + ctypes.POINTER(ctypes.c_uint), + ctypes.c_uint, +] +xlGetApplConfig.restype = xlclass.XLstatus +xlGetApplConfig.errcheck = check_status + +xlGetChannelIndex = _xlapi_dll.xlGetChannelIndex +xlGetChannelIndex.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] +xlGetChannelIndex.restype = ctypes.c_int + +xlGetChannelMask = _xlapi_dll.xlGetChannelMask +xlGetChannelMask.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] +xlGetChannelMask.restype = xlclass.XLaccess + +xlOpenPort = _xlapi_dll.xlOpenPort +xlOpenPort.argtypes = [ + ctypes.POINTER(xlclass.XLportHandle), + ctypes.c_char_p, + xlclass.XLaccess, + ctypes.POINTER(xlclass.XLaccess), + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, +] +xlOpenPort.restype = xlclass.XLstatus +xlOpenPort.errcheck = check_status + +xlGetSyncTime = _xlapi_dll.xlGetSyncTime +xlGetSyncTime.argtypes = [xlclass.XLportHandle, ctypes.POINTER(xlclass.XLuint64)] +xlGetSyncTime.restype = xlclass.XLstatus +xlGetSyncTime.errcheck = check_status + +xlClosePort = _xlapi_dll.xlClosePort +xlClosePort.argtypes = [xlclass.XLportHandle] +xlClosePort.restype = xlclass.XLstatus +xlClosePort.errcheck = check_status + +xlSetNotification = _xlapi_dll.xlSetNotification +xlSetNotification.argtypes = [ + xlclass.XLportHandle, + ctypes.POINTER(xlclass.XLhandle), + ctypes.c_int, +] +xlSetNotification.restype = xlclass.XLstatus +xlSetNotification.errcheck = check_status + +xlCanSetChannelMode = _xlapi_dll.xlCanSetChannelMode +xlCanSetChannelMode.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.c_int, + ctypes.c_int, +] +xlCanSetChannelMode.restype = xlclass.XLstatus +xlCanSetChannelMode.errcheck = check_status + +xlActivateChannel = _xlapi_dll.xlActivateChannel +xlActivateChannel.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.c_uint, + ctypes.c_uint, +] +xlActivateChannel.restype = xlclass.XLstatus +xlActivateChannel.errcheck = check_status + +xlDeactivateChannel = _xlapi_dll.xlDeactivateChannel +xlDeactivateChannel.argtypes = [xlclass.XLportHandle, xlclass.XLaccess] +xlDeactivateChannel.restype = xlclass.XLstatus +xlDeactivateChannel.errcheck = check_status + +xlCanFdSetConfiguration = _xlapi_dll.xlCanFdSetConfiguration +xlCanFdSetConfiguration.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.POINTER(xlclass.XLcanFdConf), +] +xlCanFdSetConfiguration.restype = xlclass.XLstatus +xlCanFdSetConfiguration.errcheck = check_status + +xlReceive = _xlapi_dll.xlReceive +xlReceive.argtypes = [ + xlclass.XLportHandle, + ctypes.POINTER(ctypes.c_uint), + ctypes.POINTER(xlclass.XLevent), +] +xlReceive.restype = xlclass.XLstatus +xlReceive.errcheck = check_status + +xlCanReceive = _xlapi_dll.xlCanReceive +xlCanReceive.argtypes = [xlclass.XLportHandle, ctypes.POINTER(xlclass.XLcanRxEvent)] +xlCanReceive.restype = xlclass.XLstatus +xlCanReceive.errcheck = check_status + +xlCanSetChannelBitrate = _xlapi_dll.xlCanSetChannelBitrate +xlCanSetChannelBitrate.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.c_ulong, +] +xlCanSetChannelBitrate.restype = xlclass.XLstatus +xlCanSetChannelBitrate.errcheck = check_status + +xlCanSetChannelParams = _xlapi_dll.xlCanSetChannelParams +xlCanSetChannelParams.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.POINTER(xlclass.XLchipParams), +] +xlCanSetChannelParams.restype = xlclass.XLstatus +xlCanSetChannelParams.errcheck = check_status + +xlCanTransmit = _xlapi_dll.xlCanTransmit +xlCanTransmit.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.POINTER(ctypes.c_uint), + ctypes.POINTER(xlclass.XLevent), +] +xlCanTransmit.restype = xlclass.XLstatus +xlCanTransmit.errcheck = check_status + +xlCanTransmitEx = _xlapi_dll.xlCanTransmitEx +xlCanTransmitEx.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.c_uint, + ctypes.POINTER(ctypes.c_uint), + ctypes.POINTER(xlclass.XLcanTxEvent), +] +xlCanTransmitEx.restype = xlclass.XLstatus +xlCanTransmitEx.errcheck = check_status + +xlCanFlushTransmitQueue = _xlapi_dll.xlCanFlushTransmitQueue +xlCanFlushTransmitQueue.argtypes = [xlclass.XLportHandle, xlclass.XLaccess] +xlCanFlushTransmitQueue.restype = xlclass.XLstatus +xlCanFlushTransmitQueue.errcheck = check_status + +xlCanSetChannelAcceptance = _xlapi_dll.xlCanSetChannelAcceptance +xlCanSetChannelAcceptance.argtypes = [ + xlclass.XLportHandle, + xlclass.XLaccess, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_uint, +] +xlCanSetChannelAcceptance.restype = xlclass.XLstatus +xlCanSetChannelAcceptance.errcheck = check_status + +xlCanResetAcceptance = _xlapi_dll.xlCanResetAcceptance +xlCanResetAcceptance.argtypes = [xlclass.XLportHandle, xlclass.XLaccess, ctypes.c_uint] +xlCanResetAcceptance.restype = xlclass.XLstatus +xlCanResetAcceptance.errcheck = check_status + +xlCanRequestChipState = _xlapi_dll.xlCanRequestChipState +xlCanRequestChipState.argtypes = [xlclass.XLportHandle, xlclass.XLaccess] +xlCanRequestChipState.restype = xlclass.XLstatus +xlCanRequestChipState.errcheck = check_status + +xlCanSetChannelOutput = _xlapi_dll.xlCanSetChannelOutput +xlCanSetChannelOutput.argtypes = [xlclass.XLportHandle, xlclass.XLaccess, ctypes.c_char] +xlCanSetChannelOutput.restype = xlclass.XLstatus +xlCanSetChannelOutput.errcheck = check_status diff --git a/can/io/asc.py b/can/io/asc.py index 2e25e5c2c..656ac4fbe 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -56,7 +56,6 @@ def __iter__(self): temp = line.strip() if not temp or not temp[0].isdigit(): continue - try: timestamp, channel, dummy = temp.split( None, 2 @@ -64,24 +63,20 @@ def __iter__(self): except ValueError: # we parsed an empty comment continue - timestamp = float(timestamp) try: # See ASCWriter channel = int(channel) - 1 except ValueError: pass - if dummy.strip()[0:10].lower() == "errorframe": msg = Message(timestamp=timestamp, is_error_frame=True, channel=channel) yield msg - elif ( not isinstance(channel, int) or dummy.strip()[0:10].lower() == "statistic:" ): pass - elif dummy[-1:].lower() == "r": can_id_str, _ = dummy.split(None, 1) can_id_num, is_extended_id = self._extract_can_id(can_id_str) @@ -93,7 +88,6 @@ def __iter__(self): channel=channel, ) yield msg - else: try: # this only works if dlc > 0 and thus data is availabe @@ -103,13 +97,11 @@ def __iter__(self): can_id_str, _, _, dlc = dummy.split(None, 3) # and we set data to an empty sequence manually data = "" - dlc = int(dlc) frame = bytearray() data = data.split() for byte in data[0:dlc]: frame.append(int(byte, 16)) - can_id_num, is_extended_id = self._extract_can_id(can_id_str) yield Message( @@ -121,7 +113,6 @@ def __iter__(self): data=frame, channel=channel, ) - self.stop() @@ -135,6 +126,27 @@ class ASCWriter(BaseIOHandler, Listener): """ FORMAT_MESSAGE = "{channel} {id:<15} Rx {dtype} {data}" + FORMAT_MESSAGE_FD = " ".join( + [ + "CANFD", + "{channel:>3}", + "{dir:<4}", + "{id:>8} {symbolic_name:>32}", + "{brs}", + "{esi}", + "{dlc}", + "{data_length:>2}", + "{data}", + "{message_duration:>8}", + "{message_length:>4}", + "{flags:>8X}", + "{crc:>8}", + "{bit_timing_conf_arb:>8}", + "{bit_timing_conf_data:>8}", + "{bit_timing_conf_ext_arb:>8}", + "{bit_timing_conf_ext_data:>8}", + ] + ) FORMAT_DATE = "%a %b %m %I:%M:%S.{} %p %Y" FORMAT_EVENT = "{timestamp: 9.6f} {message}\n" @@ -175,7 +187,6 @@ def log_event(self, message, timestamp=None): if not message: # if empty or None logger.debug("ASCWriter: ignoring empty message") return - # this is the case for the very first message: if not self.header_written: self.last_timestamp = timestamp or 0.0 @@ -187,15 +198,12 @@ def log_event(self, message, timestamp=None): self.file.write("Begin Triggerblock %s\n" % formatted_date) self.header_written = True self.log_event("Start of measurement") # caution: this is a recursive call! - # Use last known timestamp if unknown if timestamp is None: timestamp = self.last_timestamp - # turn into relative timestamps if necessary if timestamp >= self.started: timestamp -= self.started - line = self.FORMAT_EVENT.format(timestamp=timestamp, message=message) self.file.write(line) @@ -204,27 +212,49 @@ def on_message_received(self, msg): if msg.is_error_frame: self.log_event("{} ErrorFrame".format(self.channel), msg.timestamp) return - if msg.is_remote_frame: dtype = "r" data = [] else: dtype = "d {}".format(msg.dlc) data = ["{:02X}".format(byte) for byte in msg.data] - arb_id = "{:X}".format(msg.arbitration_id) if msg.is_extended_id: arb_id += "x" - channel = channel2int(msg.channel) if channel is None: channel = self.channel else: # Many interfaces start channel numbering at 0 which is invalid channel += 1 - - serialized = self.FORMAT_MESSAGE.format( - channel=channel, id=arb_id, dtype=dtype, data=" ".join(data) - ) - + if msg.is_fd: + flags = 0 + flags |= 1 << 12 + if msg.bitrate_switch: + flags |= 1 << 13 + if msg.error_state_indicator: + flags |= 1 << 14 + serialized = self.FORMAT_MESSAGE_FD.format( + channel=channel, + id=arb_id, + dir="Rx", + symbolic_name="", + brs=1 if msg.bitrate_switch else 0, + esi=1 if msg.error_state_indicator else 0, + dlc=msg.dlc, + data_length=len(data), + data=" ".join(data), + message_duration=0, + message_length=0, + flags=flags, + crc=0, + bit_timing_conf_arb=0, + bit_timing_conf_data=0, + bit_timing_conf_ext_arb=0, + bit_timing_conf_ext_data=0, + ) + else: + serialized = self.FORMAT_MESSAGE.format( + channel=channel, id=arb_id, dtype=dtype, data=" ".join(data) + ) self.log_event(serialized, msg.timestamp) diff --git a/can/io/generic.py b/can/io/generic.py index 62bae18d4..c64051b75 100644 --- a/can/io/generic.py +++ b/can/io/generic.py @@ -5,6 +5,10 @@ """ from abc import ABCMeta +from typing import Optional, cast + +import can +import can.typechecking class BaseIOHandler(metaclass=ABCMeta): @@ -12,35 +16,46 @@ class BaseIOHandler(metaclass=ABCMeta): Can be used as a context manager. - :attr file-like file: + :attr Optional[FileLike] file: the file-like object that is kept internally, or None if none was opened """ - def __init__(self, file, mode="rt"): + def __init__(self, file: can.typechecking.AcceptedIOType, mode: str = "rt") -> None: """ :param file: a path-like object to open a file, a file-like object to be used as a file or `None` to not use a file at all - :param str mode: the mode that should be used to open the file, see - :func:`open`, ignored if *file* is `None` + :param mode: the mode that should be used to open the file, see + :func:`open`, ignored if *file* is `None` """ if file is None or (hasattr(file, "read") and hasattr(file, "write")): # file is None or some file-like object - self.file = file + self.file = cast(Optional[can.typechecking.FileLike], file) else: # file is some path-like object - self.file = open(file, mode) + self.file = open(cast(can.typechecking.StringPathLike, file), mode) # for multiple inheritance super().__init__() - def __enter__(self): + def __enter__(self) -> "BaseIOHandler": return self - def __exit__(self, *args): + def __exit__(self, *args) -> None: self.stop() - def stop(self): + def stop(self) -> None: + """Closes the undelying file-like object and flushes it, if it was opened in write mode.""" if self.file is not None: # this also implies a flush() self.file.close() + + +# pylint: disable=abstract-method,too-few-public-methods +class MessageWriter(BaseIOHandler, can.Listener, metaclass=ABCMeta): + """The base class for all writers.""" + + +# pylint: disable=too-few-public-methods +class MessageReader(BaseIOHandler, metaclass=ABCMeta): + """The base class for all readers.""" diff --git a/can/io/logger.py b/can/io/logger.py index edffe1c78..bc9d2c5f1 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -4,7 +4,10 @@ See the :class:`Logger` class. """ -import logging +import pathlib +import typing + +import can.typechecking from ..listener import Listener from .generic import BaseIOHandler @@ -15,8 +18,6 @@ from .sqlite import SqliteWriter from .printer import Printer -log = logging.getLogger("can.io.logger") - class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method """ @@ -28,36 +29,42 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method * .csv: :class:`can.CSVWriter` * .db: :class:`can.SqliteWriter` * .log :class:`can.CanutilsLogWriter` - * other: :class:`can.Printer` + * .txt :class:`can.Printer` + + The **filename** may also be *None*, to fall back to :class:`can.Printer`. The log files may be incomplete until `stop()` is called due to buffering. .. note:: - This class itself is just a dispatcher, and any positional an keyword + This class itself is just a dispatcher, and any positional and keyword arguments are passed on to the returned instance. """ @staticmethod - def __new__(cls, filename, *args, **kwargs): + def __new__( + cls, filename: typing.Optional[can.typechecking.StringPathLike], *args, **kwargs + ): """ - :type filename: str or None or path-like - :param filename: the filename/path the file to write to, - may be a path-like object if the target logger supports - it, and may be None to instantiate a :class:`~can.Printer` - + :param filename: the filename/path of the file to write to, + may be a path-like object or None to + instantiate a :class:`~can.Printer` + :raises ValueError: if the filename's suffix is of an unknown file type """ - if filename: - if filename.endswith(".asc"): - return ASCWriter(filename, *args, **kwargs) - elif filename.endswith(".blf"): - return BLFWriter(filename, *args, **kwargs) - elif filename.endswith(".csv"): - return CSVWriter(filename, *args, **kwargs) - elif filename.endswith(".db"): - return SqliteWriter(filename, *args, **kwargs) - elif filename.endswith(".log"): - return CanutilsLogWriter(filename, *args, **kwargs) - - # else: - log.warning('unknown file type "%s", falling pack to can.Printer', filename) - return Printer(filename, *args, **kwargs) + if filename is None: + return Printer(*args, **kwargs) + + lookup = { + ".asc": ASCWriter, + ".blf": BLFWriter, + ".csv": CSVWriter, + ".db": SqliteWriter, + ".log": CanutilsLogWriter, + ".txt": Printer, + } + suffix = pathlib.PurePath(filename).suffix + try: + return lookup[suffix](filename, *args, **kwargs) + except KeyError: + raise ValueError( + f'No write support for this unknown log format "{suffix}"' + ) from None diff --git a/can/io/player.py b/can/io/player.py index 3455fe97a..a4089fc32 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -6,8 +6,12 @@ in the recorded order an time intervals. """ +import pathlib from time import time, sleep -import logging +import typing + +if typing.TYPE_CHECKING: + import can from .generic import BaseIOHandler from .asc import ASCReader @@ -16,8 +20,6 @@ from .csv import CSVReader from .sqlite import SqliteReader -log = logging.getLogger("can.io.player") - class LogReader(BaseIOHandler): """ @@ -45,45 +47,55 @@ class LogReader(BaseIOHandler): """ @staticmethod - def __new__(cls, filename, *args, **kwargs): + def __new__(cls, filename: "can.typechecking.PathLike", *args, **kwargs): """ - :param str filename: the filename/path the file to read from + :param filename: the filename/path of the file to read from + :raises ValueError: if the filename's suffix is of an unknown file type """ - if filename.endswith(".asc"): - return ASCReader(filename, *args, **kwargs) - elif filename.endswith(".blf"): - return BLFReader(filename, *args, **kwargs) - elif filename.endswith(".csv"): - return CSVReader(filename, *args, **kwargs) - elif filename.endswith(".db"): - return SqliteReader(filename, *args, **kwargs) - elif filename.endswith(".log"): - return CanutilsLogReader(filename, *args, **kwargs) - else: - raise NotImplementedError( - "No read support for this log format: {}".format(filename) - ) - - -class MessageSync: + suffix = pathlib.PurePath(filename).suffix + + lookup = { + ".asc": ASCReader, + ".blf": BLFReader, + ".csv": CSVReader, + ".db": SqliteReader, + ".log": CanutilsLogReader, + } + suffix = pathlib.PurePath(filename).suffix + try: + return lookup[suffix](filename, *args, **kwargs) + except KeyError: + raise ValueError( + f'No read support for this unknown log format "{suffix}"' + ) from None + + +class MessageSync: # pylint: disable=too-few-public-methods """ Used to iterate over some given messages in the recorded time. """ - def __init__(self, messages, timestamps=True, gap=0.0001, skip=60): + def __init__( + self, + messages: typing.Iterable["can.Message"], + timestamps: bool = True, + gap: float = 0.0001, + skip: float = 60.0, + ) -> None: """Creates an new **MessageSync** instance. - :param Iterable[can.Message] messages: An iterable of :class:`can.Message` instances. - :param bool timestamps: Use the messages' timestamps. If False, uses the *gap* parameter as the time between messages. - :param float gap: Minimum time between sent messages in seconds - :param float skip: Skip periods of inactivity greater than this (in seconds). + :param messages: An iterable of :class:`can.Message` instances. + :param timestamps: Use the messages' timestamps. If False, uses the *gap* parameter + as the time between messages. + :param gap: Minimum time between sent messages in seconds + :param skip: Skip periods of inactivity greater than this (in seconds). """ self.raw_messages = messages self.timestamps = timestamps self.gap = gap self.skip = skip - def __iter__(self): + def __iter__(self) -> typing.Generator["can.Message", None, None]: playback_start_time = time() recorded_start_time = None diff --git a/can/io/printer.py b/can/io/printer.py index f6a4b28e0..d363e6917 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -22,15 +22,18 @@ class Printer(BaseIOHandler, Listener): standard out """ - def __init__(self, file=None): + def __init__(self, file=None, append=False): """ :param file: an optional path-like object or as file-like object to "print" to instead of writing to standard out (stdout) If this is a file-like object, is has to opened in text write mode, not binary write mode. + :param bool append: if set to `True` messages are appended to + the file, else the file is truncated """ self.write_to_file = file is not None - super().__init__(file, mode="w") + mode = "a" if append else "w" + super().__init__(file, mode=mode) def on_message_received(self, msg): if self.write_to_file: diff --git a/can/listener.py b/can/listener.py index 2f773fcf0..c33fec70a 100644 --- a/can/listener.py +++ b/can/listener.py @@ -4,6 +4,11 @@ This module contains the implementation of `can.Listener` and some readers. """ +from typing import AsyncIterator, Awaitable, Optional + +from can.message import Message +from can.bus import BusABC + from abc import ABCMeta, abstractmethod try: @@ -11,7 +16,7 @@ from queue import SimpleQueue, Empty except ImportError: # Python 3.0 - 3.6 - from queue import Queue as SimpleQueue, Empty + from queue import Queue as SimpleQueue, Empty # type: ignore import asyncio @@ -33,20 +38,20 @@ class Listener(metaclass=ABCMeta): """ @abstractmethod - def on_message_received(self, msg): + def on_message_received(self, msg: Message): """This method is called to handle the given message. - :param can.Message msg: the delivered message + :param msg: the delivered message """ - def __call__(self, msg): - return self.on_message_received(msg) + def __call__(self, msg: Message): + self.on_message_received(msg) - def on_error(self, exc): + def on_error(self, exc: Exception): """This method is called to handle any exception in the receive thread. - :param Exception exc: The exception causing the thread to stop + :param exc: The exception causing the thread to stop """ def stop(self): @@ -64,10 +69,10 @@ class RedirectReader(Listener): """ - def __init__(self, bus): + def __init__(self, bus: BusABC): self.bus = bus - def on_message_received(self, msg): + def on_message_received(self, msg: Message): self.bus.send(msg) @@ -90,7 +95,7 @@ def __init__(self): self.buffer = SimpleQueue() self.is_stopped = False - def on_message_received(self, msg): + def on_message_received(self, msg: Message): """Append a message to the buffer. :raises: BufferError @@ -101,16 +106,15 @@ def on_message_received(self, msg): else: self.buffer.put(msg) - def get_message(self, timeout=0.5): + def get_message(self, timeout: float = 0.5) -> Optional[Message]: """ 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 float timeout: The number of seconds to wait for a new message. - :rytpe: can.Message or None - :return: the message if there is one, or None if there is not. + :param timeout: The number of seconds to wait for a new message. + :return: the Message if there is one, or None if there is not. """ try: return self.buffer.get(block=not self.is_stopped, timeout=timeout) @@ -134,30 +138,29 @@ class AsyncBufferedReader(Listener): print(msg) """ - def __init__(self, loop=None): + def __init__(self, loop: Optional[asyncio.events.AbstractEventLoop] = None): # set to "infinite" size - self.buffer = asyncio.Queue(loop=loop) + self.buffer: "asyncio.Queue[Message]" = asyncio.Queue(loop=loop) - def on_message_received(self, msg): + def on_message_received(self, msg: Message): """Append a message to the buffer. Must only be called inside an event loop! """ self.buffer.put_nowait(msg) - async def get_message(self): + async def get_message(self) -> Message: """ Retrieve the latest message when awaited for:: msg = await reader.get_message() - :rtype: can.Message :return: The CAN message. """ return await self.buffer.get() - def __aiter__(self): + def __aiter__(self) -> AsyncIterator[Message]: return self - def __anext__(self): + def __anext__(self) -> Awaitable[Message]: return self.buffer.get() diff --git a/can/message.py b/can/message.py index b3fd38139..d1c7c9366 100644 --- a/can/message.py +++ b/can/message.py @@ -8,6 +8,9 @@ starting with Python 3.7. """ +from typing import Optional, Union + +from . import typechecking from copy import deepcopy from math import isinf, isnan @@ -48,28 +51,28 @@ class Message: def __init__( self, - timestamp=0.0, - arbitration_id=0, - is_extended_id=True, - is_remote_frame=False, - is_error_frame=False, - channel=None, - dlc=None, - data=None, - is_fd=False, - bitrate_switch=False, - error_state_indicator=False, - check=False, + timestamp: float = 0.0, + arbitration_id: int = 0, + is_extended_id: bool = True, + is_remote_frame: bool = False, + is_error_frame: bool = False, + channel: Optional[typechecking.Channel] = None, + dlc: Optional[int] = None, + data: Optional[typechecking.CanData] = None, + is_fd: bool = False, + bitrate_switch: bool = False, + error_state_indicator: bool = False, + check: bool = False, ): """ To create a message object, simply provide any of the below attributes together with additional parameters as keyword arguments to the constructor. - :param bool check: By default, the constructor of this class does not strictly check the input. - Thus, the caller must prevent the creation of invalid messages or - set this parameter to `True`, to raise an Error on invalid inputs. - Possible problems include the `dlc` field not matching the length of `data` - or creating a message with both `is_remote_frame` and `is_error_frame` set to `True`. + :param check: By default, the constructor of this class does not strictly check the input. + Thus, the caller must prevent the creation of invalid messages or + set this parameter to `True`, to raise an Error on invalid inputs. + Possible problems include the `dlc` field not matching the length of `data` + or creating a message with both `is_remote_frame` and `is_error_frame` set to `True`. :raises ValueError: iff `check` is set to `True` and one or more arguments were invalid """ @@ -102,7 +105,7 @@ def __init__( if check: self._check() - def __str__(self): + def __str__(self) -> str: field_strings = ["Timestamp: {0:>15.6f}".format(self.timestamp)] if self.is_extended_id: arbitration_id_string = "ID: {0:08x}".format(self.arbitration_id) @@ -144,14 +147,14 @@ def __str__(self): return " ".join(field_strings).strip() - def __len__(self): + def __len__(self) -> int: # return the dlc such that it also works on remote frames return self.dlc - def __bool__(self): + def __bool__(self) -> bool: return True - def __repr__(self): + def __repr__(self) -> str: args = [ "timestamp={}".format(self.timestamp), "arbitration_id={:#x}".format(self.arbitration_id), @@ -177,16 +180,16 @@ def __repr__(self): return "can.Message({})".format(", ".join(args)) - def __format__(self, format_spec): + def __format__(self, format_spec: Optional[str]) -> str: if not format_spec: return self.__str__() else: raise ValueError("non empty format_specs are not supported") - def __bytes__(self): + def __bytes__(self) -> bytes: return bytes(self.data) - def __copy__(self): + def __copy__(self) -> "Message": new = Message( timestamp=self.timestamp, arbitration_id=self.arbitration_id, @@ -202,7 +205,7 @@ def __copy__(self): ) return new - def __deepcopy__(self, memo): + def __deepcopy__(self, memo: dict) -> "Message": new = Message( timestamp=self.timestamp, arbitration_id=self.arbitration_id, @@ -278,17 +281,17 @@ def _check(self): "error state indicator is only allowed for CAN FD frames" ) - def equals(self, other, timestamp_delta=1.0e-6): + def equals( + self, other: "Message", timestamp_delta: Optional[Union[float, int]] = 1.0e-6 + ) -> bool: """ Compares a given message with this one. - :param can.Message other: the message to compare with + :param other: the message to compare with - :type timestamp_delta: float or int or None :param timestamp_delta: the maximum difference at which two timestamps are still considered equal or None to not compare timestamps - :rtype: bool :return: True iff the given message equals this one """ # see https://github.com/hardbyte/python-can/pull/413 for a discussion diff --git a/can/notifier.py b/can/notifier.py index 5d0642ee6..679af384d 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -4,6 +4,12 @@ This module contains the implementation of :class:`~can.Notifier`. """ +from typing import Iterable, List, Optional, Union + +from can.bus import BusABC +from can.listener import Listener +from can.message import Message + import threading import logging import time @@ -13,7 +19,13 @@ class Notifier: - def __init__(self, bus, listeners, timeout=1.0, loop=None): + def __init__( + self, + bus: BusABC, + listeners: Iterable[Listener], + timeout: float = 1.0, + loop: Optional[asyncio.AbstractEventLoop] = None, + ): """Manages the distribution of :class:`can.Message` instances to listeners. Supports multiple buses and listeners. @@ -24,37 +36,40 @@ def __init__(self, bus, listeners, timeout=1.0, loop=None): many listeners carry out flush operations to persist data. - :param can.BusABC bus: A :ref:`bus` or a list of buses to listen to. - :param list listeners: An iterable of :class:`~can.Listener` - :param float timeout: An optional maximum number of seconds to wait for any message. - :param asyncio.AbstractEventLoop loop: - An :mod:`asyncio` event loop to schedule listeners in. + :param bus: A :ref:`bus` or a list of buses to listen to. + :param listeners: An iterable of :class:`~can.Listener` + :param timeout: An optional maximum number of seconds to wait for any message. + :param loop: An :mod:`asyncio` event loop to schedule listeners in. """ - self.listeners = listeners + self.listeners = list(listeners) self.bus = bus self.timeout = timeout self._loop = loop #: Exception raised in thread - self.exception = None + self.exception: Optional[Exception] = None self._running = True self._lock = threading.Lock() - self._readers = [] + self._readers: List[Union[int, threading.Thread]] = [] buses = self.bus if isinstance(self.bus, list) else [self.bus] for bus in buses: self.add_bus(bus) - def add_bus(self, bus): + def add_bus(self, bus: BusABC): """Add a bus for notification. - :param can.BusABC bus: + :param bus: CAN bus instance. """ - if self._loop is not None and hasattr(bus, "fileno") and bus.fileno() >= 0: + if ( + self._loop is not None + and hasattr(bus, "fileno") + and bus.fileno() >= 0 # type: ignore + ): # Use file descriptor to watch for messages - reader = bus.fileno() + reader = bus.fileno() # type: ignore self._loop.add_reader(reader, self._on_message_available, bus) else: reader = threading.Thread( @@ -66,11 +81,11 @@ def add_bus(self, bus): reader.start() self._readers.append(reader) - def stop(self, timeout=5): + def stop(self, timeout: float = 5): """Stop notifying Listeners when new :class:`~can.Message` objects arrive and call :meth:`~can.Listener.stop` on each Listener. - :param float timeout: + :param timeout: Max time in seconds to wait for receive threads to finish. Should be longer than timeout given at instantiation. """ @@ -81,14 +96,14 @@ def stop(self, timeout=5): now = time.time() if now < end_time: reader.join(end_time - now) - else: + elif self._loop: # reader is a file descriptor self._loop.remove_reader(reader) for listener in self.listeners: if hasattr(listener, "stop"): listener.stop() - def _rx_thread(self, bus): + def _rx_thread(self, bus: BusABC): msg = None try: while self._running: @@ -109,40 +124,38 @@ def _rx_thread(self, bus): self._on_error(exc) raise - def _on_message_available(self, bus): + def _on_message_available(self, bus: BusABC): msg = bus.recv(0) if msg is not None: self._on_message_received(msg) - def _on_message_received(self, msg): + def _on_message_received(self, msg: Message): for callback in self.listeners: res = callback(msg) if self._loop is not None and asyncio.iscoroutine(res): # Schedule coroutine self._loop.create_task(res) - def _on_error(self, exc): + def _on_error(self, exc: Exception): for listener in self.listeners: if hasattr(listener, "on_error"): listener.on_error(exc) - def add_listener(self, listener): + def add_listener(self, listener: Listener): """Add new Listener to the notification list. If it is already present, it will be called two times each time a message arrives. - :param can.Listener listener: Listener to be added to - the list to be notified + :param listener: Listener to be added to the list to be notified """ self.listeners.append(listener) - def remove_listener(self, listener): + def remove_listener(self, listener: Listener): """Remove a listener from the notification list. This method trows an exception if the given listener is not part of the stored listeners. - :param can.Listener listener: Listener to be removed from - the list to be notified + :param listener: Listener to be removed from the list to be notified :raises ValueError: if `listener` was never added to this notifier """ self.listeners.remove(listener) diff --git a/can/player.py b/can/player.py index 34be48670..b1e1c5612 100644 --- a/can/player.py +++ b/can/player.py @@ -73,6 +73,12 @@ def main(): action="store_false", ) + parser.add_argument( + "--error-frames", + help="Also send error frames to the interface.", + action="store_true", + ) + parser.add_argument( "-g", "--gap", @@ -111,6 +117,8 @@ def main(): ] can.set_logging_level(logging_level_name) + error_frames = results.error_frames + config = {"single_handle": True} if results.interface: config["interface"] = results.interface @@ -132,6 +140,8 @@ def main(): try: for m in in_sync: + if m.is_error_frame and not error_frames: + continue if verbosity >= 3: print(m) bus.send(m) diff --git a/can/thread_safe_bus.py b/can/thread_safe_bus.py index 91f7e6d2c..9f6346fb8 100644 --- a/can/thread_safe_bus.py +++ b/can/thread_safe_bus.py @@ -16,11 +16,11 @@ try: - from contextlib import nullcontext + from contextlib import nullcontext # type: ignore except ImportError: - class nullcontext: + class nullcontext: # type: ignore """A context manager that does nothing at all. A fallback for Python 3.7's :class:`contextlib.nullcontext` manager. """ diff --git a/can/typechecking.py b/can/typechecking.py new file mode 100644 index 000000000..b5b79d500 --- /dev/null +++ b/can/typechecking.py @@ -0,0 +1,29 @@ +"""Types for mypy type-checking +""" + +import typing + +if typing.TYPE_CHECKING: + import os + +import mypy_extensions + +CanFilter = mypy_extensions.TypedDict( + "CanFilter", {"can_id": int, "can_mask": int, "extended": bool} +) +CanFilters = typing.Iterable[CanFilter] + +# TODO: Once buffer protocol support lands in typing, we should switch to that, +# since can.message.Message attempts to call bytearray() on the given data, so +# this should have the same typing info. +# +# See: https://github.com/python/typing/issues/593 +CanData = typing.Union[bytes, bytearray, int, typing.Iterable[int]] + +# Used for the Abstract Base Class +Channel = typing.Union[int, str] + +# Used by the IO module +FileLike = typing.IO[typing.Any] +StringPathLike = typing.Union[str, "os.PathLike[str]"] +AcceptedIOType = typing.Optional[typing.Union[FileLike, StringPathLike]] diff --git a/doc/api.rst b/doc/api.rst index 1aef90e9a..193d1c707 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -11,7 +11,7 @@ A form of CAN interface is also required. .. toctree:: :maxdepth: 1 - + bus message listeners @@ -25,7 +25,7 @@ Utilities --------- -.. automethod:: can.detect_available_configs +.. autofunction:: can.detect_available_configs .. _notifier: diff --git a/doc/conf.py b/doc/conf.py index b95aa6557..62fd649b6 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -43,6 +43,7 @@ "sphinx.ext.viewcode", "sphinx.ext.graphviz", "sphinxcontrib.programoutput", + "sphinx_autodoc_typehints", ] # Now, you can use the alias name as a new role, e.g. :issue:`123`. diff --git a/doc/doc-requirements.txt b/doc/doc-requirements.txt index 45026701b..a63beee71 100644 --- a/doc/doc-requirements.txt +++ b/doc/doc-requirements.txt @@ -1,2 +1,3 @@ sphinx>=1.8.1 sphinxcontrib-programoutput +sphinx-autodoc-typehints==1.6.0 diff --git a/doc/installation.rst b/doc/installation.rst index 147b27b74..a70f7d5ea 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -78,6 +78,17 @@ neoVI See :doc:`interfaces/neovi`. +Vector +~~~~~~ + +To install ``python-can`` using the XL Driver Library as the backend: + +1. Install the `latest drivers `__ for your Vector hardware interface. + +2. Install the `XL Driver Library `__ or copy the ``vxlapi.dll`` and/or + ``vxlapi64.dll`` into your working directory. + +3. Use Vector Hardware Configuration to assign a channel to your application. Installing python-can in development mode ----------------------------------------- diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 7c8253f9e..a19dc7e84 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -25,6 +25,7 @@ The available interfaces are: interfaces/virtual interfaces/canalystii interfaces/systec + interfaces/seeedstudio 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. diff --git a/doc/interfaces/vector.rst b/doc/interfaces/vector.rst index a936e693e..dcd45f1bf 100644 --- a/doc/interfaces/vector.rst +++ b/doc/interfaces/vector.rst @@ -1,7 +1,7 @@ Vector ====== -This interface adds support for CAN controllers by `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 Config program and create diff --git a/doc/listeners.rst b/doc/listeners.rst index 975de6fd1..fcdc32f52 100644 --- a/doc/listeners.rst +++ b/doc/listeners.rst @@ -41,6 +41,13 @@ BufferedReader :members: +RedirectReader +-------------- + +.. autoclass:: can.RedirectReader + :members: + + Logger ------ diff --git a/requirements-lint.txt b/requirements-lint.txt index 6a81fe2eb..ce953e68b 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1,2 +1,4 @@ pylint==2.3.1 black==19.3b0 +mypy==0.720 +mypy-extensions==0.4.1 diff --git a/setup.py b/setup.py index ded73f458..2b1bf2296 100644 --- a/setup.py +++ b/setup.py @@ -103,6 +103,8 @@ "aenum", 'windows-curses;platform_system=="Windows"', "filelock", + "mypy_extensions >= 0.4.0, < 0.5.0", + 'pywin32;platform_system=="Windows"', ], setup_requires=pytest_runner, extras_require=extras_require, diff --git a/test/listener_test.py b/test/listener_test.py index c44acc7a6..00dad1b0a 100644 --- a/test/listener_test.py +++ b/test/listener_test.py @@ -4,12 +4,10 @@ """ """ -from time import sleep import unittest import random import logging import tempfile -import sqlite3 import os from os.path import join, dirname @@ -113,9 +111,11 @@ def test_filetype_to_instance(extension, klass): test_filetype_to_instance(".db", can.SqliteReader) test_filetype_to_instance(".log", can.CanutilsLogReader) - # test file extensions that are not supported - with self.assertRaisesRegex(NotImplementedError, ".xyz_42"): - test_filetype_to_instance(".xyz_42", can.Printer) + def testPlayerTypeResolutionUnsupportedFileTypes(self): + for should_fail_with in ["", ".", ".some_unknown_extention_42"]: + with self.assertRaises(ValueError): + with can.LogReader(should_fail_with): # make sure we close it anyways + pass def testLoggerTypeResolution(self): def test_filetype_to_instance(extension, klass): @@ -137,13 +137,15 @@ def test_filetype_to_instance(extension, klass): test_filetype_to_instance(".log", can.CanutilsLogWriter) test_filetype_to_instance(".txt", can.Printer) - # test file extensions that should use a fallback - test_filetype_to_instance("", can.Printer) - test_filetype_to_instance(".", can.Printer) - test_filetype_to_instance(".some_unknown_extention_42", can.Printer) with can.Logger(None) as logger: self.assertIsInstance(logger, can.Printer) + def testLoggerTypeResolutionUnsupportedFileTypes(self): + for should_fail_with in ["", ".", ".some_unknown_extention_42"]: + with self.assertRaises(ValueError): + with can.Logger(should_fail_with): # make sure we close it anyways + pass + def testBufferedListenerReceives(self): a_listener = can.BufferedReader() a_listener(generate_message(0xDADADA)) diff --git a/test/logformats_test.py b/test/logformats_test.py index 46eded869..9983b0ecb 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -459,7 +459,10 @@ def test_read_all(self): class TestPrinter(unittest.TestCase): - """Tests that can.Printer does not crash""" + """Tests that can.Printer does not crash + + TODO test append mode + """ # TODO add CAN FD messages messages = ( diff --git a/test/test_vector.py b/test/test_vector.py new file mode 100644 index 000000000..639b28de9 --- /dev/null +++ b/test/test_vector.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +Test for Vector Interface +""" + +import ctypes +import time +import logging +import os +import unittest +from unittest.mock import Mock + +import pytest + +import can +from can.interfaces.vector import canlib, xldefine, xlclass + + +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() + + # receiver functions + can.interfaces.vector.canlib.xldriver.xlReceive = Mock(side_effect=xlReceive) + can.interfaces.vector.canlib.xldriver.xlCanReceive = Mock( + side_effect=xlCanReceive + ) + + # 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, + sjwAbr=10, + tseg1Abr=11, + tseg2Abr=12, + sjwDbr=13, + tseg1Dbr=14, + tseg2Dbr=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: + 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: + 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_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_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(OSError): + # do not set the _testing argument, since it supresses the exception + can.Bus(channel=0, bustype="vector") + + +def xlGetApplConfig( + app_name_p: ctypes.c_char_p, + app_channel: ctypes.c_uint, + hw_type: ctypes.POINTER(ctypes.c_uint), + hw_index: ctypes.POINTER(ctypes.c_uint), + hw_channel: ctypes.POINTER(ctypes.c_uint), + bus_type: ctypes.c_uint, +) -> int: + hw_type.value = 1 + hw_channel.value = app_channel + return 0 + + +def xlGetChannelIndex( + hw_type: ctypes.c_int, hw_index: ctypes.c_int, hw_channel: ctypes.c_int +) -> int: + return hw_channel + + +def xlOpenPort( + port_handle_p: ctypes.POINTER(xlclass.XLportHandle), + app_name_p: ctypes.c_char_p, + 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 + return 0 + + +def xlGetSyncTime( + port_handle: xlclass.XLportHandle, time_p: ctypes.POINTER(xlclass.XLuint64) +) -> int: + time_p.value = 544219859027581 + return 0 + + +def xlSetNotification( + port_handle: xlclass.XLportHandle, + event_handle: ctypes.POINTER(xlclass.XLhandle), + queue_level: ctypes.c_int, +) -> int: + event_handle.value = 520 + return 0 + + +def xlReceive( + port_handle: xlclass.XLportHandle, + event_count_p: ctypes.POINTER(ctypes.c_uint), + event: ctypes.POINTER(xlclass.XLevent), +) -> int: + event.tag = xldefine.XL_EventTags.XL_RECEIVE_MSG.value + event.tagData.msg.id = 0x123 + event.tagData.msg.dlc = 8 + event.tagData.msg.flags = 0 + event.timeStamp = 0 + event.chanIndex = 0 + for idx, value in enumerate([1, 2, 3, 4, 5, 6, 7, 8]): + event.tagData.msg.data[idx] = value + return 0 + + +def xlCanReceive( + port_handle: xlclass.XLportHandle, event: ctypes.POINTER(xlclass.XLcanRxEvent) +) -> int: + event.tag = xldefine.XL_CANFD_RX_EventTags.XL_CAN_EV_TAG_RX_OK.value + event.tagData.canRxOkMsg.canId = 0x123 + event.tagData.canRxOkMsg.dlc = 8 + event.tagData.canRxOkMsg.msgFlags = 0 + event.timeStamp = 0 + event.chanIndex = 0 + for idx, value in enumerate([1, 2, 3, 4, 5, 6, 7, 8]): + event.tagData.canRxOkMsg.data[idx] = value + return 0 + + +if __name__ == "__main__": + unittest.main()